Path: blob/main/crates/bevy_pbr/src/material_bind_groups.rs
6598 views
//! Material bind group management for bindless resources.1//!2//! In bindless mode, Bevy's renderer groups materials into bind groups. This3//! allocator manages each bind group, assigning slots to materials as4//! appropriate.56use crate::Material;7use bevy_derive::{Deref, DerefMut};8use bevy_ecs::{9resource::Resource,10system::{Commands, Res},11};12use bevy_platform::collections::{HashMap, HashSet};13use bevy_reflect::{prelude::ReflectDefault, Reflect};14use bevy_render::render_resource::BindlessSlabResourceLimit;15use bevy_render::{16render_resource::{17BindGroup, BindGroupEntry, BindGroupLayout, BindingNumber, BindingResource,18BindingResources, BindlessDescriptor, BindlessIndex, BindlessIndexTableDescriptor,19BindlessResourceType, Buffer, BufferBinding, BufferDescriptor, BufferId,20BufferInitDescriptor, BufferUsages, CompareFunction, FilterMode, OwnedBindingResource,21PreparedBindGroup, RawBufferVec, Sampler, SamplerDescriptor, SamplerId, TextureView,22TextureViewDimension, TextureViewId, UnpreparedBindGroup, WgpuSampler, WgpuTextureView,23},24renderer::{RenderDevice, RenderQueue},25settings::WgpuFeatures,26texture::FallbackImage,27};28use bevy_utils::{default, TypeIdMap};29use bytemuck::Pod;30use core::hash::Hash;31use core::{cmp::Ordering, iter, mem, ops::Range};32use tracing::{error, trace};3334#[derive(Resource, Deref, DerefMut, Default)]35pub struct MaterialBindGroupAllocators(TypeIdMap<MaterialBindGroupAllocator>);3637/// A resource that places materials into bind groups and tracks their38/// resources.39///40/// Internally, Bevy has separate allocators for bindless and non-bindless41/// materials. This resource provides a common interface to the specific42/// allocator in use.43pub enum MaterialBindGroupAllocator {44/// The allocator used when the material is bindless.45Bindless(Box<MaterialBindGroupBindlessAllocator>),46/// The allocator used when the material is non-bindless.47NonBindless(Box<MaterialBindGroupNonBindlessAllocator>),48}4950/// The allocator that places bindless materials into bind groups and tracks51/// their resources.52pub struct MaterialBindGroupBindlessAllocator {53/// The label of the bind group allocator to use for allocated buffers.54label: Option<&'static str>,55/// The slabs, each of which contains a bind group.56slabs: Vec<MaterialBindlessSlab>,57/// The layout of the bind groups that we produce.58bind_group_layout: BindGroupLayout,59/// Information about the bindless resources in the material.60///61/// We use this information to create and maintain bind groups.62bindless_descriptor: BindlessDescriptor,6364/// Dummy buffers that we use to fill empty slots in buffer binding arrays.65///66/// There's one fallback buffer for each buffer in the bind group, each67/// appropriately sized. Each buffer contains one uninitialized element of68/// the applicable type.69fallback_buffers: HashMap<BindlessIndex, Buffer>,7071/// The maximum number of resources that can be stored in a slab.72///73/// This corresponds to `SLAB_CAPACITY` in the `#[bindless(SLAB_CAPACITY)]`74/// attribute, when deriving `AsBindGroup`.75slab_capacity: u32,76}7778/// A single bind group and the bookkeeping necessary to allocate into it.79pub struct MaterialBindlessSlab {80/// The current bind group, if it's up to date.81///82/// If this is `None`, then the bind group is dirty and needs to be83/// regenerated.84bind_group: Option<BindGroup>,8586/// The GPU-accessible buffers that hold the mapping from binding index to87/// bindless slot.88///89/// This is conventionally assigned to bind group binding 0, but it can be90/// changed using the `#[bindless(index_table(binding(B)))]` attribute on91/// `AsBindGroup`.92///93/// Because the slab binary searches this table, the entries within must be94/// sorted by bindless index.95bindless_index_tables: Vec<MaterialBindlessIndexTable>,9697/// The binding arrays containing samplers.98samplers: HashMap<BindlessResourceType, MaterialBindlessBindingArray<Sampler>>,99/// The binding arrays containing textures.100textures: HashMap<BindlessResourceType, MaterialBindlessBindingArray<TextureView>>,101/// The binding arrays containing buffers.102buffers: HashMap<BindlessIndex, MaterialBindlessBindingArray<Buffer>>,103/// The buffers that contain plain old data (i.e. the structure-level104/// `#[data]` attribute of `AsBindGroup`).105data_buffers: HashMap<BindlessIndex, MaterialDataBuffer>,106107/// A list of free slot IDs.108free_slots: Vec<MaterialBindGroupSlot>,109/// The total number of materials currently allocated in this slab.110live_allocation_count: u32,111/// The total number of resources currently allocated in the binding arrays.112allocated_resource_count: u32,113}114115/// A GPU-accessible buffer that holds the mapping from binding index to116/// bindless slot.117///118/// This is conventionally assigned to bind group binding 0, but it can be119/// changed by altering the [`Self::binding_number`], which corresponds to the120/// `#[bindless(index_table(binding(B)))]` attribute in `AsBindGroup`.121struct MaterialBindlessIndexTable {122/// The buffer containing the mappings.123buffer: RetainedRawBufferVec<u32>,124/// The range of bindless indices that this bindless index table covers.125///126/// If this range is M..N, then the field at index $i$ maps to bindless127/// index $i$ + M. The size of this table is N - M.128///129/// This corresponds to the `#[bindless(index_table(range(M..N)))]`130/// attribute in `AsBindGroup`.131index_range: Range<BindlessIndex>,132/// The binding number that this index table is assigned to in the shader.133binding_number: BindingNumber,134}135136/// A single binding array for storing bindless resources and the bookkeeping137/// necessary to allocate into it.138struct MaterialBindlessBindingArray<R>139where140R: GetBindingResourceId,141{142/// The number of the binding that we attach this binding array to.143binding_number: BindingNumber,144/// A mapping from bindless slot index to the resource stored in that slot,145/// if any.146bindings: Vec<Option<MaterialBindlessBinding<R>>>,147/// The type of resource stored in this binding array.148resource_type: BindlessResourceType,149/// Maps a resource ID to the slot in which it's stored.150///151/// This is essentially the inverse mapping of [`Self::bindings`].152resource_to_slot: HashMap<BindingResourceId, u32>,153/// A list of free slots in [`Self::bindings`] that contain no binding.154free_slots: Vec<u32>,155/// The number of allocated objects in this binding array.156len: u32,157}158159/// A single resource (sampler, texture, or buffer) in a binding array.160///161/// Resources hold a reference count, which specifies the number of materials162/// currently allocated within the slab that refer to this resource. When the163/// reference count drops to zero, the resource is freed.164struct MaterialBindlessBinding<R>165where166R: GetBindingResourceId,167{168/// The sampler, texture, or buffer.169resource: R,170/// The number of materials currently allocated within the containing slab171/// that use this resource.172ref_count: u32,173}174175/// The allocator that stores bind groups for non-bindless materials.176pub struct MaterialBindGroupNonBindlessAllocator {177/// The label of the bind group allocator to use for allocated buffers.178label: Option<&'static str>,179/// A mapping from [`MaterialBindGroupIndex`] to the bind group allocated in180/// each slot.181bind_groups: Vec<Option<MaterialNonBindlessAllocatedBindGroup>>,182/// The bind groups that are dirty and need to be prepared.183///184/// To prepare the bind groups, call185/// [`MaterialBindGroupAllocator::prepare_bind_groups`].186to_prepare: HashSet<MaterialBindGroupIndex>,187/// A list of free bind group indices.188free_indices: Vec<MaterialBindGroupIndex>,189}190191/// A single bind group that a [`MaterialBindGroupNonBindlessAllocator`] is192/// currently managing.193enum MaterialNonBindlessAllocatedBindGroup {194/// An unprepared bind group.195///196/// The allocator prepares all outstanding unprepared bind groups when197/// [`MaterialBindGroupNonBindlessAllocator::prepare_bind_groups`] is198/// called.199Unprepared {200/// The unprepared bind group, including extra data.201bind_group: UnpreparedBindGroup,202/// The layout of that bind group.203layout: BindGroupLayout,204},205/// A bind group that's already been prepared.206Prepared {207bind_group: PreparedBindGroup,208#[expect(dead_code, reason = "These buffers are only referenced by bind groups")]209uniform_buffers: Vec<Buffer>,210},211}212213/// Dummy instances of various resources that we fill unused slots in binding214/// arrays with.215#[derive(Resource)]216pub struct FallbackBindlessResources {217/// A dummy filtering sampler.218filtering_sampler: Sampler,219/// A dummy non-filtering sampler.220non_filtering_sampler: Sampler,221/// A dummy comparison sampler.222comparison_sampler: Sampler,223}224225/// The `wgpu` ID of a single bindless or non-bindless resource.226#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]227enum BindingResourceId {228/// A buffer.229Buffer(BufferId),230/// A texture view, with the given dimension.231TextureView(TextureViewDimension, TextureViewId),232/// A sampler.233Sampler(SamplerId),234/// A buffer containing plain old data.235///236/// This corresponds to the `#[data]` structure-level attribute on237/// `AsBindGroup`.238DataBuffer,239}240241/// A temporary list of references to `wgpu` bindless resources.242///243/// We need this because the `wgpu` bindless API takes a slice of references.244/// Thus we need to create intermediate vectors of bindless resources in order245/// to satisfy `wgpu`'s lifetime requirements.246enum BindingResourceArray<'a> {247/// A list of bindings.248Buffers(Vec<BufferBinding<'a>>),249/// A list of texture views.250TextureViews(Vec<&'a WgpuTextureView>),251/// A list of samplers.252Samplers(Vec<&'a WgpuSampler>),253}254255/// The location of a material (either bindless or non-bindless) within the256/// slabs.257#[derive(Clone, Copy, Debug, Default, Reflect)]258#[reflect(Clone, Default)]259pub struct MaterialBindingId {260/// The index of the bind group (slab) where the GPU data is located.261pub group: MaterialBindGroupIndex,262/// The slot within that bind group.263///264/// Non-bindless materials will always have a slot of 0.265pub slot: MaterialBindGroupSlot,266}267268/// The index of each material bind group.269///270/// In bindless mode, each bind group contains multiple materials. In271/// non-bindless mode, each bind group contains only one material.272#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect, Deref, DerefMut)]273#[reflect(Default, Clone, PartialEq, Hash)]274pub struct MaterialBindGroupIndex(pub u32);275276impl From<u32> for MaterialBindGroupIndex {277fn from(value: u32) -> Self {278MaterialBindGroupIndex(value)279}280}281282/// The index of the slot containing material data within each material bind283/// group.284///285/// In bindless mode, this slot is needed to locate the material data in each286/// bind group, since multiple materials are packed into a single slab. In287/// non-bindless mode, this slot is always 0.288#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect, Deref, DerefMut)]289#[reflect(Default, Clone, PartialEq)]290pub struct MaterialBindGroupSlot(pub u32);291292/// The CPU/GPU synchronization state of a buffer that we maintain.293///294/// Currently, the only buffer that we maintain is the295/// [`MaterialBindlessIndexTable`].296enum BufferDirtyState {297/// The buffer is currently synchronized between the CPU and GPU.298Clean,299/// The buffer hasn't been created yet.300NeedsReserve,301/// The buffer exists on both CPU and GPU, but the GPU data is out of date.302NeedsUpload,303}304305/// Information that describes a potential allocation of an306/// [`UnpreparedBindGroup`] into a slab.307struct BindlessAllocationCandidate {308/// A map that, for every resource in the [`UnpreparedBindGroup`] that309/// already existed in this slab, maps bindless index of that resource to310/// its slot in the appropriate binding array.311pre_existing_resources: HashMap<BindlessIndex, u32>,312/// Stores the number of free slots that are needed to satisfy this313/// allocation.314needed_free_slots: u32,315}316317/// A trait that allows fetching the [`BindingResourceId`] from a318/// [`BindlessResourceType`].319///320/// This is used when freeing bindless resources, in order to locate the IDs321/// assigned to each resource so that they can be removed from the appropriate322/// maps.323trait GetBindingResourceId {324/// Returns the [`BindingResourceId`] for this resource.325///326/// `resource_type` specifies this resource's type. This is used for327/// textures, as a `wgpu` [`TextureView`] doesn't store enough information328/// itself to determine its dimension.329fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId;330}331332/// The public interface to a slab, which represents a single bind group.333pub struct MaterialSlab<'a>(MaterialSlabImpl<'a>);334335/// The actual implementation of a material slab.336///337/// This has bindless and non-bindless variants.338enum MaterialSlabImpl<'a> {339/// The implementation of the slab interface we use when the slab340/// is bindless.341Bindless(&'a MaterialBindlessSlab),342/// The implementation of the slab interface we use when the slab343/// is non-bindless.344NonBindless(MaterialNonBindlessSlab<'a>),345}346347/// A single bind group that the [`MaterialBindGroupNonBindlessAllocator`]348/// manages.349enum MaterialNonBindlessSlab<'a> {350/// A slab that has a bind group.351Prepared(&'a PreparedBindGroup),352/// A slab that doesn't yet have a bind group.353Unprepared,354}355356/// Manages an array of untyped plain old data on GPU and allocates individual357/// slots within that array.358///359/// This supports the `#[data]` attribute of `AsBindGroup`.360struct MaterialDataBuffer {361/// The number of the binding that we attach this storage buffer to.362binding_number: BindingNumber,363/// The actual data.364///365/// Note that this is untyped (`u8`); the actual aligned size of each366/// element is given by [`Self::aligned_element_size`];367buffer: RetainedRawBufferVec<u8>,368/// The size of each element in the buffer, including padding and alignment369/// if any.370aligned_element_size: u32,371/// A list of free slots within the buffer.372free_slots: Vec<u32>,373/// The actual number of slots that have been allocated.374len: u32,375}376377/// A buffer containing plain old data, already packed into the appropriate GPU378/// format, and that can be updated incrementally.379///380/// This structure exists in order to encapsulate the lazy update381/// ([`BufferDirtyState`]) logic in a single place.382#[derive(Deref, DerefMut)]383struct RetainedRawBufferVec<T>384where385T: Pod,386{387/// The contents of the buffer.388#[deref]389buffer: RawBufferVec<T>,390/// Whether the contents of the buffer have been uploaded to the GPU.391dirty: BufferDirtyState,392}393394/// The size of the buffer that we assign to unused buffer slots, in bytes.395///396/// This is essentially arbitrary, as it doesn't seem to matter to `wgpu` what397/// the size is.398const DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE: u64 = 16;399400impl From<u32> for MaterialBindGroupSlot {401fn from(value: u32) -> Self {402MaterialBindGroupSlot(value)403}404}405406impl From<MaterialBindGroupSlot> for u32 {407fn from(value: MaterialBindGroupSlot) -> Self {408value.0409}410}411412impl<'a> From<&'a OwnedBindingResource> for BindingResourceId {413fn from(value: &'a OwnedBindingResource) -> Self {414match *value {415OwnedBindingResource::Buffer(ref buffer) => BindingResourceId::Buffer(buffer.id()),416OwnedBindingResource::Data(_) => BindingResourceId::DataBuffer,417OwnedBindingResource::TextureView(ref texture_view_dimension, ref texture_view) => {418BindingResourceId::TextureView(*texture_view_dimension, texture_view.id())419}420OwnedBindingResource::Sampler(_, ref sampler) => {421BindingResourceId::Sampler(sampler.id())422}423}424}425}426427impl GetBindingResourceId for Buffer {428fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {429BindingResourceId::Buffer(self.id())430}431}432433impl GetBindingResourceId for Sampler {434fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {435BindingResourceId::Sampler(self.id())436}437}438439impl GetBindingResourceId for TextureView {440fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId {441let texture_view_dimension = match resource_type {442BindlessResourceType::Texture1d => TextureViewDimension::D1,443BindlessResourceType::Texture2d => TextureViewDimension::D2,444BindlessResourceType::Texture2dArray => TextureViewDimension::D2Array,445BindlessResourceType::Texture3d => TextureViewDimension::D3,446BindlessResourceType::TextureCube => TextureViewDimension::Cube,447BindlessResourceType::TextureCubeArray => TextureViewDimension::CubeArray,448_ => panic!("Resource type is not a texture"),449};450BindingResourceId::TextureView(texture_view_dimension, self.id())451}452}453454impl MaterialBindGroupAllocator {455/// Creates a new [`MaterialBindGroupAllocator`] managing the data for a456/// single material.457pub fn new(458render_device: &RenderDevice,459label: Option<&'static str>,460bindless_descriptor: Option<BindlessDescriptor>,461bind_group_layout: BindGroupLayout,462slab_capacity: Option<BindlessSlabResourceLimit>,463) -> MaterialBindGroupAllocator {464if let Some(bindless_descriptor) = bindless_descriptor {465MaterialBindGroupAllocator::Bindless(Box::new(MaterialBindGroupBindlessAllocator::new(466render_device,467label,468bindless_descriptor,469bind_group_layout,470slab_capacity,471)))472} else {473MaterialBindGroupAllocator::NonBindless(Box::new(474MaterialBindGroupNonBindlessAllocator::new(label),475))476}477}478479/// Returns the slab with the given index, if one exists.480pub fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialSlab<'_>> {481match *self {482MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator483.get(group)484.map(|bindless_slab| MaterialSlab(MaterialSlabImpl::Bindless(bindless_slab))),485MaterialBindGroupAllocator::NonBindless(ref non_bindless_allocator) => {486non_bindless_allocator.get(group).map(|non_bindless_slab| {487MaterialSlab(MaterialSlabImpl::NonBindless(non_bindless_slab))488})489}490}491}492493/// Allocates an [`UnpreparedBindGroup`] and returns the resulting binding ID.494///495/// This method should generally be preferred over496/// [`Self::allocate_prepared`], because this method supports both bindless497/// and non-bindless bind groups. Only use [`Self::allocate_prepared`] if498/// you need to prepare the bind group yourself.499pub fn allocate_unprepared(500&mut self,501unprepared_bind_group: UnpreparedBindGroup,502bind_group_layout: &BindGroupLayout,503) -> MaterialBindingId {504match *self {505MaterialBindGroupAllocator::Bindless(506ref mut material_bind_group_bindless_allocator,507) => material_bind_group_bindless_allocator.allocate_unprepared(unprepared_bind_group),508MaterialBindGroupAllocator::NonBindless(509ref mut material_bind_group_non_bindless_allocator,510) => material_bind_group_non_bindless_allocator511.allocate_unprepared(unprepared_bind_group, (*bind_group_layout).clone()),512}513}514515/// Places a pre-prepared bind group into a slab.516///517/// For bindless materials, the allocator internally manages the bind518/// groups, so calling this method will panic if this is a bindless519/// allocator. Only non-bindless allocators support this method.520///521/// It's generally preferred to use [`Self::allocate_unprepared`], because522/// that method supports both bindless and non-bindless allocators. Only use523/// this method if you need to prepare the bind group yourself.524pub fn allocate_prepared(525&mut self,526prepared_bind_group: PreparedBindGroup,527) -> MaterialBindingId {528match *self {529MaterialBindGroupAllocator::Bindless(_) => {530panic!(531"Bindless resources are incompatible with implementing `as_bind_group` \532directly; implement `unprepared_bind_group` instead or disable bindless"533)534}535MaterialBindGroupAllocator::NonBindless(ref mut non_bindless_allocator) => {536non_bindless_allocator.allocate_prepared(prepared_bind_group)537}538}539}540541/// Deallocates the material with the given binding ID.542///543/// Any resources that are no longer referenced are removed from the slab.544pub fn free(&mut self, material_binding_id: MaterialBindingId) {545match *self {546MaterialBindGroupAllocator::Bindless(547ref mut material_bind_group_bindless_allocator,548) => material_bind_group_bindless_allocator.free(material_binding_id),549MaterialBindGroupAllocator::NonBindless(550ref mut material_bind_group_non_bindless_allocator,551) => material_bind_group_non_bindless_allocator.free(material_binding_id),552}553}554555/// Recreates any bind groups corresponding to slabs that have been modified556/// since last calling [`MaterialBindGroupAllocator::prepare_bind_groups`].557pub fn prepare_bind_groups(558&mut self,559render_device: &RenderDevice,560fallback_bindless_resources: &FallbackBindlessResources,561fallback_image: &FallbackImage,562) {563match *self {564MaterialBindGroupAllocator::Bindless(565ref mut material_bind_group_bindless_allocator,566) => material_bind_group_bindless_allocator.prepare_bind_groups(567render_device,568fallback_bindless_resources,569fallback_image,570),571MaterialBindGroupAllocator::NonBindless(572ref mut material_bind_group_non_bindless_allocator,573) => material_bind_group_non_bindless_allocator.prepare_bind_groups(render_device),574}575}576577/// Uploads the contents of all buffers that this578/// [`MaterialBindGroupAllocator`] manages to the GPU.579///580/// Non-bindless allocators don't currently manage any buffers, so this581/// method only has an effect for bindless allocators.582pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {583match *self {584MaterialBindGroupAllocator::Bindless(585ref mut material_bind_group_bindless_allocator,586) => material_bind_group_bindless_allocator.write_buffers(render_device, render_queue),587MaterialBindGroupAllocator::NonBindless(_) => {588// Not applicable.589}590}591}592}593594impl MaterialBindlessIndexTable {595/// Creates a new [`MaterialBindlessIndexTable`] for a single slab.596fn new(597bindless_index_table_descriptor: &BindlessIndexTableDescriptor,598) -> MaterialBindlessIndexTable {599// Preallocate space for one bindings table, so that there will always be a buffer.600let mut buffer = RetainedRawBufferVec::new(BufferUsages::STORAGE);601for _ in *bindless_index_table_descriptor.indices.start602..*bindless_index_table_descriptor.indices.end603{604buffer.push(0);605}606607MaterialBindlessIndexTable {608buffer,609index_range: bindless_index_table_descriptor.indices.clone(),610binding_number: bindless_index_table_descriptor.binding_number,611}612}613614/// Returns the bindings in the binding index table.615///616/// If the current [`MaterialBindlessIndexTable::index_range`] is M..N, then617/// element *i* of the returned binding index table contains the slot of the618/// bindless resource with bindless index *i* + M.619fn get(&self, slot: MaterialBindGroupSlot) -> &[u32] {620let struct_size = *self.index_range.end as usize - *self.index_range.start as usize;621let start = struct_size * slot.0 as usize;622&self.buffer.values()[start..(start + struct_size)]623}624625/// Returns a single binding from the binding index table.626fn get_binding(627&self,628slot: MaterialBindGroupSlot,629bindless_index: BindlessIndex,630) -> Option<u32> {631if bindless_index < self.index_range.start || bindless_index >= self.index_range.end {632return None;633}634self.get(slot)635.get((*bindless_index - *self.index_range.start) as usize)636.copied()637}638639fn table_length(&self) -> u32 {640self.index_range.end.0 - self.index_range.start.0641}642643/// Updates the binding index table for a single material.644///645/// The `allocated_resource_slots` map contains a mapping from the646/// [`BindlessIndex`] of each resource that the material references to the647/// slot that that resource occupies in the appropriate binding array. This648/// method serializes that map into a binding index table that the shader649/// can read.650fn set(651&mut self,652slot: MaterialBindGroupSlot,653allocated_resource_slots: &HashMap<BindlessIndex, u32>,654) {655let table_len = self.table_length() as usize;656let range = (slot.0 as usize * table_len)..((slot.0 as usize + 1) * table_len);657while self.buffer.len() < range.end {658self.buffer.push(0);659}660661for (&bindless_index, &resource_slot) in allocated_resource_slots {662if self.index_range.contains(&bindless_index) {663self.buffer.set(664*bindless_index + range.start as u32 - *self.index_range.start,665resource_slot,666);667}668}669670// Mark the buffer as needing to be recreated, in case we grew it.671self.buffer.dirty = BufferDirtyState::NeedsReserve;672}673674/// Returns the [`BindGroupEntry`] for the index table itself.675fn bind_group_entry(&self) -> BindGroupEntry<'_> {676BindGroupEntry {677binding: *self.binding_number,678resource: self679.buffer680.buffer()681.expect("Bindings buffer must exist")682.as_entire_binding(),683}684}685}686687impl<T> RetainedRawBufferVec<T>688where689T: Pod,690{691/// Creates a new empty [`RetainedRawBufferVec`] supporting the given692/// [`BufferUsages`].693fn new(buffer_usages: BufferUsages) -> RetainedRawBufferVec<T> {694RetainedRawBufferVec {695buffer: RawBufferVec::new(buffer_usages),696dirty: BufferDirtyState::NeedsUpload,697}698}699700/// Recreates the GPU backing buffer if needed.701fn prepare(&mut self, render_device: &RenderDevice) {702match self.dirty {703BufferDirtyState::Clean | BufferDirtyState::NeedsUpload => {}704BufferDirtyState::NeedsReserve => {705let capacity = self.buffer.len();706self.buffer.reserve(capacity, render_device);707self.dirty = BufferDirtyState::NeedsUpload;708}709}710}711712/// Writes the current contents of the buffer to the GPU if necessary.713fn write(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {714match self.dirty {715BufferDirtyState::Clean => {}716BufferDirtyState::NeedsReserve | BufferDirtyState::NeedsUpload => {717self.buffer.write_buffer(render_device, render_queue);718self.dirty = BufferDirtyState::Clean;719}720}721}722}723724impl MaterialBindGroupBindlessAllocator {725/// Creates a new [`MaterialBindGroupBindlessAllocator`] managing the data726/// for a single bindless material.727fn new(728render_device: &RenderDevice,729label: Option<&'static str>,730bindless_descriptor: BindlessDescriptor,731bind_group_layout: BindGroupLayout,732slab_capacity: Option<BindlessSlabResourceLimit>,733) -> MaterialBindGroupBindlessAllocator {734let fallback_buffers = bindless_descriptor735.buffers736.iter()737.map(|bindless_buffer_descriptor| {738(739bindless_buffer_descriptor.bindless_index,740render_device.create_buffer(&BufferDescriptor {741label: Some("bindless fallback buffer"),742size: match bindless_buffer_descriptor.size {743Some(size) => size as u64,744None => DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE,745},746usage: BufferUsages::STORAGE,747mapped_at_creation: false,748}),749)750})751.collect();752753MaterialBindGroupBindlessAllocator {754label,755slabs: vec![],756bind_group_layout,757bindless_descriptor,758fallback_buffers,759slab_capacity: slab_capacity760.expect("Non-bindless materials should use the non-bindless allocator")761.resolve(),762}763}764765/// Allocates the resources for a single material into a slab and returns766/// the resulting ID.767///768/// The returned [`MaterialBindingId`] can later be used to fetch the slab769/// that was used.770///771/// This function can't fail. If all slabs are full, then a new slab is772/// created, and the material is allocated into it.773fn allocate_unprepared(774&mut self,775mut unprepared_bind_group: UnpreparedBindGroup,776) -> MaterialBindingId {777for (slab_index, slab) in self.slabs.iter_mut().enumerate() {778trace!("Trying to allocate in slab {}", slab_index);779match slab.try_allocate(unprepared_bind_group, self.slab_capacity) {780Ok(slot) => {781return MaterialBindingId {782group: MaterialBindGroupIndex(slab_index as u32),783slot,784};785}786Err(bind_group) => unprepared_bind_group = bind_group,787}788}789790let group = MaterialBindGroupIndex(self.slabs.len() as u32);791self.slabs792.push(MaterialBindlessSlab::new(&self.bindless_descriptor));793794// Allocate into the newly-pushed slab.795let Ok(slot) = self796.slabs797.last_mut()798.expect("We just pushed a slab")799.try_allocate(unprepared_bind_group, self.slab_capacity)800else {801panic!("An allocation into an empty slab should always succeed")802};803804MaterialBindingId { group, slot }805}806807/// Deallocates the material with the given binding ID.808///809/// Any resources that are no longer referenced are removed from the slab.810fn free(&mut self, material_binding_id: MaterialBindingId) {811self.slabs812.get_mut(material_binding_id.group.0 as usize)813.expect("Slab should exist")814.free(material_binding_id.slot, &self.bindless_descriptor);815}816817/// Returns the slab with the given bind group index.818///819/// A [`MaterialBindGroupIndex`] can be fetched from a820/// [`MaterialBindingId`].821fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab> {822self.slabs.get(group.0 as usize)823}824825/// Recreates any bind groups corresponding to slabs that have been modified826/// since last calling827/// [`MaterialBindGroupBindlessAllocator::prepare_bind_groups`].828fn prepare_bind_groups(829&mut self,830render_device: &RenderDevice,831fallback_bindless_resources: &FallbackBindlessResources,832fallback_image: &FallbackImage,833) {834for slab in &mut self.slabs {835slab.prepare(836render_device,837self.label,838&self.bind_group_layout,839fallback_bindless_resources,840&self.fallback_buffers,841fallback_image,842&self.bindless_descriptor,843self.slab_capacity,844);845}846}847848/// Writes any buffers that we're managing to the GPU.849///850/// Currently, this only consists of the bindless index tables.851fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {852for slab in &mut self.slabs {853slab.write_buffer(render_device, render_queue);854}855}856}857858impl MaterialBindlessSlab {859/// Attempts to allocate the given unprepared bind group in this slab.860///861/// If the allocation succeeds, this method returns the slot that the862/// allocation was placed in. If the allocation fails because the slab was863/// full, this method returns the unprepared bind group back to the caller864/// so that it can try to allocate again.865fn try_allocate(866&mut self,867unprepared_bind_group: UnpreparedBindGroup,868slot_capacity: u32,869) -> Result<MaterialBindGroupSlot, UnpreparedBindGroup> {870// Locate pre-existing resources, and determine how many free slots we need.871let Some(allocation_candidate) = self.check_allocation(&unprepared_bind_group) else {872return Err(unprepared_bind_group);873};874875// Check to see if we have enough free space.876//877// As a special case, note that if *nothing* is allocated in this slab,878// then we always allow a material to be placed in it, regardless of the879// number of bindings the material has. This is so that, if the880// platform's maximum bindless count is set too low to hold even a881// single material, we can still place each material into a separate882// slab instead of failing outright.883if self.allocated_resource_count > 0884&& self.allocated_resource_count + allocation_candidate.needed_free_slots885> slot_capacity886{887trace!("Slab is full, can't allocate");888return Err(unprepared_bind_group);889}890891// OK, we can allocate in this slab. Assign a slot ID.892let slot = self893.free_slots894.pop()895.unwrap_or(MaterialBindGroupSlot(self.live_allocation_count));896897// Bump the live allocation count.898self.live_allocation_count += 1;899900// Insert the resources into the binding arrays.901let allocated_resource_slots =902self.insert_resources(unprepared_bind_group.bindings, allocation_candidate);903904// Serialize the allocated resource slots.905for bindless_index_table in &mut self.bindless_index_tables {906bindless_index_table.set(slot, &allocated_resource_slots);907}908909// Invalidate the cached bind group.910self.bind_group = None;911912Ok(slot)913}914915/// Gathers the information needed to determine whether the given unprepared916/// bind group can be allocated in this slab.917fn check_allocation(918&self,919unprepared_bind_group: &UnpreparedBindGroup,920) -> Option<BindlessAllocationCandidate> {921let mut allocation_candidate = BindlessAllocationCandidate {922pre_existing_resources: HashMap::default(),923needed_free_slots: 0,924};925926for &(bindless_index, ref owned_binding_resource) in unprepared_bind_group.bindings.iter() {927let bindless_index = BindlessIndex(bindless_index);928match *owned_binding_resource {929OwnedBindingResource::Buffer(ref buffer) => {930let Some(binding_array) = self.buffers.get(&bindless_index) else {931error!(932"Binding array wasn't present for buffer at index {:?}",933bindless_index934);935return None;936};937match binding_array.find(BindingResourceId::Buffer(buffer.id())) {938Some(slot) => {939allocation_candidate940.pre_existing_resources941.insert(bindless_index, slot);942}943None => allocation_candidate.needed_free_slots += 1,944}945}946947OwnedBindingResource::Data(_) => {948// The size of a data buffer is unlimited.949}950951OwnedBindingResource::TextureView(texture_view_dimension, ref texture_view) => {952let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);953match self954.textures955.get(&bindless_resource_type)956.expect("Missing binding array for texture")957.find(BindingResourceId::TextureView(958texture_view_dimension,959texture_view.id(),960)) {961Some(slot) => {962allocation_candidate963.pre_existing_resources964.insert(bindless_index, slot);965}966None => {967allocation_candidate.needed_free_slots += 1;968}969}970}971972OwnedBindingResource::Sampler(sampler_binding_type, ref sampler) => {973let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);974match self975.samplers976.get(&bindless_resource_type)977.expect("Missing binding array for sampler")978.find(BindingResourceId::Sampler(sampler.id()))979{980Some(slot) => {981allocation_candidate982.pre_existing_resources983.insert(bindless_index, slot);984}985None => {986allocation_candidate.needed_free_slots += 1;987}988}989}990}991}992993Some(allocation_candidate)994}995996/// Inserts the given [`BindingResources`] into this slab.997///998/// Returns a table that maps the bindless index of each resource to its999/// slot in its binding array.1000fn insert_resources(1001&mut self,1002mut binding_resources: BindingResources,1003allocation_candidate: BindlessAllocationCandidate,1004) -> HashMap<BindlessIndex, u32> {1005let mut allocated_resource_slots = HashMap::default();10061007for (bindless_index, owned_binding_resource) in binding_resources.drain(..) {1008let bindless_index = BindlessIndex(bindless_index);10091010let pre_existing_slot = allocation_candidate1011.pre_existing_resources1012.get(&bindless_index);10131014// Otherwise, we need to insert it anew.1015let binding_resource_id = BindingResourceId::from(&owned_binding_resource);1016let increment_allocated_resource_count = match owned_binding_resource {1017OwnedBindingResource::Buffer(buffer) => {1018let slot = self1019.buffers1020.get_mut(&bindless_index)1021.expect("Buffer binding array should exist")1022.insert(binding_resource_id, buffer);1023allocated_resource_slots.insert(bindless_index, slot);10241025if let Some(pre_existing_slot) = pre_existing_slot {1026assert_eq!(*pre_existing_slot, slot);10271028false1029} else {1030true1031}1032}1033OwnedBindingResource::Data(data) => {1034if pre_existing_slot.is_some() {1035panic!("Data buffers can't be deduplicated")1036}10371038let slot = self1039.data_buffers1040.get_mut(&bindless_index)1041.expect("Data buffer binding array should exist")1042.insert(&data);1043allocated_resource_slots.insert(bindless_index, slot);1044false1045}1046OwnedBindingResource::TextureView(texture_view_dimension, texture_view) => {1047let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);1048let slot = self1049.textures1050.get_mut(&bindless_resource_type)1051.expect("Texture array should exist")1052.insert(binding_resource_id, texture_view);1053allocated_resource_slots.insert(bindless_index, slot);10541055if let Some(pre_existing_slot) = pre_existing_slot {1056assert_eq!(*pre_existing_slot, slot);10571058false1059} else {1060true1061}1062}1063OwnedBindingResource::Sampler(sampler_binding_type, sampler) => {1064let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);1065let slot = self1066.samplers1067.get_mut(&bindless_resource_type)1068.expect("Sampler should exist")1069.insert(binding_resource_id, sampler);1070allocated_resource_slots.insert(bindless_index, slot);10711072if let Some(pre_existing_slot) = pre_existing_slot {1073assert_eq!(*pre_existing_slot, slot);10741075false1076} else {1077true1078}1079}1080};10811082// Bump the allocated resource count.1083if increment_allocated_resource_count {1084self.allocated_resource_count += 1;1085}1086}10871088allocated_resource_slots1089}10901091/// Removes the material allocated in the given slot, with the given1092/// descriptor, from this slab.1093fn free(&mut self, slot: MaterialBindGroupSlot, bindless_descriptor: &BindlessDescriptor) {1094// Loop through each binding.1095for (bindless_index, bindless_resource_type) in1096bindless_descriptor.resources.iter().enumerate()1097{1098let bindless_index = BindlessIndex::from(bindless_index as u32);1099let Some(bindless_index_table) = self.get_bindless_index_table(bindless_index) else {1100continue;1101};1102let Some(bindless_binding) = bindless_index_table.get_binding(slot, bindless_index)1103else {1104continue;1105};11061107// Free the binding. If the resource in question was anything other1108// than a data buffer, then it has a reference count and1109// consequently we need to decrement it.1110let decrement_allocated_resource_count = match *bindless_resource_type {1111BindlessResourceType::None => false,1112BindlessResourceType::Buffer => self1113.buffers1114.get_mut(&bindless_index)1115.expect("Buffer should exist with that bindless index")1116.remove(bindless_binding),1117BindlessResourceType::DataBuffer => {1118self.data_buffers1119.get_mut(&bindless_index)1120.expect("Data buffer should exist with that bindless index")1121.remove(bindless_binding);1122false1123}1124BindlessResourceType::SamplerFiltering1125| BindlessResourceType::SamplerNonFiltering1126| BindlessResourceType::SamplerComparison => self1127.samplers1128.get_mut(bindless_resource_type)1129.expect("Sampler array should exist")1130.remove(bindless_binding),1131BindlessResourceType::Texture1d1132| BindlessResourceType::Texture2d1133| BindlessResourceType::Texture2dArray1134| BindlessResourceType::Texture3d1135| BindlessResourceType::TextureCube1136| BindlessResourceType::TextureCubeArray => self1137.textures1138.get_mut(bindless_resource_type)1139.expect("Texture array should exist")1140.remove(bindless_binding),1141};11421143// If the slot is now free, decrement the allocated resource1144// count.1145if decrement_allocated_resource_count {1146self.allocated_resource_count -= 1;1147}1148}11491150// Invalidate the cached bind group.1151self.bind_group = None;11521153// Release the slot ID.1154self.free_slots.push(slot);1155self.live_allocation_count -= 1;1156}11571158/// Recreates the bind group and bindless index table buffer if necessary.1159fn prepare(1160&mut self,1161render_device: &RenderDevice,1162label: Option<&'static str>,1163bind_group_layout: &BindGroupLayout,1164fallback_bindless_resources: &FallbackBindlessResources,1165fallback_buffers: &HashMap<BindlessIndex, Buffer>,1166fallback_image: &FallbackImage,1167bindless_descriptor: &BindlessDescriptor,1168slab_capacity: u32,1169) {1170// Create the bindless index table buffers if needed.1171for bindless_index_table in &mut self.bindless_index_tables {1172bindless_index_table.buffer.prepare(render_device);1173}11741175// Create any data buffers we were managing if necessary.1176for data_buffer in self.data_buffers.values_mut() {1177data_buffer.buffer.prepare(render_device);1178}11791180// Create the bind group if needed.1181self.prepare_bind_group(1182render_device,1183label,1184bind_group_layout,1185fallback_bindless_resources,1186fallback_buffers,1187fallback_image,1188bindless_descriptor,1189slab_capacity,1190);1191}11921193/// Recreates the bind group if this slab has been changed since the last1194/// time we created it.1195fn prepare_bind_group(1196&mut self,1197render_device: &RenderDevice,1198label: Option<&'static str>,1199bind_group_layout: &BindGroupLayout,1200fallback_bindless_resources: &FallbackBindlessResources,1201fallback_buffers: &HashMap<BindlessIndex, Buffer>,1202fallback_image: &FallbackImage,1203bindless_descriptor: &BindlessDescriptor,1204slab_capacity: u32,1205) {1206// If the bind group is clean, then do nothing.1207if self.bind_group.is_some() {1208return;1209}12101211// Determine whether we need to pad out our binding arrays with dummy1212// resources.1213let required_binding_array_size = if render_device1214.features()1215.contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)1216{1217None1218} else {1219Some(slab_capacity)1220};12211222let binding_resource_arrays = self.create_binding_resource_arrays(1223fallback_bindless_resources,1224fallback_buffers,1225fallback_image,1226bindless_descriptor,1227required_binding_array_size,1228);12291230let mut bind_group_entries: Vec<_> = self1231.bindless_index_tables1232.iter()1233.map(|bindless_index_table| bindless_index_table.bind_group_entry())1234.collect();12351236for &(&binding, ref binding_resource_array) in binding_resource_arrays.iter() {1237bind_group_entries.push(BindGroupEntry {1238binding,1239resource: match *binding_resource_array {1240BindingResourceArray::Buffers(ref buffer_bindings) => {1241BindingResource::BufferArray(&buffer_bindings[..])1242}1243BindingResourceArray::TextureViews(ref texture_views) => {1244BindingResource::TextureViewArray(&texture_views[..])1245}1246BindingResourceArray::Samplers(ref samplers) => {1247BindingResource::SamplerArray(&samplers[..])1248}1249},1250});1251}12521253// Create bind group entries for any data buffers we're managing.1254for data_buffer in self.data_buffers.values() {1255bind_group_entries.push(BindGroupEntry {1256binding: *data_buffer.binding_number,1257resource: data_buffer1258.buffer1259.buffer()1260.expect("Backing data buffer must have been uploaded by now")1261.as_entire_binding(),1262});1263}12641265self.bind_group =1266Some(render_device.create_bind_group(label, bind_group_layout, &bind_group_entries));1267}12681269/// Writes any buffers that we're managing to the GPU.1270///1271/// Currently, this consists of the bindless index table plus any data1272/// buffers we're managing.1273fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {1274for bindless_index_table in &mut self.bindless_index_tables {1275bindless_index_table1276.buffer1277.write(render_device, render_queue);1278}12791280for data_buffer in self.data_buffers.values_mut() {1281data_buffer.buffer.write(render_device, render_queue);1282}1283}12841285/// Converts our binding arrays into binding resource arrays suitable for1286/// passing to `wgpu`.1287fn create_binding_resource_arrays<'a>(1288&'a self,1289fallback_bindless_resources: &'a FallbackBindlessResources,1290fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,1291fallback_image: &'a FallbackImage,1292bindless_descriptor: &'a BindlessDescriptor,1293required_binding_array_size: Option<u32>,1294) -> Vec<(&'a u32, BindingResourceArray<'a>)> {1295let mut binding_resource_arrays = vec![];12961297// Build sampler bindings.1298self.create_sampler_binding_resource_arrays(1299&mut binding_resource_arrays,1300fallback_bindless_resources,1301required_binding_array_size,1302);13031304// Build texture bindings.1305self.create_texture_binding_resource_arrays(1306&mut binding_resource_arrays,1307fallback_image,1308required_binding_array_size,1309);13101311// Build buffer bindings.1312self.create_buffer_binding_resource_arrays(1313&mut binding_resource_arrays,1314fallback_buffers,1315bindless_descriptor,1316required_binding_array_size,1317);13181319binding_resource_arrays1320}13211322/// Accumulates sampler binding arrays into binding resource arrays suitable1323/// for passing to `wgpu`.1324fn create_sampler_binding_resource_arrays<'a, 'b>(1325&'a self,1326binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1327fallback_bindless_resources: &'a FallbackBindlessResources,1328required_binding_array_size: Option<u32>,1329) {1330// We have one binding resource array per sampler type.1331for (bindless_resource_type, fallback_sampler) in [1332(1333BindlessResourceType::SamplerFiltering,1334&fallback_bindless_resources.filtering_sampler,1335),1336(1337BindlessResourceType::SamplerNonFiltering,1338&fallback_bindless_resources.non_filtering_sampler,1339),1340(1341BindlessResourceType::SamplerComparison,1342&fallback_bindless_resources.comparison_sampler,1343),1344] {1345let mut sampler_bindings = vec![];13461347match self.samplers.get(&bindless_resource_type) {1348Some(sampler_bindless_binding_array) => {1349for maybe_bindless_binding in sampler_bindless_binding_array.bindings.iter() {1350match *maybe_bindless_binding {1351Some(ref bindless_binding) => {1352sampler_bindings.push(&*bindless_binding.resource);1353}1354None => sampler_bindings.push(&**fallback_sampler),1355}1356}1357}13581359None => {1360// Fill with a single fallback sampler.1361sampler_bindings.push(&**fallback_sampler);1362}1363}13641365if let Some(required_binding_array_size) = required_binding_array_size {1366sampler_bindings.extend(iter::repeat_n(1367&**fallback_sampler,1368required_binding_array_size as usize - sampler_bindings.len(),1369));1370}13711372let binding_number = bindless_resource_type1373.binding_number()1374.expect("Sampler bindless resource type must have a binding number");13751376binding_resource_arrays.push((1377&**binding_number,1378BindingResourceArray::Samplers(sampler_bindings),1379));1380}1381}13821383/// Accumulates texture binding arrays into binding resource arrays suitable1384/// for passing to `wgpu`.1385fn create_texture_binding_resource_arrays<'a, 'b>(1386&'a self,1387binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1388fallback_image: &'a FallbackImage,1389required_binding_array_size: Option<u32>,1390) {1391for (bindless_resource_type, fallback_image) in [1392(BindlessResourceType::Texture1d, &fallback_image.d1),1393(BindlessResourceType::Texture2d, &fallback_image.d2),1394(1395BindlessResourceType::Texture2dArray,1396&fallback_image.d2_array,1397),1398(BindlessResourceType::Texture3d, &fallback_image.d3),1399(BindlessResourceType::TextureCube, &fallback_image.cube),1400(1401BindlessResourceType::TextureCubeArray,1402&fallback_image.cube_array,1403),1404] {1405let mut texture_bindings = vec![];14061407let binding_number = bindless_resource_type1408.binding_number()1409.expect("Texture bindless resource type must have a binding number");14101411match self.textures.get(&bindless_resource_type) {1412Some(texture_bindless_binding_array) => {1413for maybe_bindless_binding in texture_bindless_binding_array.bindings.iter() {1414match *maybe_bindless_binding {1415Some(ref bindless_binding) => {1416texture_bindings.push(&*bindless_binding.resource);1417}1418None => texture_bindings.push(&*fallback_image.texture_view),1419}1420}1421}14221423None => {1424// Fill with a single fallback image.1425texture_bindings.push(&*fallback_image.texture_view);1426}1427}14281429if let Some(required_binding_array_size) = required_binding_array_size {1430texture_bindings.extend(iter::repeat_n(1431&*fallback_image.texture_view,1432required_binding_array_size as usize - texture_bindings.len(),1433));1434}14351436binding_resource_arrays.push((1437binding_number,1438BindingResourceArray::TextureViews(texture_bindings),1439));1440}1441}14421443/// Accumulates buffer binding arrays into binding resource arrays suitable1444/// for `wgpu`.1445fn create_buffer_binding_resource_arrays<'a, 'b>(1446&'a self,1447binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1448fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,1449bindless_descriptor: &'a BindlessDescriptor,1450required_binding_array_size: Option<u32>,1451) {1452for bindless_buffer_descriptor in bindless_descriptor.buffers.iter() {1453let Some(buffer_bindless_binding_array) =1454self.buffers.get(&bindless_buffer_descriptor.bindless_index)1455else {1456// This is OK, because index buffers are present in1457// `BindlessDescriptor::buffers` but not in1458// `BindlessDescriptor::resources`.1459continue;1460};14611462let fallback_buffer = fallback_buffers1463.get(&bindless_buffer_descriptor.bindless_index)1464.expect("Fallback buffer should exist");14651466let mut buffer_bindings: Vec<_> = buffer_bindless_binding_array1467.bindings1468.iter()1469.map(|maybe_bindless_binding| {1470let buffer = match *maybe_bindless_binding {1471None => fallback_buffer,1472Some(ref bindless_binding) => &bindless_binding.resource,1473};1474BufferBinding {1475buffer,1476offset: 0,1477size: None,1478}1479})1480.collect();14811482if let Some(required_binding_array_size) = required_binding_array_size {1483buffer_bindings.extend(iter::repeat_n(1484BufferBinding {1485buffer: fallback_buffer,1486offset: 0,1487size: None,1488},1489required_binding_array_size as usize - buffer_bindings.len(),1490));1491}14921493binding_resource_arrays.push((1494&*buffer_bindless_binding_array.binding_number,1495BindingResourceArray::Buffers(buffer_bindings),1496));1497}1498}14991500/// Returns the [`BindGroup`] corresponding to this slab, if it's been1501/// prepared.1502fn bind_group(&self) -> Option<&BindGroup> {1503self.bind_group.as_ref()1504}15051506/// Returns the bindless index table containing the given bindless index.1507fn get_bindless_index_table(1508&self,1509bindless_index: BindlessIndex,1510) -> Option<&MaterialBindlessIndexTable> {1511let table_index = self1512.bindless_index_tables1513.binary_search_by(|bindless_index_table| {1514if bindless_index < bindless_index_table.index_range.start {1515Ordering::Less1516} else if bindless_index >= bindless_index_table.index_range.end {1517Ordering::Greater1518} else {1519Ordering::Equal1520}1521})1522.ok()?;1523self.bindless_index_tables.get(table_index)1524}1525}15261527impl<R> MaterialBindlessBindingArray<R>1528where1529R: GetBindingResourceId,1530{1531/// Creates a new [`MaterialBindlessBindingArray`] with the given binding1532/// number, managing resources of the given type.1533fn new(1534binding_number: BindingNumber,1535resource_type: BindlessResourceType,1536) -> MaterialBindlessBindingArray<R> {1537MaterialBindlessBindingArray {1538binding_number,1539bindings: vec![],1540resource_type,1541resource_to_slot: HashMap::default(),1542free_slots: vec![],1543len: 0,1544}1545}15461547/// Returns the slot corresponding to the given resource, if that resource1548/// is located in this binding array.1549///1550/// If the resource isn't in this binding array, this method returns `None`.1551fn find(&self, binding_resource_id: BindingResourceId) -> Option<u32> {1552self.resource_to_slot.get(&binding_resource_id).copied()1553}15541555/// Inserts a bindless resource into a binding array and returns the index1556/// of the slot it was inserted into.1557fn insert(&mut self, binding_resource_id: BindingResourceId, resource: R) -> u32 {1558match self.resource_to_slot.entry(binding_resource_id) {1559bevy_platform::collections::hash_map::Entry::Occupied(o) => {1560let slot = *o.get();15611562self.bindings[slot as usize]1563.as_mut()1564.expect("A slot in the resource_to_slot map should have a value")1565.ref_count += 1;15661567slot1568}1569bevy_platform::collections::hash_map::Entry::Vacant(v) => {1570let slot = self.free_slots.pop().unwrap_or(self.len);1571v.insert(slot);15721573if self.bindings.len() < slot as usize + 1 {1574self.bindings.resize_with(slot as usize + 1, || None);1575}1576self.bindings[slot as usize] = Some(MaterialBindlessBinding::new(resource));15771578self.len += 1;1579slot1580}1581}1582}15831584/// Removes a reference to an object from the slot.1585///1586/// If the reference count dropped to 0 and the object was freed, this1587/// method returns true. If the object was still referenced after removing1588/// it, returns false.1589fn remove(&mut self, slot: u32) -> bool {1590let maybe_binding = &mut self.bindings[slot as usize];1591let binding = maybe_binding1592.as_mut()1593.expect("Attempted to free an already-freed binding");15941595binding.ref_count -= 1;1596if binding.ref_count != 0 {1597return false;1598}15991600let binding_resource_id = binding.resource.binding_resource_id(self.resource_type);1601self.resource_to_slot.remove(&binding_resource_id);16021603*maybe_binding = None;1604self.free_slots.push(slot);1605self.len -= 1;1606true1607}1608}16091610impl<R> MaterialBindlessBinding<R>1611where1612R: GetBindingResourceId,1613{1614/// Creates a new [`MaterialBindlessBinding`] for a freshly-added resource.1615///1616/// The reference count is initialized to 1.1617fn new(resource: R) -> MaterialBindlessBinding<R> {1618MaterialBindlessBinding {1619resource,1620ref_count: 1,1621}1622}1623}16241625/// Returns true if the material will *actually* use bindless resources or false1626/// if it won't.1627///1628/// This takes the platform support (or lack thereof) for bindless resources1629/// into account.1630pub fn material_uses_bindless_resources<M>(render_device: &RenderDevice) -> bool1631where1632M: Material,1633{1634M::bindless_slot_count().is_some_and(|bindless_slot_count| {1635M::bindless_supported(render_device) && bindless_slot_count.resolve() > 11636})1637}16381639impl MaterialBindlessSlab {1640/// Creates a new [`MaterialBindlessSlab`] for a material with the given1641/// bindless descriptor.1642///1643/// We use this when no existing slab could hold a material to be allocated.1644fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab {1645let mut buffers = HashMap::default();1646let mut samplers = HashMap::default();1647let mut textures = HashMap::default();1648let mut data_buffers = HashMap::default();16491650for (bindless_index, bindless_resource_type) in1651bindless_descriptor.resources.iter().enumerate()1652{1653let bindless_index = BindlessIndex(bindless_index as u32);1654match *bindless_resource_type {1655BindlessResourceType::None => {}1656BindlessResourceType::Buffer => {1657let binding_number = bindless_descriptor1658.buffers1659.iter()1660.find(|bindless_buffer_descriptor| {1661bindless_buffer_descriptor.bindless_index == bindless_index1662})1663.expect(1664"Bindless buffer descriptor matching that bindless index should be \1665present",1666)1667.binding_number;1668buffers.insert(1669bindless_index,1670MaterialBindlessBindingArray::new(binding_number, *bindless_resource_type),1671);1672}1673BindlessResourceType::DataBuffer => {1674// Copy the data in.1675let buffer_descriptor = bindless_descriptor1676.buffers1677.iter()1678.find(|bindless_buffer_descriptor| {1679bindless_buffer_descriptor.bindless_index == bindless_index1680})1681.expect(1682"Bindless buffer descriptor matching that bindless index should be \1683present",1684);1685data_buffers.insert(1686bindless_index,1687MaterialDataBuffer::new(1688buffer_descriptor.binding_number,1689buffer_descriptor1690.size1691.expect("Data buffers should have a size")1692as u32,1693),1694);1695}1696BindlessResourceType::SamplerFiltering1697| BindlessResourceType::SamplerNonFiltering1698| BindlessResourceType::SamplerComparison => {1699samplers.insert(1700*bindless_resource_type,1701MaterialBindlessBindingArray::new(1702*bindless_resource_type.binding_number().unwrap(),1703*bindless_resource_type,1704),1705);1706}1707BindlessResourceType::Texture1d1708| BindlessResourceType::Texture2d1709| BindlessResourceType::Texture2dArray1710| BindlessResourceType::Texture3d1711| BindlessResourceType::TextureCube1712| BindlessResourceType::TextureCubeArray => {1713textures.insert(1714*bindless_resource_type,1715MaterialBindlessBindingArray::new(1716*bindless_resource_type.binding_number().unwrap(),1717*bindless_resource_type,1718),1719);1720}1721}1722}17231724let bindless_index_tables = bindless_descriptor1725.index_tables1726.iter()1727.map(MaterialBindlessIndexTable::new)1728.collect();17291730MaterialBindlessSlab {1731bind_group: None,1732bindless_index_tables,1733samplers,1734textures,1735buffers,1736data_buffers,1737free_slots: vec![],1738live_allocation_count: 0,1739allocated_resource_count: 0,1740}1741}1742}17431744pub fn init_fallback_bindless_resources(mut commands: Commands, render_device: Res<RenderDevice>) {1745commands.insert_resource(FallbackBindlessResources {1746filtering_sampler: render_device.create_sampler(&SamplerDescriptor {1747label: Some("fallback filtering sampler"),1748..default()1749}),1750non_filtering_sampler: render_device.create_sampler(&SamplerDescriptor {1751label: Some("fallback non-filtering sampler"),1752mag_filter: FilterMode::Nearest,1753min_filter: FilterMode::Nearest,1754mipmap_filter: FilterMode::Nearest,1755..default()1756}),1757comparison_sampler: render_device.create_sampler(&SamplerDescriptor {1758label: Some("fallback comparison sampler"),1759compare: Some(CompareFunction::Always),1760..default()1761}),1762});1763}17641765impl MaterialBindGroupNonBindlessAllocator {1766/// Creates a new [`MaterialBindGroupNonBindlessAllocator`] managing the1767/// bind groups for a single non-bindless material.1768fn new(label: Option<&'static str>) -> MaterialBindGroupNonBindlessAllocator {1769MaterialBindGroupNonBindlessAllocator {1770label,1771bind_groups: vec![],1772to_prepare: HashSet::default(),1773free_indices: vec![],1774}1775}17761777/// Inserts a bind group, either unprepared or prepared, into this allocator1778/// and returns a [`MaterialBindingId`].1779///1780/// The returned [`MaterialBindingId`] can later be used to fetch the bind1781/// group.1782fn allocate(&mut self, bind_group: MaterialNonBindlessAllocatedBindGroup) -> MaterialBindingId {1783let group_id = self1784.free_indices1785.pop()1786.unwrap_or(MaterialBindGroupIndex(self.bind_groups.len() as u32));1787if self.bind_groups.len() < *group_id as usize + 1 {1788self.bind_groups1789.resize_with(*group_id as usize + 1, || None);1790}17911792if matches!(1793bind_group,1794MaterialNonBindlessAllocatedBindGroup::Unprepared { .. }1795) {1796self.to_prepare.insert(group_id);1797}17981799self.bind_groups[*group_id as usize] = Some(bind_group);18001801MaterialBindingId {1802group: group_id,1803slot: default(),1804}1805}18061807/// Inserts an unprepared bind group into this allocator and returns a1808/// [`MaterialBindingId`].1809fn allocate_unprepared(1810&mut self,1811unprepared_bind_group: UnpreparedBindGroup,1812bind_group_layout: BindGroupLayout,1813) -> MaterialBindingId {1814self.allocate(MaterialNonBindlessAllocatedBindGroup::Unprepared {1815bind_group: unprepared_bind_group,1816layout: bind_group_layout,1817})1818}18191820/// Inserts an prepared bind group into this allocator and returns a1821/// [`MaterialBindingId`].1822fn allocate_prepared(&mut self, prepared_bind_group: PreparedBindGroup) -> MaterialBindingId {1823self.allocate(MaterialNonBindlessAllocatedBindGroup::Prepared {1824bind_group: prepared_bind_group,1825uniform_buffers: vec![],1826})1827}18281829/// Deallocates the bind group with the given binding ID.1830fn free(&mut self, binding_id: MaterialBindingId) {1831debug_assert_eq!(binding_id.slot, MaterialBindGroupSlot(0));1832debug_assert!(self.bind_groups[*binding_id.group as usize].is_some());1833self.bind_groups[*binding_id.group as usize] = None;1834self.to_prepare.remove(&binding_id.group);1835self.free_indices.push(binding_id.group);1836}18371838/// Returns a wrapper around the bind group with the given index.1839fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialNonBindlessSlab<'_>> {1840self.bind_groups[group.0 as usize]1841.as_ref()1842.map(|bind_group| match bind_group {1843MaterialNonBindlessAllocatedBindGroup::Prepared { bind_group, .. } => {1844MaterialNonBindlessSlab::Prepared(bind_group)1845}1846MaterialNonBindlessAllocatedBindGroup::Unprepared { .. } => {1847MaterialNonBindlessSlab::Unprepared1848}1849})1850}18511852/// Prepares any as-yet unprepared bind groups that this allocator is1853/// managing.1854///1855/// Unprepared bind groups can be added to this allocator with1856/// [`Self::allocate_unprepared`]. Such bind groups will defer being1857/// prepared until the next time this method is called.1858fn prepare_bind_groups(&mut self, render_device: &RenderDevice) {1859for bind_group_index in mem::take(&mut self.to_prepare) {1860let Some(MaterialNonBindlessAllocatedBindGroup::Unprepared {1861bind_group: unprepared_bind_group,1862layout: bind_group_layout,1863}) = mem::take(&mut self.bind_groups[*bind_group_index as usize])1864else {1865panic!("Allocation didn't exist or was already prepared");1866};18671868// Pack any `Data` into uniform buffers.1869let mut uniform_buffers = vec![];1870for (index, binding) in unprepared_bind_group.bindings.iter() {1871let OwnedBindingResource::Data(ref owned_data) = *binding else {1872continue;1873};1874let label = format!("material uniform data {}", *index);1875let uniform_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {1876label: Some(&label),1877contents: &owned_data.0,1878usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,1879});1880uniform_buffers.push(uniform_buffer);1881}18821883// Create bind group entries.1884let mut bind_group_entries = vec![];1885let mut uniform_buffers_iter = uniform_buffers.iter();1886for (index, binding) in unprepared_bind_group.bindings.iter() {1887match *binding {1888OwnedBindingResource::Data(_) => {1889bind_group_entries.push(BindGroupEntry {1890binding: *index,1891resource: uniform_buffers_iter1892.next()1893.expect("We should have created uniform buffers for each `Data`")1894.as_entire_binding(),1895});1896}1897_ => bind_group_entries.push(BindGroupEntry {1898binding: *index,1899resource: binding.get_binding(),1900}),1901}1902}19031904// Create the bind group.1905let bind_group = render_device.create_bind_group(1906self.label,1907&bind_group_layout,1908&bind_group_entries,1909);19101911self.bind_groups[*bind_group_index as usize] =1912Some(MaterialNonBindlessAllocatedBindGroup::Prepared {1913bind_group: PreparedBindGroup {1914bindings: unprepared_bind_group.bindings,1915bind_group,1916},1917uniform_buffers,1918});1919}1920}1921}19221923impl<'a> MaterialSlab<'a> {1924/// Returns the [`BindGroup`] corresponding to this slab, if it's been1925/// prepared.1926///1927/// You can prepare bind groups by calling1928/// [`MaterialBindGroupAllocator::prepare_bind_groups`]. If the bind group1929/// isn't ready, this method returns `None`.1930pub fn bind_group(&self) -> Option<&'a BindGroup> {1931match self.0 {1932MaterialSlabImpl::Bindless(material_bindless_slab) => {1933material_bindless_slab.bind_group()1934}1935MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared(1936prepared_bind_group,1937)) => Some(&prepared_bind_group.bind_group),1938MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared) => None,1939}1940}1941}19421943impl MaterialDataBuffer {1944/// Creates a new [`MaterialDataBuffer`] managing a buffer of elements of1945/// size `aligned_element_size` that will be bound to the given binding1946/// number.1947fn new(binding_number: BindingNumber, aligned_element_size: u32) -> MaterialDataBuffer {1948MaterialDataBuffer {1949binding_number,1950buffer: RetainedRawBufferVec::new(BufferUsages::STORAGE),1951aligned_element_size,1952free_slots: vec![],1953len: 0,1954}1955}19561957/// Allocates a slot for a new piece of data, copies the data into that1958/// slot, and returns the slot ID.1959///1960/// The size of the piece of data supplied to this method must equal the1961/// [`Self::aligned_element_size`] provided to [`MaterialDataBuffer::new`].1962fn insert(&mut self, data: &[u8]) -> u32 {1963// Make sure the data is of the right length.1964debug_assert_eq!(data.len(), self.aligned_element_size as usize);19651966// Grab a slot.1967let slot = self.free_slots.pop().unwrap_or(self.len);19681969// Calculate the range we're going to copy to.1970let start = slot as usize * self.aligned_element_size as usize;1971let end = (slot as usize + 1) * self.aligned_element_size as usize;19721973// Resize the buffer if necessary.1974if self.buffer.len() < end {1975self.buffer.reserve_internal(end);1976}1977while self.buffer.values().len() < end {1978self.buffer.push(0);1979}19801981// Copy in the data.1982self.buffer.values_mut()[start..end].copy_from_slice(data);19831984// Mark the buffer dirty, and finish up.1985self.len += 1;1986self.buffer.dirty = BufferDirtyState::NeedsReserve;1987slot1988}19891990/// Marks the given slot as free.1991fn remove(&mut self, slot: u32) {1992self.free_slots.push(slot);1993self.len -= 1;1994}1995}199619971998