bevy_pbr/
material_bind_groups.rs

1//! Material bind group management for bindless resources.
2//!
3//! In bindless mode, Bevy's renderer groups materials into bind groups. This
4//! allocator manages each bind group, assigning slots to materials as
5//! appropriate.
6
7use crate::Material;
8use bevy_derive::{Deref, DerefMut};
9use bevy_ecs::{
10    resource::Resource,
11    system::{Commands, Res},
12};
13use bevy_platform::collections::{HashMap, HashSet};
14use bevy_reflect::{prelude::ReflectDefault, Reflect};
15use bevy_render::render_resource::BindlessSlabResourceLimit;
16use bevy_render::{
17    render_resource::{
18        BindGroup, BindGroupEntry, BindGroupLayout, BindingNumber, BindingResource,
19        BindingResources, BindlessDescriptor, BindlessIndex, BindlessIndexTableDescriptor,
20        BindlessResourceType, Buffer, BufferBinding, BufferDescriptor, BufferId,
21        BufferInitDescriptor, BufferUsages, CompareFunction, FilterMode, OwnedBindingResource,
22        PreparedBindGroup, RawBufferVec, Sampler, SamplerDescriptor, SamplerId, TextureView,
23        TextureViewDimension, TextureViewId, UnpreparedBindGroup, WgpuSampler, WgpuTextureView,
24    },
25    renderer::{RenderDevice, RenderQueue},
26    settings::WgpuFeatures,
27    texture::FallbackImage,
28};
29use bevy_utils::{default, TypeIdMap};
30use bytemuck::Pod;
31use core::hash::Hash;
32use core::{cmp::Ordering, iter, mem, ops::Range};
33use tracing::{error, trace};
34
35#[derive(Resource, Deref, DerefMut, Default)]
36pub struct MaterialBindGroupAllocators(TypeIdMap<MaterialBindGroupAllocator>);
37
38/// A resource that places materials into bind groups and tracks their
39/// resources.
40///
41/// Internally, Bevy has separate allocators for bindless and non-bindless
42/// materials. This resource provides a common interface to the specific
43/// allocator in use.
44pub enum MaterialBindGroupAllocator {
45    /// The allocator used when the material is bindless.
46    Bindless(Box<MaterialBindGroupBindlessAllocator>),
47    /// The allocator used when the material is non-bindless.
48    NonBindless(Box<MaterialBindGroupNonBindlessAllocator>),
49}
50
51/// The allocator that places bindless materials into bind groups and tracks
52/// their resources.
53pub struct MaterialBindGroupBindlessAllocator {
54    /// The label of the bind group allocator to use for allocated buffers.
55    label: Option<&'static str>,
56    /// The slabs, each of which contains a bind group.
57    slabs: Vec<MaterialBindlessSlab>,
58    /// The layout of the bind groups that we produce.
59    bind_group_layout: BindGroupLayout,
60    /// Information about the bindless resources in the material.
61    ///
62    /// We use this information to create and maintain bind groups.
63    bindless_descriptor: BindlessDescriptor,
64
65    /// Dummy buffers that we use to fill empty slots in buffer binding arrays.
66    ///
67    /// There's one fallback buffer for each buffer in the bind group, each
68    /// appropriately sized. Each buffer contains one uninitialized element of
69    /// the applicable type.
70    fallback_buffers: HashMap<BindlessIndex, Buffer>,
71
72    /// The maximum number of resources that can be stored in a slab.
73    ///
74    /// This corresponds to `SLAB_CAPACITY` in the `#[bindless(SLAB_CAPACITY)]`
75    /// attribute, when deriving `AsBindGroup`.
76    slab_capacity: u32,
77}
78
79/// A single bind group and the bookkeeping necessary to allocate into it.
80pub struct MaterialBindlessSlab {
81    /// The current bind group, if it's up to date.
82    ///
83    /// If this is `None`, then the bind group is dirty and needs to be
84    /// regenerated.
85    bind_group: Option<BindGroup>,
86
87    /// The GPU-accessible buffers that hold the mapping from binding index to
88    /// bindless slot.
89    ///
90    /// This is conventionally assigned to bind group binding 0, but it can be
91    /// changed using the `#[bindless(index_table(binding(B)))]` attribute on
92    /// `AsBindGroup`.
93    ///
94    /// Because the slab binary searches this table, the entries within must be
95    /// sorted by bindless index.
96    bindless_index_tables: Vec<MaterialBindlessIndexTable>,
97
98    /// The binding arrays containing samplers.
99    samplers: HashMap<BindlessResourceType, MaterialBindlessBindingArray<Sampler>>,
100    /// The binding arrays containing textures.
101    textures: HashMap<BindlessResourceType, MaterialBindlessBindingArray<TextureView>>,
102    /// The binding arrays containing buffers.
103    buffers: HashMap<BindlessIndex, MaterialBindlessBindingArray<Buffer>>,
104    /// The buffers that contain plain old data (i.e. the structure-level
105    /// `#[data]` attribute of `AsBindGroup`).
106    data_buffers: HashMap<BindlessIndex, MaterialDataBuffer>,
107
108    /// A list of free slot IDs.
109    free_slots: Vec<MaterialBindGroupSlot>,
110    /// The total number of materials currently allocated in this slab.
111    live_allocation_count: u32,
112    /// The total number of resources currently allocated in the binding arrays.
113    allocated_resource_count: u32,
114}
115
116/// A GPU-accessible buffer that holds the mapping from binding index to
117/// bindless slot.
118///
119/// This is conventionally assigned to bind group binding 0, but it can be
120/// changed by altering the [`Self::binding_number`], which corresponds to the
121/// `#[bindless(index_table(binding(B)))]` attribute in `AsBindGroup`.
122struct MaterialBindlessIndexTable {
123    /// The buffer containing the mappings.
124    buffer: RetainedRawBufferVec<u32>,
125    /// The range of bindless indices that this bindless index table covers.
126    ///
127    /// If this range is M..N, then the field at index $i$ maps to bindless
128    /// index $i$ + M. The size of this table is N - M.
129    ///
130    /// This corresponds to the `#[bindless(index_table(range(M..N)))]`
131    /// attribute in `AsBindGroup`.
132    index_range: Range<BindlessIndex>,
133    /// The binding number that this index table is assigned to in the shader.
134    binding_number: BindingNumber,
135}
136
137/// A single binding array for storing bindless resources and the bookkeeping
138/// necessary to allocate into it.
139struct MaterialBindlessBindingArray<R>
140where
141    R: GetBindingResourceId,
142{
143    /// The number of the binding that we attach this binding array to.
144    binding_number: BindingNumber,
145    /// A mapping from bindless slot index to the resource stored in that slot,
146    /// if any.
147    bindings: Vec<Option<MaterialBindlessBinding<R>>>,
148    /// The type of resource stored in this binding array.
149    resource_type: BindlessResourceType,
150    /// Maps a resource ID to the slot in which it's stored.
151    ///
152    /// This is essentially the inverse mapping of [`Self::bindings`].
153    resource_to_slot: HashMap<BindingResourceId, u32>,
154    /// A list of free slots in [`Self::bindings`] that contain no binding.
155    free_slots: Vec<u32>,
156    /// The number of allocated objects in this binding array.
157    len: u32,
158}
159
160/// A single resource (sampler, texture, or buffer) in a binding array.
161///
162/// Resources hold a reference count, which specifies the number of materials
163/// currently allocated within the slab that refer to this resource. When the
164/// reference count drops to zero, the resource is freed.
165struct MaterialBindlessBinding<R>
166where
167    R: GetBindingResourceId,
168{
169    /// The sampler, texture, or buffer.
170    resource: R,
171    /// The number of materials currently allocated within the containing slab
172    /// that use this resource.
173    ref_count: u32,
174}
175
176/// The allocator that stores bind groups for non-bindless materials.
177pub struct MaterialBindGroupNonBindlessAllocator {
178    /// The label of the bind group allocator to use for allocated buffers.
179    label: Option<&'static str>,
180    /// A mapping from [`MaterialBindGroupIndex`] to the bind group allocated in
181    /// each slot.
182    bind_groups: Vec<Option<MaterialNonBindlessAllocatedBindGroup>>,
183    /// The bind groups that are dirty and need to be prepared.
184    ///
185    /// To prepare the bind groups, call
186    /// [`MaterialBindGroupAllocator::prepare_bind_groups`].
187    to_prepare: HashSet<MaterialBindGroupIndex>,
188    /// A list of free bind group indices.
189    free_indices: Vec<MaterialBindGroupIndex>,
190}
191
192/// A single bind group that a [`MaterialBindGroupNonBindlessAllocator`] is
193/// currently managing.
194enum MaterialNonBindlessAllocatedBindGroup {
195    /// An unprepared bind group.
196    ///
197    /// The allocator prepares all outstanding unprepared bind groups when
198    /// [`MaterialBindGroupNonBindlessAllocator::prepare_bind_groups`] is
199    /// called.
200    Unprepared {
201        /// The unprepared bind group, including extra data.
202        bind_group: UnpreparedBindGroup,
203        /// The layout of that bind group.
204        layout: BindGroupLayout,
205    },
206    /// A bind group that's already been prepared.
207    Prepared {
208        bind_group: PreparedBindGroup,
209        #[expect(dead_code, reason = "These buffers are only referenced by bind groups")]
210        uniform_buffers: Vec<Buffer>,
211    },
212}
213
214/// Dummy instances of various resources that we fill unused slots in binding
215/// arrays with.
216#[derive(Resource)]
217pub struct FallbackBindlessResources {
218    /// A dummy filtering sampler.
219    filtering_sampler: Sampler,
220    /// A dummy non-filtering sampler.
221    non_filtering_sampler: Sampler,
222    /// A dummy comparison sampler.
223    comparison_sampler: Sampler,
224}
225
226/// The `wgpu` ID of a single bindless or non-bindless resource.
227#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
228enum BindingResourceId {
229    /// A buffer.
230    Buffer(BufferId),
231    /// A texture view, with the given dimension.
232    TextureView(TextureViewDimension, TextureViewId),
233    /// A sampler.
234    Sampler(SamplerId),
235    /// A buffer containing plain old data.
236    ///
237    /// This corresponds to the `#[data]` structure-level attribute on
238    /// `AsBindGroup`.
239    DataBuffer,
240}
241
242/// A temporary list of references to `wgpu` bindless resources.
243///
244/// We need this because the `wgpu` bindless API takes a slice of references.
245/// Thus we need to create intermediate vectors of bindless resources in order
246/// to satisfy `wgpu`'s lifetime requirements.
247enum BindingResourceArray<'a> {
248    /// A list of bindings.
249    Buffers(Vec<BufferBinding<'a>>),
250    /// A list of texture views.
251    TextureViews(Vec<&'a WgpuTextureView>),
252    /// A list of samplers.
253    Samplers(Vec<&'a WgpuSampler>),
254}
255
256/// The location of a material (either bindless or non-bindless) within the
257/// slabs.
258#[derive(Clone, Copy, Debug, Default, Reflect)]
259#[reflect(Clone, Default)]
260pub struct MaterialBindingId {
261    /// The index of the bind group (slab) where the GPU data is located.
262    pub group: MaterialBindGroupIndex,
263    /// The slot within that bind group.
264    ///
265    /// Non-bindless materials will always have a slot of 0.
266    pub slot: MaterialBindGroupSlot,
267}
268
269/// The index of each material bind group.
270///
271/// In bindless mode, each bind group contains multiple materials. In
272/// non-bindless mode, each bind group contains only one material.
273#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect, Deref, DerefMut)]
274#[reflect(Default, Clone, PartialEq, Hash)]
275pub struct MaterialBindGroupIndex(pub u32);
276
277impl From<u32> for MaterialBindGroupIndex {
278    fn from(value: u32) -> Self {
279        MaterialBindGroupIndex(value)
280    }
281}
282
283/// The index of the slot containing material data within each material bind
284/// group.
285///
286/// In bindless mode, this slot is needed to locate the material data in each
287/// bind group, since multiple materials are packed into a single slab. In
288/// non-bindless mode, this slot is always 0.
289#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]
290#[reflect(Default, Clone, PartialEq)]
291pub struct MaterialBindGroupSlot(pub u32);
292
293/// The CPU/GPU synchronization state of a buffer that we maintain.
294///
295/// Currently, the only buffer that we maintain is the
296/// [`MaterialBindlessIndexTable`].
297enum BufferDirtyState {
298    /// The buffer is currently synchronized between the CPU and GPU.
299    Clean,
300    /// The buffer hasn't been created yet.
301    NeedsReserve,
302    /// The buffer exists on both CPU and GPU, but the GPU data is out of date.
303    NeedsUpload,
304}
305
306/// Information that describes a potential allocation of an
307/// [`UnpreparedBindGroup`] into a slab.
308struct BindlessAllocationCandidate {
309    /// A map that, for every resource in the [`UnpreparedBindGroup`] that
310    /// already existed in this slab, maps bindless index of that resource to
311    /// its slot in the appropriate binding array.
312    pre_existing_resources: HashMap<BindlessIndex, u32>,
313    /// Stores the number of free slots that are needed to satisfy this
314    /// allocation.
315    needed_free_slots: u32,
316}
317
318/// A trait that allows fetching the [`BindingResourceId`] from a
319/// [`BindlessResourceType`].
320///
321/// This is used when freeing bindless resources, in order to locate the IDs
322/// assigned to each resource so that they can be removed from the appropriate
323/// maps.
324trait GetBindingResourceId {
325    /// Returns the [`BindingResourceId`] for this resource.
326    ///
327    /// `resource_type` specifies this resource's type. This is used for
328    /// textures, as a `wgpu` [`TextureView`] doesn't store enough information
329    /// itself to determine its dimension.
330    fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId;
331}
332
333/// The public interface to a slab, which represents a single bind group.
334pub struct MaterialSlab<'a>(MaterialSlabImpl<'a>);
335
336/// The actual implementation of a material slab.
337///
338/// This has bindless and non-bindless variants.
339enum MaterialSlabImpl<'a> {
340    /// The implementation of the slab interface we use when the slab
341    /// is bindless.
342    Bindless(&'a MaterialBindlessSlab),
343    /// The implementation of the slab interface we use when the slab
344    /// is non-bindless.
345    NonBindless(MaterialNonBindlessSlab<'a>),
346}
347
348/// A single bind group that the [`MaterialBindGroupNonBindlessAllocator`]
349/// manages.
350enum MaterialNonBindlessSlab<'a> {
351    /// A slab that has a bind group.
352    Prepared(&'a PreparedBindGroup),
353    /// A slab that doesn't yet have a bind group.
354    Unprepared,
355}
356
357/// Manages an array of untyped plain old data on GPU and allocates individual
358/// slots within that array.
359///
360/// This supports the `#[data]` attribute of `AsBindGroup`.
361struct MaterialDataBuffer {
362    /// The number of the binding that we attach this storage buffer to.
363    binding_number: BindingNumber,
364    /// The actual data.
365    ///
366    /// Note that this is untyped (`u8`); the actual aligned size of each
367    /// element is given by [`Self::aligned_element_size`];
368    buffer: RetainedRawBufferVec<u8>,
369    /// The size of each element in the buffer, including padding and alignment
370    /// if any.
371    aligned_element_size: u32,
372    /// A list of free slots within the buffer.
373    free_slots: Vec<u32>,
374    /// The actual number of slots that have been allocated.
375    len: u32,
376}
377
378/// A buffer containing plain old data, already packed into the appropriate GPU
379/// format, and that can be updated incrementally.
380///
381/// This structure exists in order to encapsulate the lazy update
382/// ([`BufferDirtyState`]) logic in a single place.
383#[derive(Deref, DerefMut)]
384struct RetainedRawBufferVec<T>
385where
386    T: Pod,
387{
388    /// The contents of the buffer.
389    #[deref]
390    buffer: RawBufferVec<T>,
391    /// Whether the contents of the buffer have been uploaded to the GPU.
392    dirty: BufferDirtyState,
393}
394
395/// The size of the buffer that we assign to unused buffer slots, in bytes.
396///
397/// This is essentially arbitrary, as it doesn't seem to matter to `wgpu` what
398/// the size is.
399const DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE: u64 = 16;
400
401impl From<u32> for MaterialBindGroupSlot {
402    fn from(value: u32) -> Self {
403        MaterialBindGroupSlot(value)
404    }
405}
406
407impl From<MaterialBindGroupSlot> for u32 {
408    fn from(value: MaterialBindGroupSlot) -> Self {
409        value.0
410    }
411}
412
413impl<'a> From<&'a OwnedBindingResource> for BindingResourceId {
414    fn from(value: &'a OwnedBindingResource) -> Self {
415        match *value {
416            OwnedBindingResource::Buffer(ref buffer) => BindingResourceId::Buffer(buffer.id()),
417            OwnedBindingResource::Data(_) => BindingResourceId::DataBuffer,
418            OwnedBindingResource::TextureView(ref texture_view_dimension, ref texture_view) => {
419                BindingResourceId::TextureView(*texture_view_dimension, texture_view.id())
420            }
421            OwnedBindingResource::Sampler(_, ref sampler) => {
422                BindingResourceId::Sampler(sampler.id())
423            }
424        }
425    }
426}
427
428impl GetBindingResourceId for Buffer {
429    fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {
430        BindingResourceId::Buffer(self.id())
431    }
432}
433
434impl GetBindingResourceId for Sampler {
435    fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {
436        BindingResourceId::Sampler(self.id())
437    }
438}
439
440impl GetBindingResourceId for TextureView {
441    fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId {
442        let texture_view_dimension = match resource_type {
443            BindlessResourceType::Texture1d => TextureViewDimension::D1,
444            BindlessResourceType::Texture2d => TextureViewDimension::D2,
445            BindlessResourceType::Texture2dArray => TextureViewDimension::D2Array,
446            BindlessResourceType::Texture3d => TextureViewDimension::D3,
447            BindlessResourceType::TextureCube => TextureViewDimension::Cube,
448            BindlessResourceType::TextureCubeArray => TextureViewDimension::CubeArray,
449            _ => panic!("Resource type is not a texture"),
450        };
451        BindingResourceId::TextureView(texture_view_dimension, self.id())
452    }
453}
454
455impl MaterialBindGroupAllocator {
456    /// Creates a new [`MaterialBindGroupAllocator`] managing the data for a
457    /// single material.
458    pub fn new(
459        render_device: &RenderDevice,
460        label: Option<&'static str>,
461        bindless_descriptor: Option<BindlessDescriptor>,
462        bind_group_layout: BindGroupLayout,
463        slab_capacity: Option<BindlessSlabResourceLimit>,
464    ) -> MaterialBindGroupAllocator {
465        if let Some(bindless_descriptor) = bindless_descriptor {
466            MaterialBindGroupAllocator::Bindless(Box::new(MaterialBindGroupBindlessAllocator::new(
467                render_device,
468                label,
469                bindless_descriptor,
470                bind_group_layout,
471                slab_capacity,
472            )))
473        } else {
474            MaterialBindGroupAllocator::NonBindless(Box::new(
475                MaterialBindGroupNonBindlessAllocator::new(label),
476            ))
477        }
478    }
479
480    /// Returns the slab with the given index, if one exists.
481    pub fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialSlab<'_>> {
482        match *self {
483            MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator
484                .get(group)
485                .map(|bindless_slab| MaterialSlab(MaterialSlabImpl::Bindless(bindless_slab))),
486            MaterialBindGroupAllocator::NonBindless(ref non_bindless_allocator) => {
487                non_bindless_allocator.get(group).map(|non_bindless_slab| {
488                    MaterialSlab(MaterialSlabImpl::NonBindless(non_bindless_slab))
489                })
490            }
491        }
492    }
493
494    /// Allocates an [`UnpreparedBindGroup`] and returns the resulting binding ID.
495    ///
496    /// This method should generally be preferred over
497    /// [`Self::allocate_prepared`], because this method supports both bindless
498    /// and non-bindless bind groups. Only use [`Self::allocate_prepared`] if
499    /// you need to prepare the bind group yourself.
500    pub fn allocate_unprepared(
501        &mut self,
502        unprepared_bind_group: UnpreparedBindGroup,
503        bind_group_layout: &BindGroupLayout,
504    ) -> MaterialBindingId {
505        match *self {
506            MaterialBindGroupAllocator::Bindless(
507                ref mut material_bind_group_bindless_allocator,
508            ) => material_bind_group_bindless_allocator.allocate_unprepared(unprepared_bind_group),
509            MaterialBindGroupAllocator::NonBindless(
510                ref mut material_bind_group_non_bindless_allocator,
511            ) => material_bind_group_non_bindless_allocator
512                .allocate_unprepared(unprepared_bind_group, (*bind_group_layout).clone()),
513        }
514    }
515
516    /// Places a pre-prepared bind group into a slab.
517    ///
518    /// For bindless materials, the allocator internally manages the bind
519    /// groups, so calling this method will panic if this is a bindless
520    /// allocator. Only non-bindless allocators support this method.
521    ///
522    /// It's generally preferred to use [`Self::allocate_unprepared`], because
523    /// that method supports both bindless and non-bindless allocators. Only use
524    /// this method if you need to prepare the bind group yourself.
525    pub fn allocate_prepared(
526        &mut self,
527        prepared_bind_group: PreparedBindGroup,
528    ) -> MaterialBindingId {
529        match *self {
530            MaterialBindGroupAllocator::Bindless(_) => {
531                panic!(
532                    "Bindless resources are incompatible with implementing `as_bind_group` \
533                     directly; implement `unprepared_bind_group` instead or disable bindless"
534                )
535            }
536            MaterialBindGroupAllocator::NonBindless(ref mut non_bindless_allocator) => {
537                non_bindless_allocator.allocate_prepared(prepared_bind_group)
538            }
539        }
540    }
541
542    /// Deallocates the material with the given binding ID.
543    ///
544    /// Any resources that are no longer referenced are removed from the slab.
545    pub fn free(&mut self, material_binding_id: MaterialBindingId) {
546        match *self {
547            MaterialBindGroupAllocator::Bindless(
548                ref mut material_bind_group_bindless_allocator,
549            ) => material_bind_group_bindless_allocator.free(material_binding_id),
550            MaterialBindGroupAllocator::NonBindless(
551                ref mut material_bind_group_non_bindless_allocator,
552            ) => material_bind_group_non_bindless_allocator.free(material_binding_id),
553        }
554    }
555
556    /// Recreates any bind groups corresponding to slabs that have been modified
557    /// since last calling [`MaterialBindGroupAllocator::prepare_bind_groups`].
558    pub fn prepare_bind_groups(
559        &mut self,
560        render_device: &RenderDevice,
561        fallback_bindless_resources: &FallbackBindlessResources,
562        fallback_image: &FallbackImage,
563    ) {
564        match *self {
565            MaterialBindGroupAllocator::Bindless(
566                ref mut material_bind_group_bindless_allocator,
567            ) => material_bind_group_bindless_allocator.prepare_bind_groups(
568                render_device,
569                fallback_bindless_resources,
570                fallback_image,
571            ),
572            MaterialBindGroupAllocator::NonBindless(
573                ref mut material_bind_group_non_bindless_allocator,
574            ) => material_bind_group_non_bindless_allocator.prepare_bind_groups(render_device),
575        }
576    }
577
578    /// Uploads the contents of all buffers that this
579    /// [`MaterialBindGroupAllocator`] manages to the GPU.
580    ///
581    /// Non-bindless allocators don't currently manage any buffers, so this
582    /// method only has an effect for bindless allocators.
583    pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
584        match *self {
585            MaterialBindGroupAllocator::Bindless(
586                ref mut material_bind_group_bindless_allocator,
587            ) => material_bind_group_bindless_allocator.write_buffers(render_device, render_queue),
588            MaterialBindGroupAllocator::NonBindless(_) => {
589                // Not applicable.
590            }
591        }
592    }
593}
594
595impl MaterialBindlessIndexTable {
596    /// Creates a new [`MaterialBindlessIndexTable`] for a single slab.
597    fn new(
598        bindless_index_table_descriptor: &BindlessIndexTableDescriptor,
599    ) -> MaterialBindlessIndexTable {
600        // Preallocate space for one bindings table, so that there will always be a buffer.
601        let mut buffer = RetainedRawBufferVec::new(BufferUsages::STORAGE);
602        for _ in *bindless_index_table_descriptor.indices.start
603            ..*bindless_index_table_descriptor.indices.end
604        {
605            buffer.push(0);
606        }
607
608        MaterialBindlessIndexTable {
609            buffer,
610            index_range: bindless_index_table_descriptor.indices.clone(),
611            binding_number: bindless_index_table_descriptor.binding_number,
612        }
613    }
614
615    /// Returns the bindings in the binding index table.
616    ///
617    /// If the current [`MaterialBindlessIndexTable::index_range`] is M..N, then
618    /// element *i* of the returned binding index table contains the slot of the
619    /// bindless resource with bindless index *i* + M.
620    fn get(&self, slot: MaterialBindGroupSlot) -> &[u32] {
621        let struct_size = *self.index_range.end as usize - *self.index_range.start as usize;
622        let start = struct_size * slot.0 as usize;
623        &self.buffer.values()[start..(start + struct_size)]
624    }
625
626    /// Returns a single binding from the binding index table.
627    fn get_binding(
628        &self,
629        slot: MaterialBindGroupSlot,
630        bindless_index: BindlessIndex,
631    ) -> Option<u32> {
632        if bindless_index < self.index_range.start || bindless_index >= self.index_range.end {
633            return None;
634        }
635        self.get(slot)
636            .get((*bindless_index - *self.index_range.start) as usize)
637            .copied()
638    }
639
640    fn table_length(&self) -> u32 {
641        self.index_range.end.0 - self.index_range.start.0
642    }
643
644    /// Updates the binding index table for a single material.
645    ///
646    /// The `allocated_resource_slots` map contains a mapping from the
647    /// [`BindlessIndex`] of each resource that the material references to the
648    /// slot that that resource occupies in the appropriate binding array. This
649    /// method serializes that map into a binding index table that the shader
650    /// can read.
651    fn set(
652        &mut self,
653        slot: MaterialBindGroupSlot,
654        allocated_resource_slots: &HashMap<BindlessIndex, u32>,
655    ) {
656        let table_len = self.table_length() as usize;
657        let range = (slot.0 as usize * table_len)..((slot.0 as usize + 1) * table_len);
658        while self.buffer.len() < range.end {
659            self.buffer.push(0);
660        }
661
662        for (&bindless_index, &resource_slot) in allocated_resource_slots {
663            if self.index_range.contains(&bindless_index) {
664                self.buffer.set(
665                    *bindless_index + range.start as u32 - *self.index_range.start,
666                    resource_slot,
667                );
668            }
669        }
670
671        // Mark the buffer as needing to be recreated, in case we grew it.
672        self.buffer.dirty = BufferDirtyState::NeedsReserve;
673    }
674
675    /// Returns the [`BindGroupEntry`] for the index table itself.
676    fn bind_group_entry(&self) -> BindGroupEntry<'_> {
677        BindGroupEntry {
678            binding: *self.binding_number,
679            resource: self
680                .buffer
681                .buffer()
682                .expect("Bindings buffer must exist")
683                .as_entire_binding(),
684        }
685    }
686}
687
688impl<T> RetainedRawBufferVec<T>
689where
690    T: Pod,
691{
692    /// Creates a new empty [`RetainedRawBufferVec`] supporting the given
693    /// [`BufferUsages`].
694    fn new(buffer_usages: BufferUsages) -> RetainedRawBufferVec<T> {
695        RetainedRawBufferVec {
696            buffer: RawBufferVec::new(buffer_usages),
697            dirty: BufferDirtyState::NeedsUpload,
698        }
699    }
700
701    /// Recreates the GPU backing buffer if needed.
702    fn prepare(&mut self, render_device: &RenderDevice) {
703        match self.dirty {
704            BufferDirtyState::Clean | BufferDirtyState::NeedsUpload => {}
705            BufferDirtyState::NeedsReserve => {
706                let capacity = self.buffer.len();
707                self.buffer.reserve(capacity, render_device);
708                self.dirty = BufferDirtyState::NeedsUpload;
709            }
710        }
711    }
712
713    /// Writes the current contents of the buffer to the GPU if necessary.
714    fn write(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
715        match self.dirty {
716            BufferDirtyState::Clean => {}
717            BufferDirtyState::NeedsReserve | BufferDirtyState::NeedsUpload => {
718                self.buffer.write_buffer(render_device, render_queue);
719                self.dirty = BufferDirtyState::Clean;
720            }
721        }
722    }
723}
724
725impl MaterialBindGroupBindlessAllocator {
726    /// Creates a new [`MaterialBindGroupBindlessAllocator`] managing the data
727    /// for a single bindless material.
728    fn new(
729        render_device: &RenderDevice,
730        label: Option<&'static str>,
731        bindless_descriptor: BindlessDescriptor,
732        bind_group_layout: BindGroupLayout,
733        slab_capacity: Option<BindlessSlabResourceLimit>,
734    ) -> MaterialBindGroupBindlessAllocator {
735        let fallback_buffers = bindless_descriptor
736            .buffers
737            .iter()
738            .map(|bindless_buffer_descriptor| {
739                (
740                    bindless_buffer_descriptor.bindless_index,
741                    render_device.create_buffer(&BufferDescriptor {
742                        label: Some("bindless fallback buffer"),
743                        size: match bindless_buffer_descriptor.size {
744                            Some(size) => size as u64,
745                            None => DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE,
746                        },
747                        usage: BufferUsages::STORAGE,
748                        mapped_at_creation: false,
749                    }),
750                )
751            })
752            .collect();
753
754        MaterialBindGroupBindlessAllocator {
755            label,
756            slabs: vec![],
757            bind_group_layout,
758            bindless_descriptor,
759            fallback_buffers,
760            slab_capacity: slab_capacity
761                .expect("Non-bindless materials should use the non-bindless allocator")
762                .resolve(),
763        }
764    }
765
766    /// Allocates the resources for a single material into a slab and returns
767    /// the resulting ID.
768    ///
769    /// The returned [`MaterialBindingId`] can later be used to fetch the slab
770    /// that was used.
771    ///
772    /// This function can't fail. If all slabs are full, then a new slab is
773    /// created, and the material is allocated into it.
774    fn allocate_unprepared(
775        &mut self,
776        mut unprepared_bind_group: UnpreparedBindGroup,
777    ) -> MaterialBindingId {
778        for (slab_index, slab) in self.slabs.iter_mut().enumerate() {
779            trace!("Trying to allocate in slab {}", slab_index);
780            match slab.try_allocate(unprepared_bind_group, self.slab_capacity) {
781                Ok(slot) => {
782                    return MaterialBindingId {
783                        group: MaterialBindGroupIndex(slab_index as u32),
784                        slot,
785                    };
786                }
787                Err(bind_group) => unprepared_bind_group = bind_group,
788            }
789        }
790
791        let group = MaterialBindGroupIndex(self.slabs.len() as u32);
792        self.slabs
793            .push(MaterialBindlessSlab::new(&self.bindless_descriptor));
794
795        // Allocate into the newly-pushed slab.
796        let Ok(slot) = self
797            .slabs
798            .last_mut()
799            .expect("We just pushed a slab")
800            .try_allocate(unprepared_bind_group, self.slab_capacity)
801        else {
802            panic!("An allocation into an empty slab should always succeed")
803        };
804
805        MaterialBindingId { group, slot }
806    }
807
808    /// Deallocates the material with the given binding ID.
809    ///
810    /// Any resources that are no longer referenced are removed from the slab.
811    fn free(&mut self, material_binding_id: MaterialBindingId) {
812        self.slabs
813            .get_mut(material_binding_id.group.0 as usize)
814            .expect("Slab should exist")
815            .free(material_binding_id.slot, &self.bindless_descriptor);
816    }
817
818    /// Returns the slab with the given bind group index.
819    ///
820    /// A [`MaterialBindGroupIndex`] can be fetched from a
821    /// [`MaterialBindingId`].
822    fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab> {
823        self.slabs.get(group.0 as usize)
824    }
825
826    /// Recreates any bind groups corresponding to slabs that have been modified
827    /// since last calling
828    /// [`MaterialBindGroupBindlessAllocator::prepare_bind_groups`].
829    fn prepare_bind_groups(
830        &mut self,
831        render_device: &RenderDevice,
832        fallback_bindless_resources: &FallbackBindlessResources,
833        fallback_image: &FallbackImage,
834    ) {
835        for slab in &mut self.slabs {
836            slab.prepare(
837                render_device,
838                self.label,
839                &self.bind_group_layout,
840                fallback_bindless_resources,
841                &self.fallback_buffers,
842                fallback_image,
843                &self.bindless_descriptor,
844                self.slab_capacity,
845            );
846        }
847    }
848
849    /// Writes any buffers that we're managing to the GPU.
850    ///
851    /// Currently, this only consists of the bindless index tables.
852    fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
853        for slab in &mut self.slabs {
854            slab.write_buffer(render_device, render_queue);
855        }
856    }
857}
858
859impl MaterialBindlessSlab {
860    /// Attempts to allocate the given unprepared bind group in this slab.
861    ///
862    /// If the allocation succeeds, this method returns the slot that the
863    /// allocation was placed in. If the allocation fails because the slab was
864    /// full, this method returns the unprepared bind group back to the caller
865    /// so that it can try to allocate again.
866    fn try_allocate(
867        &mut self,
868        unprepared_bind_group: UnpreparedBindGroup,
869        slot_capacity: u32,
870    ) -> Result<MaterialBindGroupSlot, UnpreparedBindGroup> {
871        // Locate pre-existing resources, and determine how many free slots we need.
872        let Some(allocation_candidate) = self.check_allocation(&unprepared_bind_group) else {
873            return Err(unprepared_bind_group);
874        };
875
876        // Check to see if we have enough free space.
877        //
878        // As a special case, note that if *nothing* is allocated in this slab,
879        // then we always allow a material to be placed in it, regardless of the
880        // number of bindings the material has. This is so that, if the
881        // platform's maximum bindless count is set too low to hold even a
882        // single material, we can still place each material into a separate
883        // slab instead of failing outright.
884        if self.allocated_resource_count > 0
885            && self.allocated_resource_count + allocation_candidate.needed_free_slots
886                > slot_capacity
887        {
888            trace!("Slab is full, can't allocate");
889            return Err(unprepared_bind_group);
890        }
891
892        // OK, we can allocate in this slab. Assign a slot ID.
893        let slot = self
894            .free_slots
895            .pop()
896            .unwrap_or(MaterialBindGroupSlot(self.live_allocation_count));
897
898        // Bump the live allocation count.
899        self.live_allocation_count += 1;
900
901        // Insert the resources into the binding arrays.
902        let allocated_resource_slots =
903            self.insert_resources(unprepared_bind_group.bindings, allocation_candidate);
904
905        // Serialize the allocated resource slots.
906        for bindless_index_table in &mut self.bindless_index_tables {
907            bindless_index_table.set(slot, &allocated_resource_slots);
908        }
909
910        // Invalidate the cached bind group.
911        self.bind_group = None;
912
913        Ok(slot)
914    }
915
916    /// Gathers the information needed to determine whether the given unprepared
917    /// bind group can be allocated in this slab.
918    fn check_allocation(
919        &self,
920        unprepared_bind_group: &UnpreparedBindGroup,
921    ) -> Option<BindlessAllocationCandidate> {
922        let mut allocation_candidate = BindlessAllocationCandidate {
923            pre_existing_resources: HashMap::default(),
924            needed_free_slots: 0,
925        };
926
927        for &(bindless_index, ref owned_binding_resource) in unprepared_bind_group.bindings.iter() {
928            let bindless_index = BindlessIndex(bindless_index);
929            match *owned_binding_resource {
930                OwnedBindingResource::Buffer(ref buffer) => {
931                    let Some(binding_array) = self.buffers.get(&bindless_index) else {
932                        error!(
933                            "Binding array wasn't present for buffer at index {:?}",
934                            bindless_index
935                        );
936                        return None;
937                    };
938                    match binding_array.find(BindingResourceId::Buffer(buffer.id())) {
939                        Some(slot) => {
940                            allocation_candidate
941                                .pre_existing_resources
942                                .insert(bindless_index, slot);
943                        }
944                        None => allocation_candidate.needed_free_slots += 1,
945                    }
946                }
947
948                OwnedBindingResource::Data(_) => {
949                    // The size of a data buffer is unlimited.
950                }
951
952                OwnedBindingResource::TextureView(texture_view_dimension, ref texture_view) => {
953                    let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);
954                    match self
955                        .textures
956                        .get(&bindless_resource_type)
957                        .expect("Missing binding array for texture")
958                        .find(BindingResourceId::TextureView(
959                            texture_view_dimension,
960                            texture_view.id(),
961                        )) {
962                        Some(slot) => {
963                            allocation_candidate
964                                .pre_existing_resources
965                                .insert(bindless_index, slot);
966                        }
967                        None => {
968                            allocation_candidate.needed_free_slots += 1;
969                        }
970                    }
971                }
972
973                OwnedBindingResource::Sampler(sampler_binding_type, ref sampler) => {
974                    let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);
975                    match self
976                        .samplers
977                        .get(&bindless_resource_type)
978                        .expect("Missing binding array for sampler")
979                        .find(BindingResourceId::Sampler(sampler.id()))
980                    {
981                        Some(slot) => {
982                            allocation_candidate
983                                .pre_existing_resources
984                                .insert(bindless_index, slot);
985                        }
986                        None => {
987                            allocation_candidate.needed_free_slots += 1;
988                        }
989                    }
990                }
991            }
992        }
993
994        Some(allocation_candidate)
995    }
996
997    /// Inserts the given [`BindingResources`] into this slab.
998    ///
999    /// Returns a table that maps the bindless index of each resource to its
1000    /// slot in its binding array.
1001    fn insert_resources(
1002        &mut self,
1003        mut binding_resources: BindingResources,
1004        allocation_candidate: BindlessAllocationCandidate,
1005    ) -> HashMap<BindlessIndex, u32> {
1006        let mut allocated_resource_slots = HashMap::default();
1007
1008        for (bindless_index, owned_binding_resource) in binding_resources.drain(..) {
1009            let bindless_index = BindlessIndex(bindless_index);
1010
1011            let pre_existing_slot = allocation_candidate
1012                .pre_existing_resources
1013                .get(&bindless_index);
1014
1015            // Otherwise, we need to insert it anew.
1016            let binding_resource_id = BindingResourceId::from(&owned_binding_resource);
1017            let increment_allocated_resource_count = match owned_binding_resource {
1018                OwnedBindingResource::Buffer(buffer) => {
1019                    let slot = self
1020                        .buffers
1021                        .get_mut(&bindless_index)
1022                        .expect("Buffer binding array should exist")
1023                        .insert(binding_resource_id, buffer);
1024                    allocated_resource_slots.insert(bindless_index, slot);
1025
1026                    if let Some(pre_existing_slot) = pre_existing_slot {
1027                        assert_eq!(*pre_existing_slot, slot);
1028
1029                        false
1030                    } else {
1031                        true
1032                    }
1033                }
1034                OwnedBindingResource::Data(data) => {
1035                    if pre_existing_slot.is_some() {
1036                        panic!("Data buffers can't be deduplicated")
1037                    }
1038
1039                    let slot = self
1040                        .data_buffers
1041                        .get_mut(&bindless_index)
1042                        .expect("Data buffer binding array should exist")
1043                        .insert(&data);
1044                    allocated_resource_slots.insert(bindless_index, slot);
1045                    false
1046                }
1047                OwnedBindingResource::TextureView(texture_view_dimension, texture_view) => {
1048                    let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);
1049                    let slot = self
1050                        .textures
1051                        .get_mut(&bindless_resource_type)
1052                        .expect("Texture array should exist")
1053                        .insert(binding_resource_id, texture_view);
1054                    allocated_resource_slots.insert(bindless_index, slot);
1055
1056                    if let Some(pre_existing_slot) = pre_existing_slot {
1057                        assert_eq!(*pre_existing_slot, slot);
1058
1059                        false
1060                    } else {
1061                        true
1062                    }
1063                }
1064                OwnedBindingResource::Sampler(sampler_binding_type, sampler) => {
1065                    let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);
1066                    let slot = self
1067                        .samplers
1068                        .get_mut(&bindless_resource_type)
1069                        .expect("Sampler should exist")
1070                        .insert(binding_resource_id, sampler);
1071                    allocated_resource_slots.insert(bindless_index, slot);
1072
1073                    if let Some(pre_existing_slot) = pre_existing_slot {
1074                        assert_eq!(*pre_existing_slot, slot);
1075
1076                        false
1077                    } else {
1078                        true
1079                    }
1080                }
1081            };
1082
1083            // Bump the allocated resource count.
1084            if increment_allocated_resource_count {
1085                self.allocated_resource_count += 1;
1086            }
1087        }
1088
1089        allocated_resource_slots
1090    }
1091
1092    /// Removes the material allocated in the given slot, with the given
1093    /// descriptor, from this slab.
1094    fn free(&mut self, slot: MaterialBindGroupSlot, bindless_descriptor: &BindlessDescriptor) {
1095        // Loop through each binding.
1096        for (bindless_index, bindless_resource_type) in
1097            bindless_descriptor.resources.iter().enumerate()
1098        {
1099            let bindless_index = BindlessIndex::from(bindless_index as u32);
1100            let Some(bindless_index_table) = self.get_bindless_index_table(bindless_index) else {
1101                continue;
1102            };
1103            let Some(bindless_binding) = bindless_index_table.get_binding(slot, bindless_index)
1104            else {
1105                continue;
1106            };
1107
1108            // Free the binding. If the resource in question was anything other
1109            // than a data buffer, then it has a reference count and
1110            // consequently we need to decrement it.
1111            let decrement_allocated_resource_count = match *bindless_resource_type {
1112                BindlessResourceType::None => false,
1113                BindlessResourceType::Buffer => self
1114                    .buffers
1115                    .get_mut(&bindless_index)
1116                    .expect("Buffer should exist with that bindless index")
1117                    .remove(bindless_binding),
1118                BindlessResourceType::DataBuffer => {
1119                    self.data_buffers
1120                        .get_mut(&bindless_index)
1121                        .expect("Data buffer should exist with that bindless index")
1122                        .remove(bindless_binding);
1123                    false
1124                }
1125                BindlessResourceType::SamplerFiltering
1126                | BindlessResourceType::SamplerNonFiltering
1127                | BindlessResourceType::SamplerComparison => self
1128                    .samplers
1129                    .get_mut(bindless_resource_type)
1130                    .expect("Sampler array should exist")
1131                    .remove(bindless_binding),
1132                BindlessResourceType::Texture1d
1133                | BindlessResourceType::Texture2d
1134                | BindlessResourceType::Texture2dArray
1135                | BindlessResourceType::Texture3d
1136                | BindlessResourceType::TextureCube
1137                | BindlessResourceType::TextureCubeArray => self
1138                    .textures
1139                    .get_mut(bindless_resource_type)
1140                    .expect("Texture array should exist")
1141                    .remove(bindless_binding),
1142            };
1143
1144            // If the slot is now free, decrement the allocated resource
1145            // count.
1146            if decrement_allocated_resource_count {
1147                self.allocated_resource_count -= 1;
1148            }
1149        }
1150
1151        // Invalidate the cached bind group.
1152        self.bind_group = None;
1153
1154        // Release the slot ID.
1155        self.free_slots.push(slot);
1156        self.live_allocation_count -= 1;
1157    }
1158
1159    /// Recreates the bind group and bindless index table buffer if necessary.
1160    fn prepare(
1161        &mut self,
1162        render_device: &RenderDevice,
1163        label: Option<&'static str>,
1164        bind_group_layout: &BindGroupLayout,
1165        fallback_bindless_resources: &FallbackBindlessResources,
1166        fallback_buffers: &HashMap<BindlessIndex, Buffer>,
1167        fallback_image: &FallbackImage,
1168        bindless_descriptor: &BindlessDescriptor,
1169        slab_capacity: u32,
1170    ) {
1171        // Create the bindless index table buffers if needed.
1172        for bindless_index_table in &mut self.bindless_index_tables {
1173            bindless_index_table.buffer.prepare(render_device);
1174        }
1175
1176        // Create any data buffers we were managing if necessary.
1177        for data_buffer in self.data_buffers.values_mut() {
1178            data_buffer.buffer.prepare(render_device);
1179        }
1180
1181        // Create the bind group if needed.
1182        self.prepare_bind_group(
1183            render_device,
1184            label,
1185            bind_group_layout,
1186            fallback_bindless_resources,
1187            fallback_buffers,
1188            fallback_image,
1189            bindless_descriptor,
1190            slab_capacity,
1191        );
1192    }
1193
1194    /// Recreates the bind group if this slab has been changed since the last
1195    /// time we created it.
1196    fn prepare_bind_group(
1197        &mut self,
1198        render_device: &RenderDevice,
1199        label: Option<&'static str>,
1200        bind_group_layout: &BindGroupLayout,
1201        fallback_bindless_resources: &FallbackBindlessResources,
1202        fallback_buffers: &HashMap<BindlessIndex, Buffer>,
1203        fallback_image: &FallbackImage,
1204        bindless_descriptor: &BindlessDescriptor,
1205        slab_capacity: u32,
1206    ) {
1207        // If the bind group is clean, then do nothing.
1208        if self.bind_group.is_some() {
1209            return;
1210        }
1211
1212        // Determine whether we need to pad out our binding arrays with dummy
1213        // resources.
1214        let required_binding_array_size = if render_device
1215            .features()
1216            .contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)
1217        {
1218            None
1219        } else {
1220            Some(slab_capacity)
1221        };
1222
1223        let binding_resource_arrays = self.create_binding_resource_arrays(
1224            fallback_bindless_resources,
1225            fallback_buffers,
1226            fallback_image,
1227            bindless_descriptor,
1228            required_binding_array_size,
1229        );
1230
1231        let mut bind_group_entries: Vec<_> = self
1232            .bindless_index_tables
1233            .iter()
1234            .map(|bindless_index_table| bindless_index_table.bind_group_entry())
1235            .collect();
1236
1237        for &(&binding, ref binding_resource_array) in binding_resource_arrays.iter() {
1238            bind_group_entries.push(BindGroupEntry {
1239                binding,
1240                resource: match *binding_resource_array {
1241                    BindingResourceArray::Buffers(ref buffer_bindings) => {
1242                        BindingResource::BufferArray(&buffer_bindings[..])
1243                    }
1244                    BindingResourceArray::TextureViews(ref texture_views) => {
1245                        BindingResource::TextureViewArray(&texture_views[..])
1246                    }
1247                    BindingResourceArray::Samplers(ref samplers) => {
1248                        BindingResource::SamplerArray(&samplers[..])
1249                    }
1250                },
1251            });
1252        }
1253
1254        // Create bind group entries for any data buffers we're managing.
1255        for data_buffer in self.data_buffers.values() {
1256            bind_group_entries.push(BindGroupEntry {
1257                binding: *data_buffer.binding_number,
1258                resource: data_buffer
1259                    .buffer
1260                    .buffer()
1261                    .expect("Backing data buffer must have been uploaded by now")
1262                    .as_entire_binding(),
1263            });
1264        }
1265
1266        self.bind_group =
1267            Some(render_device.create_bind_group(label, bind_group_layout, &bind_group_entries));
1268    }
1269
1270    /// Writes any buffers that we're managing to the GPU.
1271    ///
1272    /// Currently, this consists of the bindless index table plus any data
1273    /// buffers we're managing.
1274    fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
1275        for bindless_index_table in &mut self.bindless_index_tables {
1276            bindless_index_table
1277                .buffer
1278                .write(render_device, render_queue);
1279        }
1280
1281        for data_buffer in self.data_buffers.values_mut() {
1282            data_buffer.buffer.write(render_device, render_queue);
1283        }
1284    }
1285
1286    /// Converts our binding arrays into binding resource arrays suitable for
1287    /// passing to `wgpu`.
1288    fn create_binding_resource_arrays<'a>(
1289        &'a self,
1290        fallback_bindless_resources: &'a FallbackBindlessResources,
1291        fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,
1292        fallback_image: &'a FallbackImage,
1293        bindless_descriptor: &'a BindlessDescriptor,
1294        required_binding_array_size: Option<u32>,
1295    ) -> Vec<(&'a u32, BindingResourceArray<'a>)> {
1296        let mut binding_resource_arrays = vec![];
1297
1298        // Build sampler bindings.
1299        self.create_sampler_binding_resource_arrays(
1300            &mut binding_resource_arrays,
1301            fallback_bindless_resources,
1302            required_binding_array_size,
1303        );
1304
1305        // Build texture bindings.
1306        self.create_texture_binding_resource_arrays(
1307            &mut binding_resource_arrays,
1308            fallback_image,
1309            required_binding_array_size,
1310        );
1311
1312        // Build buffer bindings.
1313        self.create_buffer_binding_resource_arrays(
1314            &mut binding_resource_arrays,
1315            fallback_buffers,
1316            bindless_descriptor,
1317            required_binding_array_size,
1318        );
1319
1320        binding_resource_arrays
1321    }
1322
1323    /// Accumulates sampler binding arrays into binding resource arrays suitable
1324    /// for passing to `wgpu`.
1325    fn create_sampler_binding_resource_arrays<'a, 'b>(
1326        &'a self,
1327        binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,
1328        fallback_bindless_resources: &'a FallbackBindlessResources,
1329        required_binding_array_size: Option<u32>,
1330    ) {
1331        // We have one binding resource array per sampler type.
1332        for (bindless_resource_type, fallback_sampler) in [
1333            (
1334                BindlessResourceType::SamplerFiltering,
1335                &fallback_bindless_resources.filtering_sampler,
1336            ),
1337            (
1338                BindlessResourceType::SamplerNonFiltering,
1339                &fallback_bindless_resources.non_filtering_sampler,
1340            ),
1341            (
1342                BindlessResourceType::SamplerComparison,
1343                &fallback_bindless_resources.comparison_sampler,
1344            ),
1345        ] {
1346            let mut sampler_bindings = vec![];
1347
1348            match self.samplers.get(&bindless_resource_type) {
1349                Some(sampler_bindless_binding_array) => {
1350                    for maybe_bindless_binding in sampler_bindless_binding_array.bindings.iter() {
1351                        match *maybe_bindless_binding {
1352                            Some(ref bindless_binding) => {
1353                                sampler_bindings.push(&*bindless_binding.resource);
1354                            }
1355                            None => sampler_bindings.push(&**fallback_sampler),
1356                        }
1357                    }
1358                }
1359
1360                None => {
1361                    // Fill with a single fallback sampler.
1362                    sampler_bindings.push(&**fallback_sampler);
1363                }
1364            }
1365
1366            if let Some(required_binding_array_size) = required_binding_array_size {
1367                sampler_bindings.extend(iter::repeat_n(
1368                    &**fallback_sampler,
1369                    required_binding_array_size as usize - sampler_bindings.len(),
1370                ));
1371            }
1372
1373            let binding_number = bindless_resource_type
1374                .binding_number()
1375                .expect("Sampler bindless resource type must have a binding number");
1376
1377            binding_resource_arrays.push((
1378                &**binding_number,
1379                BindingResourceArray::Samplers(sampler_bindings),
1380            ));
1381        }
1382    }
1383
1384    /// Accumulates texture binding arrays into binding resource arrays suitable
1385    /// for passing to `wgpu`.
1386    fn create_texture_binding_resource_arrays<'a, 'b>(
1387        &'a self,
1388        binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,
1389        fallback_image: &'a FallbackImage,
1390        required_binding_array_size: Option<u32>,
1391    ) {
1392        for (bindless_resource_type, fallback_image) in [
1393            (BindlessResourceType::Texture1d, &fallback_image.d1),
1394            (BindlessResourceType::Texture2d, &fallback_image.d2),
1395            (
1396                BindlessResourceType::Texture2dArray,
1397                &fallback_image.d2_array,
1398            ),
1399            (BindlessResourceType::Texture3d, &fallback_image.d3),
1400            (BindlessResourceType::TextureCube, &fallback_image.cube),
1401            (
1402                BindlessResourceType::TextureCubeArray,
1403                &fallback_image.cube_array,
1404            ),
1405        ] {
1406            let mut texture_bindings = vec![];
1407
1408            let binding_number = bindless_resource_type
1409                .binding_number()
1410                .expect("Texture bindless resource type must have a binding number");
1411
1412            match self.textures.get(&bindless_resource_type) {
1413                Some(texture_bindless_binding_array) => {
1414                    for maybe_bindless_binding in texture_bindless_binding_array.bindings.iter() {
1415                        match *maybe_bindless_binding {
1416                            Some(ref bindless_binding) => {
1417                                texture_bindings.push(&*bindless_binding.resource);
1418                            }
1419                            None => texture_bindings.push(&*fallback_image.texture_view),
1420                        }
1421                    }
1422                }
1423
1424                None => {
1425                    // Fill with a single fallback image.
1426                    texture_bindings.push(&*fallback_image.texture_view);
1427                }
1428            }
1429
1430            if let Some(required_binding_array_size) = required_binding_array_size {
1431                texture_bindings.extend(iter::repeat_n(
1432                    &*fallback_image.texture_view,
1433                    required_binding_array_size as usize - texture_bindings.len(),
1434                ));
1435            }
1436
1437            binding_resource_arrays.push((
1438                binding_number,
1439                BindingResourceArray::TextureViews(texture_bindings),
1440            ));
1441        }
1442    }
1443
1444    /// Accumulates buffer binding arrays into binding resource arrays suitable
1445    /// for `wgpu`.
1446    fn create_buffer_binding_resource_arrays<'a, 'b>(
1447        &'a self,
1448        binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,
1449        fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,
1450        bindless_descriptor: &'a BindlessDescriptor,
1451        required_binding_array_size: Option<u32>,
1452    ) {
1453        for bindless_buffer_descriptor in bindless_descriptor.buffers.iter() {
1454            let Some(buffer_bindless_binding_array) =
1455                self.buffers.get(&bindless_buffer_descriptor.bindless_index)
1456            else {
1457                // This is OK, because index buffers are present in
1458                // `BindlessDescriptor::buffers` but not in
1459                // `BindlessDescriptor::resources`.
1460                continue;
1461            };
1462
1463            let fallback_buffer = fallback_buffers
1464                .get(&bindless_buffer_descriptor.bindless_index)
1465                .expect("Fallback buffer should exist");
1466
1467            let mut buffer_bindings: Vec<_> = buffer_bindless_binding_array
1468                .bindings
1469                .iter()
1470                .map(|maybe_bindless_binding| {
1471                    let buffer = match *maybe_bindless_binding {
1472                        None => fallback_buffer,
1473                        Some(ref bindless_binding) => &bindless_binding.resource,
1474                    };
1475                    BufferBinding {
1476                        buffer,
1477                        offset: 0,
1478                        size: None,
1479                    }
1480                })
1481                .collect();
1482
1483            if let Some(required_binding_array_size) = required_binding_array_size {
1484                buffer_bindings.extend(iter::repeat_n(
1485                    BufferBinding {
1486                        buffer: fallback_buffer,
1487                        offset: 0,
1488                        size: None,
1489                    },
1490                    required_binding_array_size as usize - buffer_bindings.len(),
1491                ));
1492            }
1493
1494            binding_resource_arrays.push((
1495                &*buffer_bindless_binding_array.binding_number,
1496                BindingResourceArray::Buffers(buffer_bindings),
1497            ));
1498        }
1499    }
1500
1501    /// Returns the [`BindGroup`] corresponding to this slab, if it's been
1502    /// prepared.
1503    fn bind_group(&self) -> Option<&BindGroup> {
1504        self.bind_group.as_ref()
1505    }
1506
1507    /// Returns the bindless index table containing the given bindless index.
1508    fn get_bindless_index_table(
1509        &self,
1510        bindless_index: BindlessIndex,
1511    ) -> Option<&MaterialBindlessIndexTable> {
1512        let table_index = self
1513            .bindless_index_tables
1514            .binary_search_by(|bindless_index_table| {
1515                if bindless_index < bindless_index_table.index_range.start {
1516                    Ordering::Less
1517                } else if bindless_index >= bindless_index_table.index_range.end {
1518                    Ordering::Greater
1519                } else {
1520                    Ordering::Equal
1521                }
1522            })
1523            .ok()?;
1524        self.bindless_index_tables.get(table_index)
1525    }
1526}
1527
1528impl<R> MaterialBindlessBindingArray<R>
1529where
1530    R: GetBindingResourceId,
1531{
1532    /// Creates a new [`MaterialBindlessBindingArray`] with the given binding
1533    /// number, managing resources of the given type.
1534    fn new(
1535        binding_number: BindingNumber,
1536        resource_type: BindlessResourceType,
1537    ) -> MaterialBindlessBindingArray<R> {
1538        MaterialBindlessBindingArray {
1539            binding_number,
1540            bindings: vec![],
1541            resource_type,
1542            resource_to_slot: HashMap::default(),
1543            free_slots: vec![],
1544            len: 0,
1545        }
1546    }
1547
1548    /// Returns the slot corresponding to the given resource, if that resource
1549    /// is located in this binding array.
1550    ///
1551    /// If the resource isn't in this binding array, this method returns `None`.
1552    fn find(&self, binding_resource_id: BindingResourceId) -> Option<u32> {
1553        self.resource_to_slot.get(&binding_resource_id).copied()
1554    }
1555
1556    /// Inserts a bindless resource into a binding array and returns the index
1557    /// of the slot it was inserted into.
1558    fn insert(&mut self, binding_resource_id: BindingResourceId, resource: R) -> u32 {
1559        match self.resource_to_slot.entry(binding_resource_id) {
1560            bevy_platform::collections::hash_map::Entry::Occupied(o) => {
1561                let slot = *o.get();
1562
1563                self.bindings[slot as usize]
1564                    .as_mut()
1565                    .expect("A slot in the resource_to_slot map should have a value")
1566                    .ref_count += 1;
1567
1568                slot
1569            }
1570            bevy_platform::collections::hash_map::Entry::Vacant(v) => {
1571                let slot = self.free_slots.pop().unwrap_or(self.len);
1572                v.insert(slot);
1573
1574                if self.bindings.len() < slot as usize + 1 {
1575                    self.bindings.resize_with(slot as usize + 1, || None);
1576                }
1577                self.bindings[slot as usize] = Some(MaterialBindlessBinding::new(resource));
1578
1579                self.len += 1;
1580                slot
1581            }
1582        }
1583    }
1584
1585    /// Removes a reference to an object from the slot.
1586    ///
1587    /// If the reference count dropped to 0 and the object was freed, this
1588    /// method returns true. If the object was still referenced after removing
1589    /// it, returns false.
1590    fn remove(&mut self, slot: u32) -> bool {
1591        let maybe_binding = &mut self.bindings[slot as usize];
1592        let binding = maybe_binding
1593            .as_mut()
1594            .expect("Attempted to free an already-freed binding");
1595
1596        binding.ref_count -= 1;
1597        if binding.ref_count != 0 {
1598            return false;
1599        }
1600
1601        let binding_resource_id = binding.resource.binding_resource_id(self.resource_type);
1602        self.resource_to_slot.remove(&binding_resource_id);
1603
1604        *maybe_binding = None;
1605        self.free_slots.push(slot);
1606        self.len -= 1;
1607        true
1608    }
1609}
1610
1611impl<R> MaterialBindlessBinding<R>
1612where
1613    R: GetBindingResourceId,
1614{
1615    /// Creates a new [`MaterialBindlessBinding`] for a freshly-added resource.
1616    ///
1617    /// The reference count is initialized to 1.
1618    fn new(resource: R) -> MaterialBindlessBinding<R> {
1619        MaterialBindlessBinding {
1620            resource,
1621            ref_count: 1,
1622        }
1623    }
1624}
1625
1626/// Returns true if the material will *actually* use bindless resources or false
1627/// if it won't.
1628///
1629/// This takes the platform support (or lack thereof) for bindless resources
1630/// into account.
1631pub fn material_uses_bindless_resources<M>(render_device: &RenderDevice) -> bool
1632where
1633    M: Material,
1634{
1635    M::bindless_slot_count().is_some_and(|bindless_slot_count| {
1636        M::bindless_supported(render_device) && bindless_slot_count.resolve() > 1
1637    })
1638}
1639
1640impl MaterialBindlessSlab {
1641    /// Creates a new [`MaterialBindlessSlab`] for a material with the given
1642    /// bindless descriptor.
1643    ///
1644    /// We use this when no existing slab could hold a material to be allocated.
1645    fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab {
1646        let mut buffers = HashMap::default();
1647        let mut samplers = HashMap::default();
1648        let mut textures = HashMap::default();
1649        let mut data_buffers = HashMap::default();
1650
1651        for (bindless_index, bindless_resource_type) in
1652            bindless_descriptor.resources.iter().enumerate()
1653        {
1654            let bindless_index = BindlessIndex(bindless_index as u32);
1655            match *bindless_resource_type {
1656                BindlessResourceType::None => {}
1657                BindlessResourceType::Buffer => {
1658                    let binding_number = bindless_descriptor
1659                        .buffers
1660                        .iter()
1661                        .find(|bindless_buffer_descriptor| {
1662                            bindless_buffer_descriptor.bindless_index == bindless_index
1663                        })
1664                        .expect(
1665                            "Bindless buffer descriptor matching that bindless index should be \
1666                             present",
1667                        )
1668                        .binding_number;
1669                    buffers.insert(
1670                        bindless_index,
1671                        MaterialBindlessBindingArray::new(binding_number, *bindless_resource_type),
1672                    );
1673                }
1674                BindlessResourceType::DataBuffer => {
1675                    // Copy the data in.
1676                    let buffer_descriptor = bindless_descriptor
1677                        .buffers
1678                        .iter()
1679                        .find(|bindless_buffer_descriptor| {
1680                            bindless_buffer_descriptor.bindless_index == bindless_index
1681                        })
1682                        .expect(
1683                            "Bindless buffer descriptor matching that bindless index should be \
1684                             present",
1685                        );
1686                    data_buffers.insert(
1687                        bindless_index,
1688                        MaterialDataBuffer::new(
1689                            buffer_descriptor.binding_number,
1690                            buffer_descriptor
1691                                .size
1692                                .expect("Data buffers should have a size")
1693                                as u32,
1694                        ),
1695                    );
1696                }
1697                BindlessResourceType::SamplerFiltering
1698                | BindlessResourceType::SamplerNonFiltering
1699                | BindlessResourceType::SamplerComparison => {
1700                    samplers.insert(
1701                        *bindless_resource_type,
1702                        MaterialBindlessBindingArray::new(
1703                            *bindless_resource_type.binding_number().unwrap(),
1704                            *bindless_resource_type,
1705                        ),
1706                    );
1707                }
1708                BindlessResourceType::Texture1d
1709                | BindlessResourceType::Texture2d
1710                | BindlessResourceType::Texture2dArray
1711                | BindlessResourceType::Texture3d
1712                | BindlessResourceType::TextureCube
1713                | BindlessResourceType::TextureCubeArray => {
1714                    textures.insert(
1715                        *bindless_resource_type,
1716                        MaterialBindlessBindingArray::new(
1717                            *bindless_resource_type.binding_number().unwrap(),
1718                            *bindless_resource_type,
1719                        ),
1720                    );
1721                }
1722            }
1723        }
1724
1725        let bindless_index_tables = bindless_descriptor
1726            .index_tables
1727            .iter()
1728            .map(MaterialBindlessIndexTable::new)
1729            .collect();
1730
1731        MaterialBindlessSlab {
1732            bind_group: None,
1733            bindless_index_tables,
1734            samplers,
1735            textures,
1736            buffers,
1737            data_buffers,
1738            free_slots: vec![],
1739            live_allocation_count: 0,
1740            allocated_resource_count: 0,
1741        }
1742    }
1743}
1744
1745pub fn init_fallback_bindless_resources(mut commands: Commands, render_device: Res<RenderDevice>) {
1746    commands.insert_resource(FallbackBindlessResources {
1747        filtering_sampler: render_device.create_sampler(&SamplerDescriptor {
1748            label: Some("fallback filtering sampler"),
1749            ..default()
1750        }),
1751        non_filtering_sampler: render_device.create_sampler(&SamplerDescriptor {
1752            label: Some("fallback non-filtering sampler"),
1753            mag_filter: FilterMode::Nearest,
1754            min_filter: FilterMode::Nearest,
1755            mipmap_filter: FilterMode::Nearest,
1756            ..default()
1757        }),
1758        comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
1759            label: Some("fallback comparison sampler"),
1760            compare: Some(CompareFunction::Always),
1761            ..default()
1762        }),
1763    });
1764}
1765
1766impl MaterialBindGroupNonBindlessAllocator {
1767    /// Creates a new [`MaterialBindGroupNonBindlessAllocator`] managing the
1768    /// bind groups for a single non-bindless material.
1769    fn new(label: Option<&'static str>) -> MaterialBindGroupNonBindlessAllocator {
1770        MaterialBindGroupNonBindlessAllocator {
1771            label,
1772            bind_groups: vec![],
1773            to_prepare: HashSet::default(),
1774            free_indices: vec![],
1775        }
1776    }
1777
1778    /// Inserts a bind group, either unprepared or prepared, into this allocator
1779    /// and returns a [`MaterialBindingId`].
1780    ///
1781    /// The returned [`MaterialBindingId`] can later be used to fetch the bind
1782    /// group.
1783    fn allocate(&mut self, bind_group: MaterialNonBindlessAllocatedBindGroup) -> MaterialBindingId {
1784        let group_id = self
1785            .free_indices
1786            .pop()
1787            .unwrap_or(MaterialBindGroupIndex(self.bind_groups.len() as u32));
1788        if self.bind_groups.len() < *group_id as usize + 1 {
1789            self.bind_groups
1790                .resize_with(*group_id as usize + 1, || None);
1791        }
1792
1793        if matches!(
1794            bind_group,
1795            MaterialNonBindlessAllocatedBindGroup::Unprepared { .. }
1796        ) {
1797            self.to_prepare.insert(group_id);
1798        }
1799
1800        self.bind_groups[*group_id as usize] = Some(bind_group);
1801
1802        MaterialBindingId {
1803            group: group_id,
1804            slot: default(),
1805        }
1806    }
1807
1808    /// Inserts an unprepared bind group into this allocator and returns a
1809    /// [`MaterialBindingId`].
1810    fn allocate_unprepared(
1811        &mut self,
1812        unprepared_bind_group: UnpreparedBindGroup,
1813        bind_group_layout: BindGroupLayout,
1814    ) -> MaterialBindingId {
1815        self.allocate(MaterialNonBindlessAllocatedBindGroup::Unprepared {
1816            bind_group: unprepared_bind_group,
1817            layout: bind_group_layout,
1818        })
1819    }
1820
1821    /// Inserts an prepared bind group into this allocator and returns a
1822    /// [`MaterialBindingId`].
1823    fn allocate_prepared(&mut self, prepared_bind_group: PreparedBindGroup) -> MaterialBindingId {
1824        self.allocate(MaterialNonBindlessAllocatedBindGroup::Prepared {
1825            bind_group: prepared_bind_group,
1826            uniform_buffers: vec![],
1827        })
1828    }
1829
1830    /// Deallocates the bind group with the given binding ID.
1831    fn free(&mut self, binding_id: MaterialBindingId) {
1832        debug_assert_eq!(binding_id.slot, MaterialBindGroupSlot(0));
1833        debug_assert!(self.bind_groups[*binding_id.group as usize].is_some());
1834        self.bind_groups[*binding_id.group as usize] = None;
1835        self.to_prepare.remove(&binding_id.group);
1836        self.free_indices.push(binding_id.group);
1837    }
1838
1839    /// Returns a wrapper around the bind group with the given index.
1840    fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialNonBindlessSlab<'_>> {
1841        self.bind_groups[group.0 as usize]
1842            .as_ref()
1843            .map(|bind_group| match bind_group {
1844                MaterialNonBindlessAllocatedBindGroup::Prepared { bind_group, .. } => {
1845                    MaterialNonBindlessSlab::Prepared(bind_group)
1846                }
1847                MaterialNonBindlessAllocatedBindGroup::Unprepared { .. } => {
1848                    MaterialNonBindlessSlab::Unprepared
1849                }
1850            })
1851    }
1852
1853    /// Prepares any as-yet unprepared bind groups that this allocator is
1854    /// managing.
1855    ///
1856    /// Unprepared bind groups can be added to this allocator with
1857    /// [`Self::allocate_unprepared`]. Such bind groups will defer being
1858    /// prepared until the next time this method is called.
1859    fn prepare_bind_groups(&mut self, render_device: &RenderDevice) {
1860        for bind_group_index in mem::take(&mut self.to_prepare) {
1861            let Some(MaterialNonBindlessAllocatedBindGroup::Unprepared {
1862                bind_group: unprepared_bind_group,
1863                layout: bind_group_layout,
1864            }) = mem::take(&mut self.bind_groups[*bind_group_index as usize])
1865            else {
1866                panic!("Allocation didn't exist or was already prepared");
1867            };
1868
1869            // Pack any `Data` into uniform buffers.
1870            let mut uniform_buffers = vec![];
1871            for (index, binding) in unprepared_bind_group.bindings.iter() {
1872                let OwnedBindingResource::Data(ref owned_data) = *binding else {
1873                    continue;
1874                };
1875                let label = format!("material uniform data {}", *index);
1876                let uniform_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
1877                    label: Some(&label),
1878                    contents: &owned_data.0,
1879                    usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
1880                });
1881                uniform_buffers.push(uniform_buffer);
1882            }
1883
1884            // Create bind group entries.
1885            let mut bind_group_entries = vec![];
1886            let mut uniform_buffers_iter = uniform_buffers.iter();
1887            for (index, binding) in unprepared_bind_group.bindings.iter() {
1888                match *binding {
1889                    OwnedBindingResource::Data(_) => {
1890                        bind_group_entries.push(BindGroupEntry {
1891                            binding: *index,
1892                            resource: uniform_buffers_iter
1893                                .next()
1894                                .expect("We should have created uniform buffers for each `Data`")
1895                                .as_entire_binding(),
1896                        });
1897                    }
1898                    _ => bind_group_entries.push(BindGroupEntry {
1899                        binding: *index,
1900                        resource: binding.get_binding(),
1901                    }),
1902                }
1903            }
1904
1905            // Create the bind group.
1906            let bind_group = render_device.create_bind_group(
1907                self.label,
1908                &bind_group_layout,
1909                &bind_group_entries,
1910            );
1911
1912            self.bind_groups[*bind_group_index as usize] =
1913                Some(MaterialNonBindlessAllocatedBindGroup::Prepared {
1914                    bind_group: PreparedBindGroup {
1915                        bindings: unprepared_bind_group.bindings,
1916                        bind_group,
1917                    },
1918                    uniform_buffers,
1919                });
1920        }
1921    }
1922}
1923
1924impl<'a> MaterialSlab<'a> {
1925    /// Returns the [`BindGroup`] corresponding to this slab, if it's been
1926    /// prepared.
1927    ///
1928    /// You can prepare bind groups by calling
1929    /// [`MaterialBindGroupAllocator::prepare_bind_groups`]. If the bind group
1930    /// isn't ready, this method returns `None`.
1931    pub fn bind_group(&self) -> Option<&'a BindGroup> {
1932        match self.0 {
1933            MaterialSlabImpl::Bindless(material_bindless_slab) => {
1934                material_bindless_slab.bind_group()
1935            }
1936            MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared(
1937                prepared_bind_group,
1938            )) => Some(&prepared_bind_group.bind_group),
1939            MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared) => None,
1940        }
1941    }
1942}
1943
1944impl MaterialDataBuffer {
1945    /// Creates a new [`MaterialDataBuffer`] managing a buffer of elements of
1946    /// size `aligned_element_size` that will be bound to the given binding
1947    /// number.
1948    fn new(binding_number: BindingNumber, aligned_element_size: u32) -> MaterialDataBuffer {
1949        MaterialDataBuffer {
1950            binding_number,
1951            buffer: RetainedRawBufferVec::new(BufferUsages::STORAGE),
1952            aligned_element_size,
1953            free_slots: vec![],
1954            len: 0,
1955        }
1956    }
1957
1958    /// Allocates a slot for a new piece of data, copies the data into that
1959    /// slot, and returns the slot ID.
1960    ///
1961    /// The size of the piece of data supplied to this method must equal the
1962    /// [`Self::aligned_element_size`] provided to [`MaterialDataBuffer::new`].
1963    fn insert(&mut self, data: &[u8]) -> u32 {
1964        // Make sure the data is of the right length.
1965        debug_assert_eq!(data.len(), self.aligned_element_size as usize);
1966
1967        // Grab a slot.
1968        let slot = self.free_slots.pop().unwrap_or(self.len);
1969
1970        // Calculate the range we're going to copy to.
1971        let start = slot as usize * self.aligned_element_size as usize;
1972        let end = (slot as usize + 1) * self.aligned_element_size as usize;
1973
1974        // Resize the buffer if necessary.
1975        if self.buffer.len() < end {
1976            self.buffer.reserve_internal(end);
1977        }
1978        while self.buffer.values().len() < end {
1979            self.buffer.push(0);
1980        }
1981
1982        // Copy in the data.
1983        self.buffer.values_mut()[start..end].copy_from_slice(data);
1984
1985        // Mark the buffer dirty, and finish up.
1986        self.len += 1;
1987        self.buffer.dirty = BufferDirtyState::NeedsReserve;
1988        slot
1989    }
1990
1991    /// Marks the given slot as free.
1992    fn remove(&mut self, slot: u32) {
1993        self.free_slots.push(slot);
1994        self.len -= 1;
1995    }
1996}