Path: blob/main/crates/bevy_pbr/src/material_bind_groups.rs
9353 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, PipelineCache};15use bevy_render::{16render_resource::{17BindGroup, BindGroupEntry, BindGroupLayoutDescriptor, BindingNumber, BindingResource,18BindingResources, BindlessDescriptor, BindlessIndex, BindlessIndexTableDescriptor,19BindlessResourceType, Buffer, BufferBinding, BufferDescriptor, BufferId,20BufferInitDescriptor, BufferUsages, CompareFunction, FilterMode, MipmapFilterMode,21OwnedBindingResource, PreparedBindGroup, RawBufferVec, Sampler, SamplerDescriptor,22SamplerId, TextureView, TextureViewDimension, TextureViewId, UnpreparedBindGroup,23WgpuSampler, WgpuTextureView,24},25renderer::{RenderDevice, RenderQueue},26settings::WgpuFeatures,27texture::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};3435#[derive(Resource, Deref, DerefMut, Default)]36pub struct MaterialBindGroupAllocators(TypeIdMap<MaterialBindGroupAllocator>);3738/// A resource that places materials into bind groups and tracks their39/// resources.40///41/// Internally, Bevy has separate allocators for bindless and non-bindless42/// materials. This resource provides a common interface to the specific43/// allocator in use.44pub enum MaterialBindGroupAllocator {45/// The allocator used when the material is bindless.46Bindless(Box<MaterialBindGroupBindlessAllocator>),47/// The allocator used when the material is non-bindless.48NonBindless(Box<MaterialBindGroupNonBindlessAllocator>),49}5051/// The allocator that places bindless materials into bind groups and tracks52/// their resources.53pub struct MaterialBindGroupBindlessAllocator {54/// The label of the bind group allocator to use for allocated buffers.55label: &'static str,56/// The slabs, each of which contains a bind group.57slabs: Vec<MaterialBindlessSlab>,58/// The layout of the bind groups that we produce.59bind_group_layout: BindGroupLayoutDescriptor,60/// Information about the bindless resources in the material.61///62/// We use this information to create and maintain bind groups.63bindless_descriptor: BindlessDescriptor,6465/// 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, each68/// appropriately sized. Each buffer contains one uninitialized element of69/// the applicable type.70fallback_buffers: HashMap<BindlessIndex, Buffer>,7172/// 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`.76slab_capacity: u32,77}7879/// 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 be84/// regenerated.85bind_group: Option<BindGroup>,8687/// The GPU-accessible buffers that hold the mapping from binding index to88/// bindless slot.89///90/// This is conventionally assigned to bind group binding 0, but it can be91/// changed using the `#[bindless(index_table(binding(B)))]` attribute on92/// `AsBindGroup`.93///94/// Because the slab binary searches this table, the entries within must be95/// sorted by bindless index.96bindless_index_tables: Vec<MaterialBindlessIndexTable>,9798/// The binding arrays containing samplers.99samplers: HashMap<BindlessResourceType, MaterialBindlessBindingArray<Sampler>>,100/// The binding arrays containing textures.101textures: HashMap<BindlessResourceType, MaterialBindlessBindingArray<TextureView>>,102/// The binding arrays containing buffers.103buffers: HashMap<BindlessIndex, MaterialBindlessBindingArray<Buffer>>,104/// The buffers that contain plain old data (i.e. the structure-level105/// `#[data]` attribute of `AsBindGroup`).106data_buffers: HashMap<BindlessIndex, MaterialDataBuffer>,107108/// A list of free slot IDs.109free_slots: Vec<MaterialBindGroupSlot>,110/// The total number of materials currently allocated in this slab.111live_allocation_count: u32,112/// The total number of resources currently allocated in the binding arrays.113allocated_resource_count: u32,114}115116/// A GPU-accessible buffer that holds the mapping from binding index to117/// bindless slot.118///119/// This is conventionally assigned to bind group binding 0, but it can be120/// changed by altering the [`Self::binding_number`], which corresponds to the121/// `#[bindless(index_table(binding(B)))]` attribute in `AsBindGroup`.122struct MaterialBindlessIndexTable {123/// The buffer containing the mappings.124buffer: 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 bindless128/// 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`.132index_range: Range<BindlessIndex>,133/// The binding number that this index table is assigned to in the shader.134binding_number: BindingNumber,135}136137/// A single binding array for storing bindless resources and the bookkeeping138/// necessary to allocate into it.139struct MaterialBindlessBindingArray<R>140where141R: GetBindingResourceId,142{143/// The number of the binding that we attach this binding array to.144binding_number: BindingNumber,145/// A mapping from bindless slot index to the resource stored in that slot,146/// if any.147bindings: Vec<Option<MaterialBindlessBinding<R>>>,148/// The type of resource stored in this binding array.149resource_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`].153resource_to_slot: HashMap<BindingResourceId, u32>,154/// A list of free slots in [`Self::bindings`] that contain no binding.155free_slots: Vec<u32>,156/// The number of allocated objects in this binding array.157len: u32,158}159160/// A single resource (sampler, texture, or buffer) in a binding array.161///162/// Resources hold a reference count, which specifies the number of materials163/// currently allocated within the slab that refer to this resource. When the164/// reference count drops to zero, the resource is freed.165struct MaterialBindlessBinding<R>166where167R: GetBindingResourceId,168{169/// The sampler, texture, or buffer.170resource: R,171/// The number of materials currently allocated within the containing slab172/// that use this resource.173ref_count: u32,174}175176/// 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.179label: &'static str,180/// A mapping from [`MaterialBindGroupIndex`] to the bind group allocated in181/// each slot.182bind_groups: Vec<Option<MaterialNonBindlessAllocatedBindGroup>>,183/// The bind groups that are dirty and need to be prepared.184///185/// To prepare the bind groups, call186/// [`MaterialBindGroupAllocator::prepare_bind_groups`].187to_prepare: HashSet<MaterialBindGroupIndex>,188/// A list of free bind group indices.189free_indices: Vec<MaterialBindGroupIndex>,190}191192/// A single bind group that a [`MaterialBindGroupNonBindlessAllocator`] is193/// currently managing.194enum MaterialNonBindlessAllocatedBindGroup {195/// An unprepared bind group.196///197/// The allocator prepares all outstanding unprepared bind groups when198/// [`MaterialBindGroupNonBindlessAllocator::prepare_bind_groups`] is199/// called.200Unprepared {201/// The unprepared bind group, including extra data.202bind_group: UnpreparedBindGroup,203/// The layout of that bind group.204layout: BindGroupLayoutDescriptor,205},206/// A bind group that's already been prepared.207Prepared {208bind_group: PreparedBindGroup,209#[expect(dead_code, reason = "These buffers are only referenced by bind groups")]210uniform_buffers: Vec<Buffer>,211},212}213214/// Dummy instances of various resources that we fill unused slots in binding215/// arrays with.216#[derive(Resource)]217pub struct FallbackBindlessResources {218/// A dummy filtering sampler.219filtering_sampler: Sampler,220/// A dummy non-filtering sampler.221non_filtering_sampler: Sampler,222/// A dummy comparison sampler.223comparison_sampler: Sampler,224}225226/// The `wgpu` ID of a single bindless or non-bindless resource.227#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]228enum BindingResourceId {229/// A buffer.230Buffer(BufferId),231/// A texture view, with the given dimension.232TextureView(TextureViewDimension, TextureViewId),233/// A sampler.234Sampler(SamplerId),235/// A buffer containing plain old data.236///237/// This corresponds to the `#[data]` structure-level attribute on238/// `AsBindGroup`.239DataBuffer,240}241242/// 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 order246/// to satisfy `wgpu`'s lifetime requirements.247enum BindingResourceArray<'a> {248/// A list of bindings.249Buffers(Vec<BufferBinding<'a>>),250/// A list of texture views.251TextureViews(Vec<&'a WgpuTextureView>),252/// A list of samplers.253Samplers(Vec<&'a WgpuSampler>),254}255256/// The location of a material (either bindless or non-bindless) within the257/// 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.262pub group: MaterialBindGroupIndex,263/// The slot within that bind group.264///265/// Non-bindless materials will always have a slot of 0.266pub slot: MaterialBindGroupSlot,267}268269/// The index of each material bind group.270///271/// In bindless mode, each bind group contains multiple materials. In272/// 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);276277impl From<u32> for MaterialBindGroupIndex {278fn from(value: u32) -> Self {279MaterialBindGroupIndex(value)280}281}282283/// The index of the slot containing material data within each material bind284/// group.285///286/// In bindless mode, this slot is needed to locate the material data in each287/// bind group, since multiple materials are packed into a single slab. In288/// 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);292293/// The CPU/GPU synchronization state of a buffer that we maintain.294///295/// Currently, the only buffer that we maintain is the296/// [`MaterialBindlessIndexTable`].297enum BufferDirtyState {298/// The buffer is currently synchronized between the CPU and GPU.299Clean,300/// The buffer hasn't been created yet.301NeedsReserve,302/// The buffer exists on both CPU and GPU, but the GPU data is out of date.303NeedsUpload,304}305306/// Information that describes a potential allocation of an307/// [`UnpreparedBindGroup`] into a slab.308struct BindlessAllocationCandidate {309/// A map that, for every resource in the [`UnpreparedBindGroup`] that310/// already existed in this slab, maps bindless index of that resource to311/// its slot in the appropriate binding array.312pre_existing_resources: HashMap<BindlessIndex, u32>,313/// Stores the number of free slots that are needed to satisfy this314/// allocation.315needed_free_slots: u32,316}317318/// A trait that allows fetching the [`BindingResourceId`] from a319/// [`BindlessResourceType`].320///321/// This is used when freeing bindless resources, in order to locate the IDs322/// assigned to each resource so that they can be removed from the appropriate323/// maps.324trait GetBindingResourceId {325/// Returns the [`BindingResourceId`] for this resource.326///327/// `resource_type` specifies this resource's type. This is used for328/// textures, as a `wgpu` [`TextureView`] doesn't store enough information329/// itself to determine its dimension.330fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId;331}332333/// The public interface to a slab, which represents a single bind group.334pub struct MaterialSlab<'a>(MaterialSlabImpl<'a>);335336/// 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 slab341/// is bindless.342Bindless(&'a MaterialBindlessSlab),343/// The implementation of the slab interface we use when the slab344/// is non-bindless.345NonBindless(MaterialNonBindlessSlab<'a>),346}347348/// A single bind group that the [`MaterialBindGroupNonBindlessAllocator`]349/// manages.350enum MaterialNonBindlessSlab<'a> {351/// A slab that has a bind group.352Prepared(&'a PreparedBindGroup),353/// A slab that doesn't yet have a bind group.354Unprepared,355}356357/// Manages an array of untyped plain old data on GPU and allocates individual358/// 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.363binding_number: BindingNumber,364/// The actual data.365///366/// Note that this is untyped (`u8`); the actual aligned size of each367/// element is given by [`Self::aligned_element_size`];368buffer: RetainedRawBufferVec<u8>,369/// The size of each element in the buffer, including padding and alignment370/// if any.371aligned_element_size: u32,372/// A list of free slots within the buffer.373free_slots: Vec<u32>,374/// The actual number of slots that have been allocated.375len: u32,376}377378/// A buffer containing plain old data, already packed into the appropriate GPU379/// format, and that can be updated incrementally.380///381/// This structure exists in order to encapsulate the lazy update382/// ([`BufferDirtyState`]) logic in a single place.383#[derive(Deref, DerefMut)]384struct RetainedRawBufferVec<T>385where386T: Pod,387{388/// The contents of the buffer.389#[deref]390buffer: RawBufferVec<T>,391/// Whether the contents of the buffer have been uploaded to the GPU.392dirty: BufferDirtyState,393}394395/// 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` what398/// the size is.399const DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE: u64 = 16;400401impl From<u32> for MaterialBindGroupSlot {402fn from(value: u32) -> Self {403MaterialBindGroupSlot(value)404}405}406407impl From<MaterialBindGroupSlot> for u32 {408fn from(value: MaterialBindGroupSlot) -> Self {409value.0410}411}412413impl<'a> From<&'a OwnedBindingResource> for BindingResourceId {414fn from(value: &'a OwnedBindingResource) -> Self {415match *value {416OwnedBindingResource::Buffer(ref buffer) => BindingResourceId::Buffer(buffer.id()),417OwnedBindingResource::Data(_) => BindingResourceId::DataBuffer,418OwnedBindingResource::TextureView(ref texture_view_dimension, ref texture_view) => {419BindingResourceId::TextureView(*texture_view_dimension, texture_view.id())420}421OwnedBindingResource::Sampler(_, ref sampler) => {422BindingResourceId::Sampler(sampler.id())423}424}425}426}427428impl GetBindingResourceId for Buffer {429fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {430BindingResourceId::Buffer(self.id())431}432}433434impl GetBindingResourceId for Sampler {435fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId {436BindingResourceId::Sampler(self.id())437}438}439440impl GetBindingResourceId for TextureView {441fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId {442let texture_view_dimension = match resource_type {443BindlessResourceType::Texture1d => TextureViewDimension::D1,444BindlessResourceType::Texture2d => TextureViewDimension::D2,445BindlessResourceType::Texture2dArray => TextureViewDimension::D2Array,446BindlessResourceType::Texture3d => TextureViewDimension::D3,447BindlessResourceType::TextureCube => TextureViewDimension::Cube,448BindlessResourceType::TextureCubeArray => TextureViewDimension::CubeArray,449_ => panic!("Resource type is not a texture"),450};451BindingResourceId::TextureView(texture_view_dimension, self.id())452}453}454455impl MaterialBindGroupAllocator {456/// Creates a new [`MaterialBindGroupAllocator`] managing the data for a457/// single material.458pub fn new(459render_device: &RenderDevice,460label: &'static str,461bindless_descriptor: Option<BindlessDescriptor>,462bind_group_layout: BindGroupLayoutDescriptor,463slab_capacity: Option<BindlessSlabResourceLimit>,464) -> MaterialBindGroupAllocator {465if let Some(bindless_descriptor) = bindless_descriptor {466MaterialBindGroupAllocator::Bindless(Box::new(MaterialBindGroupBindlessAllocator::new(467render_device,468label,469bindless_descriptor,470bind_group_layout,471slab_capacity,472)))473} else {474MaterialBindGroupAllocator::NonBindless(Box::new(475MaterialBindGroupNonBindlessAllocator::new(label),476))477}478}479480/// Returns the slab with the given index, if one exists.481pub fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialSlab<'_>> {482match *self {483MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator484.get(group)485.map(|bindless_slab| MaterialSlab(MaterialSlabImpl::Bindless(bindless_slab))),486MaterialBindGroupAllocator::NonBindless(ref non_bindless_allocator) => {487non_bindless_allocator.get(group).map(|non_bindless_slab| {488MaterialSlab(MaterialSlabImpl::NonBindless(non_bindless_slab))489})490}491}492}493494/// Allocates an [`UnpreparedBindGroup`] and returns the resulting binding ID.495///496/// This method should generally be preferred over497/// [`Self::allocate_prepared`], because this method supports both bindless498/// and non-bindless bind groups. Only use [`Self::allocate_prepared`] if499/// you need to prepare the bind group yourself.500pub fn allocate_unprepared(501&mut self,502unprepared_bind_group: UnpreparedBindGroup,503bind_group_layout: &BindGroupLayoutDescriptor,504) -> MaterialBindingId {505match *self {506MaterialBindGroupAllocator::Bindless(507ref mut material_bind_group_bindless_allocator,508) => material_bind_group_bindless_allocator.allocate_unprepared(unprepared_bind_group),509MaterialBindGroupAllocator::NonBindless(510ref mut material_bind_group_non_bindless_allocator,511) => material_bind_group_non_bindless_allocator512.allocate_unprepared(unprepared_bind_group, (*bind_group_layout).clone()),513}514}515516/// Places a pre-prepared bind group into a slab.517///518/// For bindless materials, the allocator internally manages the bind519/// groups, so calling this method will panic if this is a bindless520/// allocator. Only non-bindless allocators support this method.521///522/// It's generally preferred to use [`Self::allocate_unprepared`], because523/// that method supports both bindless and non-bindless allocators. Only use524/// this method if you need to prepare the bind group yourself.525pub fn allocate_prepared(526&mut self,527prepared_bind_group: PreparedBindGroup,528) -> MaterialBindingId {529match *self {530MaterialBindGroupAllocator::Bindless(_) => {531panic!(532"Bindless resources are incompatible with implementing `as_bind_group` \533directly; implement `unprepared_bind_group` instead or disable bindless"534)535}536MaterialBindGroupAllocator::NonBindless(ref mut non_bindless_allocator) => {537non_bindless_allocator.allocate_prepared(prepared_bind_group)538}539}540}541542/// Deallocates the material with the given binding ID.543///544/// Any resources that are no longer referenced are removed from the slab.545pub fn free(&mut self, material_binding_id: MaterialBindingId) {546match *self {547MaterialBindGroupAllocator::Bindless(548ref mut material_bind_group_bindless_allocator,549) => material_bind_group_bindless_allocator.free(material_binding_id),550MaterialBindGroupAllocator::NonBindless(551ref mut material_bind_group_non_bindless_allocator,552) => material_bind_group_non_bindless_allocator.free(material_binding_id),553}554}555556/// Recreates any bind groups corresponding to slabs that have been modified557/// since last calling [`MaterialBindGroupAllocator::prepare_bind_groups`].558pub fn prepare_bind_groups(559&mut self,560render_device: &RenderDevice,561pipeline_cache: &PipelineCache,562fallback_bindless_resources: &FallbackBindlessResources,563fallback_image: &FallbackImage,564) {565match *self {566MaterialBindGroupAllocator::Bindless(567ref mut material_bind_group_bindless_allocator,568) => material_bind_group_bindless_allocator.prepare_bind_groups(569render_device,570pipeline_cache,571fallback_bindless_resources,572fallback_image,573),574MaterialBindGroupAllocator::NonBindless(575ref mut material_bind_group_non_bindless_allocator,576) => material_bind_group_non_bindless_allocator577.prepare_bind_groups(render_device, pipeline_cache),578}579}580581/// Uploads the contents of all buffers that this582/// [`MaterialBindGroupAllocator`] manages to the GPU.583///584/// Non-bindless allocators don't currently manage any buffers, so this585/// method only has an effect for bindless allocators.586pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {587match *self {588MaterialBindGroupAllocator::Bindless(589ref mut material_bind_group_bindless_allocator,590) => material_bind_group_bindless_allocator.write_buffers(render_device, render_queue),591MaterialBindGroupAllocator::NonBindless(_) => {592// Not applicable.593}594}595}596597/// Get number of allocated slabs for bindless material, returns 0 if it is598/// [`Self::NonBindless`].599pub fn slab_count(&self) -> usize {600match self {601Self::Bindless(bless) => bless.slabs.len(),602Self::NonBindless(_) => 0,603}604}605606/// Get total size of slabs allocated for bindless material, returns 0 if it is607/// [`Self::NonBindless`].608pub fn slabs_size(&self) -> usize {609match self {610Self::Bindless(bless) => bless611.slabs612.iter()613.flat_map(|slab| {614slab.data_buffers615.iter()616.map(|(_, buffer)| buffer.buffer.len())617})618.sum(),619Self::NonBindless(_) => 0,620}621}622623/// Get number of bindless material allocations in slabs, returns 0 if it is624/// [`Self::NonBindless`].625pub fn allocations(&self) -> u64 {626match self {627Self::Bindless(bless) => bless628.slabs629.iter()630.map(|slab| u64::from(slab.allocated_resource_count))631.sum(),632Self::NonBindless(_) => 0,633}634}635}636637impl MaterialBindlessIndexTable {638/// Creates a new [`MaterialBindlessIndexTable`] for a single slab.639fn new(640bindless_index_table_descriptor: &BindlessIndexTableDescriptor,641) -> MaterialBindlessIndexTable {642// Preallocate space for one bindings table, so that there will always be a buffer.643let mut buffer = RetainedRawBufferVec::new(BufferUsages::STORAGE);644for _ in *bindless_index_table_descriptor.indices.start645..*bindless_index_table_descriptor.indices.end646{647buffer.push(0);648}649650MaterialBindlessIndexTable {651buffer,652index_range: bindless_index_table_descriptor.indices.clone(),653binding_number: bindless_index_table_descriptor.binding_number,654}655}656657/// Returns the bindings in the binding index table.658///659/// If the current [`MaterialBindlessIndexTable::index_range`] is M..N, then660/// element *i* of the returned binding index table contains the slot of the661/// bindless resource with bindless index *i* + M.662fn get(&self, slot: MaterialBindGroupSlot) -> &[u32] {663let struct_size = *self.index_range.end as usize - *self.index_range.start as usize;664let start = struct_size * slot.0 as usize;665&self.buffer.values()[start..(start + struct_size)]666}667668/// Returns a single binding from the binding index table.669fn get_binding(670&self,671slot: MaterialBindGroupSlot,672bindless_index: BindlessIndex,673) -> Option<u32> {674if bindless_index < self.index_range.start || bindless_index >= self.index_range.end {675return None;676}677self.get(slot)678.get((*bindless_index - *self.index_range.start) as usize)679.copied()680}681682fn table_length(&self) -> u32 {683self.index_range.end.0 - self.index_range.start.0684}685686/// Updates the binding index table for a single material.687///688/// The `allocated_resource_slots` map contains a mapping from the689/// [`BindlessIndex`] of each resource that the material references to the690/// slot that that resource occupies in the appropriate binding array. This691/// method serializes that map into a binding index table that the shader692/// can read.693fn set(694&mut self,695slot: MaterialBindGroupSlot,696allocated_resource_slots: &HashMap<BindlessIndex, u32>,697) {698let table_len = self.table_length() as usize;699let range = (slot.0 as usize * table_len)..((slot.0 as usize + 1) * table_len);700while self.buffer.len() < range.end {701self.buffer.push(0);702}703704for (&bindless_index, &resource_slot) in allocated_resource_slots {705if self.index_range.contains(&bindless_index) {706self.buffer.set(707*bindless_index + range.start as u32 - *self.index_range.start,708resource_slot,709);710}711}712713// Mark the buffer as needing to be recreated, in case we grew it.714self.buffer.dirty = BufferDirtyState::NeedsReserve;715}716717/// Returns the [`BindGroupEntry`] for the index table itself.718fn bind_group_entry(&self) -> BindGroupEntry<'_> {719BindGroupEntry {720binding: *self.binding_number,721resource: self722.buffer723.buffer()724.expect("Bindings buffer must exist")725.as_entire_binding(),726}727}728}729730impl<T> RetainedRawBufferVec<T>731where732T: Pod,733{734/// Creates a new empty [`RetainedRawBufferVec`] supporting the given735/// [`BufferUsages`].736fn new(buffer_usages: BufferUsages) -> RetainedRawBufferVec<T> {737RetainedRawBufferVec {738buffer: RawBufferVec::new(buffer_usages),739dirty: BufferDirtyState::NeedsUpload,740}741}742743/// Recreates the GPU backing buffer if needed.744fn prepare(&mut self, render_device: &RenderDevice) {745match self.dirty {746BufferDirtyState::Clean | BufferDirtyState::NeedsUpload => {}747BufferDirtyState::NeedsReserve => {748let capacity = self.buffer.len();749self.buffer.reserve(capacity, render_device);750self.dirty = BufferDirtyState::NeedsUpload;751}752}753}754755/// Writes the current contents of the buffer to the GPU if necessary.756fn write(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {757match self.dirty {758BufferDirtyState::Clean => {}759BufferDirtyState::NeedsReserve | BufferDirtyState::NeedsUpload => {760self.buffer.write_buffer(render_device, render_queue);761self.dirty = BufferDirtyState::Clean;762}763}764}765}766767impl MaterialBindGroupBindlessAllocator {768/// Creates a new [`MaterialBindGroupBindlessAllocator`] managing the data769/// for a single bindless material.770fn new(771render_device: &RenderDevice,772label: &'static str,773bindless_descriptor: BindlessDescriptor,774bind_group_layout: BindGroupLayoutDescriptor,775slab_capacity: Option<BindlessSlabResourceLimit>,776) -> MaterialBindGroupBindlessAllocator {777let fallback_buffers = bindless_descriptor778.buffers779.iter()780.map(|bindless_buffer_descriptor| {781(782bindless_buffer_descriptor.bindless_index,783render_device.create_buffer(&BufferDescriptor {784label: Some("bindless fallback buffer"),785size: match bindless_buffer_descriptor.size {786Some(size) => size as u64,787None => DEFAULT_BINDLESS_FALLBACK_BUFFER_SIZE,788},789usage: BufferUsages::STORAGE,790mapped_at_creation: false,791}),792)793})794.collect();795796MaterialBindGroupBindlessAllocator {797label,798slabs: vec![],799bind_group_layout,800bindless_descriptor,801fallback_buffers,802slab_capacity: slab_capacity803.expect("Non-bindless materials should use the non-bindless allocator")804.resolve(),805}806}807808/// Allocates the resources for a single material into a slab and returns809/// the resulting ID.810///811/// The returned [`MaterialBindingId`] can later be used to fetch the slab812/// that was used.813///814/// This function can't fail. If all slabs are full, then a new slab is815/// created, and the material is allocated into it.816fn allocate_unprepared(817&mut self,818mut unprepared_bind_group: UnpreparedBindGroup,819) -> MaterialBindingId {820for (slab_index, slab) in self.slabs.iter_mut().enumerate() {821trace!("Trying to allocate in slab {}", slab_index);822match slab.try_allocate(unprepared_bind_group, self.slab_capacity) {823Ok(slot) => {824return MaterialBindingId {825group: MaterialBindGroupIndex(slab_index as u32),826slot,827};828}829Err(bind_group) => unprepared_bind_group = bind_group,830}831}832833let group = MaterialBindGroupIndex(self.slabs.len() as u32);834self.slabs835.push(MaterialBindlessSlab::new(&self.bindless_descriptor));836837// Allocate into the newly-pushed slab.838let Ok(slot) = self839.slabs840.last_mut()841.expect("We just pushed a slab")842.try_allocate(unprepared_bind_group, self.slab_capacity)843else {844panic!("An allocation into an empty slab should always succeed")845};846847MaterialBindingId { group, slot }848}849850/// Deallocates the material with the given binding ID.851///852/// Any resources that are no longer referenced are removed from the slab.853fn free(&mut self, material_binding_id: MaterialBindingId) {854self.slabs855.get_mut(material_binding_id.group.0 as usize)856.expect("Slab should exist")857.free(material_binding_id.slot, &self.bindless_descriptor);858}859860/// Returns the slab with the given bind group index.861///862/// A [`MaterialBindGroupIndex`] can be fetched from a863/// [`MaterialBindingId`].864fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab> {865self.slabs.get(group.0 as usize)866}867868/// Recreates any bind groups corresponding to slabs that have been modified869/// since last calling870/// [`MaterialBindGroupBindlessAllocator::prepare_bind_groups`].871fn prepare_bind_groups(872&mut self,873render_device: &RenderDevice,874pipeline_cache: &PipelineCache,875fallback_bindless_resources: &FallbackBindlessResources,876fallback_image: &FallbackImage,877) {878for slab in &mut self.slabs {879slab.prepare(880render_device,881pipeline_cache,882self.label,883&self.bind_group_layout,884fallback_bindless_resources,885&self.fallback_buffers,886fallback_image,887&self.bindless_descriptor,888self.slab_capacity,889);890}891}892893/// Writes any buffers that we're managing to the GPU.894///895/// Currently, this only consists of the bindless index tables.896fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {897for slab in &mut self.slabs {898slab.write_buffer(render_device, render_queue);899}900}901}902903impl MaterialBindlessSlab {904/// Attempts to allocate the given unprepared bind group in this slab.905///906/// If the allocation succeeds, this method returns the slot that the907/// allocation was placed in. If the allocation fails because the slab was908/// full, this method returns the unprepared bind group back to the caller909/// so that it can try to allocate again.910fn try_allocate(911&mut self,912unprepared_bind_group: UnpreparedBindGroup,913slot_capacity: u32,914) -> Result<MaterialBindGroupSlot, UnpreparedBindGroup> {915// Locate pre-existing resources, and determine how many free slots we need.916let Some(allocation_candidate) = self.check_allocation(&unprepared_bind_group) else {917return Err(unprepared_bind_group);918};919920// Check to see if we have enough free space.921//922// As a special case, note that if *nothing* is allocated in this slab,923// then we always allow a material to be placed in it, regardless of the924// number of bindings the material has. This is so that, if the925// platform's maximum bindless count is set too low to hold even a926// single material, we can still place each material into a separate927// slab instead of failing outright.928if self.allocated_resource_count > 0929&& self.allocated_resource_count + allocation_candidate.needed_free_slots930> slot_capacity931{932trace!("Slab is full, can't allocate");933return Err(unprepared_bind_group);934}935936// OK, we can allocate in this slab. Assign a slot ID.937let slot = self938.free_slots939.pop()940.unwrap_or(MaterialBindGroupSlot(self.live_allocation_count));941942// Bump the live allocation count.943self.live_allocation_count += 1;944945// Insert the resources into the binding arrays.946let allocated_resource_slots =947self.insert_resources(unprepared_bind_group.bindings, allocation_candidate);948949// Serialize the allocated resource slots.950for bindless_index_table in &mut self.bindless_index_tables {951bindless_index_table.set(slot, &allocated_resource_slots);952}953954// Invalidate the cached bind group.955self.bind_group = None;956957Ok(slot)958}959960/// Gathers the information needed to determine whether the given unprepared961/// bind group can be allocated in this slab.962fn check_allocation(963&self,964unprepared_bind_group: &UnpreparedBindGroup,965) -> Option<BindlessAllocationCandidate> {966let mut allocation_candidate = BindlessAllocationCandidate {967pre_existing_resources: HashMap::default(),968needed_free_slots: 0,969};970971for &(bindless_index, ref owned_binding_resource) in unprepared_bind_group.bindings.iter() {972let bindless_index = BindlessIndex(bindless_index);973match *owned_binding_resource {974OwnedBindingResource::Buffer(ref buffer) => {975let Some(binding_array) = self.buffers.get(&bindless_index) else {976error!(977"Binding array wasn't present for buffer at index {:?}",978bindless_index979);980return None;981};982match binding_array.find(BindingResourceId::Buffer(buffer.id())) {983Some(slot) => {984allocation_candidate985.pre_existing_resources986.insert(bindless_index, slot);987}988None => allocation_candidate.needed_free_slots += 1,989}990}991992OwnedBindingResource::Data(_) => {993// The size of a data buffer is unlimited.994}995996OwnedBindingResource::TextureView(texture_view_dimension, ref texture_view) => {997let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);998match self999.textures1000.get(&bindless_resource_type)1001.expect("Missing binding array for texture")1002.find(BindingResourceId::TextureView(1003texture_view_dimension,1004texture_view.id(),1005)) {1006Some(slot) => {1007allocation_candidate1008.pre_existing_resources1009.insert(bindless_index, slot);1010}1011None => {1012allocation_candidate.needed_free_slots += 1;1013}1014}1015}10161017OwnedBindingResource::Sampler(sampler_binding_type, ref sampler) => {1018let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);1019match self1020.samplers1021.get(&bindless_resource_type)1022.expect("Missing binding array for sampler")1023.find(BindingResourceId::Sampler(sampler.id()))1024{1025Some(slot) => {1026allocation_candidate1027.pre_existing_resources1028.insert(bindless_index, slot);1029}1030None => {1031allocation_candidate.needed_free_slots += 1;1032}1033}1034}1035}1036}10371038Some(allocation_candidate)1039}10401041/// Inserts the given [`BindingResources`] into this slab.1042///1043/// Returns a table that maps the bindless index of each resource to its1044/// slot in its binding array.1045fn insert_resources(1046&mut self,1047mut binding_resources: BindingResources,1048allocation_candidate: BindlessAllocationCandidate,1049) -> HashMap<BindlessIndex, u32> {1050let mut allocated_resource_slots = HashMap::default();10511052for (bindless_index, owned_binding_resource) in binding_resources.drain(..) {1053let bindless_index = BindlessIndex(bindless_index);10541055let pre_existing_slot = allocation_candidate1056.pre_existing_resources1057.get(&bindless_index);10581059// Otherwise, we need to insert it anew.1060let binding_resource_id = BindingResourceId::from(&owned_binding_resource);1061let increment_allocated_resource_count = match owned_binding_resource {1062OwnedBindingResource::Buffer(buffer) => {1063let slot = self1064.buffers1065.get_mut(&bindless_index)1066.expect("Buffer binding array should exist")1067.insert(binding_resource_id, buffer);1068allocated_resource_slots.insert(bindless_index, slot);10691070if let Some(pre_existing_slot) = pre_existing_slot {1071assert_eq!(*pre_existing_slot, slot);10721073false1074} else {1075true1076}1077}1078OwnedBindingResource::Data(data) => {1079if pre_existing_slot.is_some() {1080panic!("Data buffers can't be deduplicated")1081}10821083let slot = self1084.data_buffers1085.get_mut(&bindless_index)1086.expect("Data buffer binding array should exist")1087.insert(&data);1088allocated_resource_slots.insert(bindless_index, slot);1089false1090}1091OwnedBindingResource::TextureView(texture_view_dimension, texture_view) => {1092let bindless_resource_type = BindlessResourceType::from(texture_view_dimension);1093let slot = self1094.textures1095.get_mut(&bindless_resource_type)1096.expect("Texture array should exist")1097.insert(binding_resource_id, texture_view);1098allocated_resource_slots.insert(bindless_index, slot);10991100if let Some(pre_existing_slot) = pre_existing_slot {1101assert_eq!(*pre_existing_slot, slot);11021103false1104} else {1105true1106}1107}1108OwnedBindingResource::Sampler(sampler_binding_type, sampler) => {1109let bindless_resource_type = BindlessResourceType::from(sampler_binding_type);1110let slot = self1111.samplers1112.get_mut(&bindless_resource_type)1113.expect("Sampler should exist")1114.insert(binding_resource_id, sampler);1115allocated_resource_slots.insert(bindless_index, slot);11161117if let Some(pre_existing_slot) = pre_existing_slot {1118assert_eq!(*pre_existing_slot, slot);11191120false1121} else {1122true1123}1124}1125};11261127// Bump the allocated resource count.1128if increment_allocated_resource_count {1129self.allocated_resource_count += 1;1130}1131}11321133allocated_resource_slots1134}11351136/// Removes the material allocated in the given slot, with the given1137/// descriptor, from this slab.1138fn free(&mut self, slot: MaterialBindGroupSlot, bindless_descriptor: &BindlessDescriptor) {1139// Loop through each binding.1140for (bindless_index, bindless_resource_type) in1141bindless_descriptor.resources.iter().enumerate()1142{1143let bindless_index = BindlessIndex::from(bindless_index as u32);1144let Some(bindless_index_table) = self.get_bindless_index_table(bindless_index) else {1145continue;1146};1147let Some(bindless_binding) = bindless_index_table.get_binding(slot, bindless_index)1148else {1149continue;1150};11511152// Free the binding. If the resource in question was anything other1153// than a data buffer, then it has a reference count and1154// consequently we need to decrement it.1155let decrement_allocated_resource_count = match *bindless_resource_type {1156BindlessResourceType::None => false,1157BindlessResourceType::Buffer => self1158.buffers1159.get_mut(&bindless_index)1160.expect("Buffer should exist with that bindless index")1161.remove(bindless_binding),1162BindlessResourceType::DataBuffer => {1163self.data_buffers1164.get_mut(&bindless_index)1165.expect("Data buffer should exist with that bindless index")1166.remove(bindless_binding);1167false1168}1169BindlessResourceType::SamplerFiltering1170| BindlessResourceType::SamplerNonFiltering1171| BindlessResourceType::SamplerComparison => self1172.samplers1173.get_mut(bindless_resource_type)1174.expect("Sampler array should exist")1175.remove(bindless_binding),1176BindlessResourceType::Texture1d1177| BindlessResourceType::Texture2d1178| BindlessResourceType::Texture2dArray1179| BindlessResourceType::Texture3d1180| BindlessResourceType::TextureCube1181| BindlessResourceType::TextureCubeArray => self1182.textures1183.get_mut(bindless_resource_type)1184.expect("Texture array should exist")1185.remove(bindless_binding),1186};11871188// If the slot is now free, decrement the allocated resource1189// count.1190if decrement_allocated_resource_count {1191self.allocated_resource_count -= 1;1192}1193}11941195// Invalidate the cached bind group.1196self.bind_group = None;11971198// Release the slot ID.1199self.free_slots.push(slot);1200self.live_allocation_count -= 1;1201}12021203/// Recreates the bind group and bindless index table buffer if necessary.1204fn prepare(1205&mut self,1206render_device: &RenderDevice,1207pipeline_cache: &PipelineCache,1208label: &'static str,1209bind_group_layout: &BindGroupLayoutDescriptor,1210fallback_bindless_resources: &FallbackBindlessResources,1211fallback_buffers: &HashMap<BindlessIndex, Buffer>,1212fallback_image: &FallbackImage,1213bindless_descriptor: &BindlessDescriptor,1214slab_capacity: u32,1215) {1216// Create the bindless index table buffers if needed.1217for bindless_index_table in &mut self.bindless_index_tables {1218bindless_index_table.buffer.prepare(render_device);1219}12201221// Create any data buffers we were managing if necessary.1222for data_buffer in self.data_buffers.values_mut() {1223data_buffer.buffer.prepare(render_device);1224}12251226// Create the bind group if needed.1227self.prepare_bind_group(1228render_device,1229pipeline_cache,1230label,1231bind_group_layout,1232fallback_bindless_resources,1233fallback_buffers,1234fallback_image,1235bindless_descriptor,1236slab_capacity,1237);1238}12391240/// Recreates the bind group if this slab has been changed since the last1241/// time we created it.1242fn prepare_bind_group(1243&mut self,1244render_device: &RenderDevice,1245pipeline_cache: &PipelineCache,1246label: &'static str,1247bind_group_layout: &BindGroupLayoutDescriptor,1248fallback_bindless_resources: &FallbackBindlessResources,1249fallback_buffers: &HashMap<BindlessIndex, Buffer>,1250fallback_image: &FallbackImage,1251bindless_descriptor: &BindlessDescriptor,1252slab_capacity: u32,1253) {1254// If the bind group is clean, then do nothing.1255if self.bind_group.is_some() {1256return;1257}12581259// Determine whether we need to pad out our binding arrays with dummy1260// resources.1261let required_binding_array_size = if render_device1262.features()1263.contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)1264{1265None1266} else {1267Some(slab_capacity)1268};12691270let binding_resource_arrays = self.create_binding_resource_arrays(1271fallback_bindless_resources,1272fallback_buffers,1273fallback_image,1274bindless_descriptor,1275required_binding_array_size,1276);12771278let mut bind_group_entries: Vec<_> = self1279.bindless_index_tables1280.iter()1281.map(|bindless_index_table| bindless_index_table.bind_group_entry())1282.collect();12831284for &(&binding, ref binding_resource_array) in binding_resource_arrays.iter() {1285bind_group_entries.push(BindGroupEntry {1286binding,1287resource: match *binding_resource_array {1288BindingResourceArray::Buffers(ref buffer_bindings) => {1289BindingResource::BufferArray(&buffer_bindings[..])1290}1291BindingResourceArray::TextureViews(ref texture_views) => {1292BindingResource::TextureViewArray(&texture_views[..])1293}1294BindingResourceArray::Samplers(ref samplers) => {1295BindingResource::SamplerArray(&samplers[..])1296}1297},1298});1299}13001301// Create bind group entries for any data buffers we're managing.1302for data_buffer in self.data_buffers.values() {1303bind_group_entries.push(BindGroupEntry {1304binding: *data_buffer.binding_number,1305resource: data_buffer1306.buffer1307.buffer()1308.expect("Backing data buffer must have been uploaded by now")1309.as_entire_binding(),1310});1311}13121313self.bind_group = Some(render_device.create_bind_group(1314Some(label),1315&pipeline_cache.get_bind_group_layout(bind_group_layout),1316&bind_group_entries,1317));1318}13191320/// Writes any buffers that we're managing to the GPU.1321///1322/// Currently, this consists of the bindless index table plus any data1323/// buffers we're managing.1324fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {1325for bindless_index_table in &mut self.bindless_index_tables {1326bindless_index_table1327.buffer1328.write(render_device, render_queue);1329}13301331for data_buffer in self.data_buffers.values_mut() {1332data_buffer.buffer.write(render_device, render_queue);1333}1334}13351336/// Converts our binding arrays into binding resource arrays suitable for1337/// passing to `wgpu`.1338fn create_binding_resource_arrays<'a>(1339&'a self,1340fallback_bindless_resources: &'a FallbackBindlessResources,1341fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,1342fallback_image: &'a FallbackImage,1343bindless_descriptor: &'a BindlessDescriptor,1344required_binding_array_size: Option<u32>,1345) -> Vec<(&'a u32, BindingResourceArray<'a>)> {1346let mut binding_resource_arrays = vec![];13471348// Build sampler bindings.1349self.create_sampler_binding_resource_arrays(1350&mut binding_resource_arrays,1351fallback_bindless_resources,1352required_binding_array_size,1353);13541355// Build texture bindings.1356self.create_texture_binding_resource_arrays(1357&mut binding_resource_arrays,1358fallback_image,1359required_binding_array_size,1360);13611362// Build buffer bindings.1363self.create_buffer_binding_resource_arrays(1364&mut binding_resource_arrays,1365fallback_buffers,1366bindless_descriptor,1367required_binding_array_size,1368);13691370binding_resource_arrays1371}13721373/// Accumulates sampler binding arrays into binding resource arrays suitable1374/// for passing to `wgpu`.1375fn create_sampler_binding_resource_arrays<'a, 'b>(1376&'a self,1377binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1378fallback_bindless_resources: &'a FallbackBindlessResources,1379required_binding_array_size: Option<u32>,1380) {1381// We have one binding resource array per sampler type.1382for (bindless_resource_type, fallback_sampler) in [1383(1384BindlessResourceType::SamplerFiltering,1385&fallback_bindless_resources.filtering_sampler,1386),1387(1388BindlessResourceType::SamplerNonFiltering,1389&fallback_bindless_resources.non_filtering_sampler,1390),1391(1392BindlessResourceType::SamplerComparison,1393&fallback_bindless_resources.comparison_sampler,1394),1395] {1396let mut sampler_bindings = vec![];13971398match self.samplers.get(&bindless_resource_type) {1399Some(sampler_bindless_binding_array) => {1400for maybe_bindless_binding in sampler_bindless_binding_array.bindings.iter() {1401match *maybe_bindless_binding {1402Some(ref bindless_binding) => {1403sampler_bindings.push(&*bindless_binding.resource);1404}1405None => sampler_bindings.push(&**fallback_sampler),1406}1407}1408}14091410None => {1411// Fill with a single fallback sampler.1412sampler_bindings.push(&**fallback_sampler);1413}1414}14151416if let Some(required_binding_array_size) = required_binding_array_size {1417sampler_bindings.extend(iter::repeat_n(1418&**fallback_sampler,1419required_binding_array_size as usize - sampler_bindings.len(),1420));1421}14221423let binding_number = bindless_resource_type1424.binding_number()1425.expect("Sampler bindless resource type must have a binding number");14261427binding_resource_arrays.push((1428&**binding_number,1429BindingResourceArray::Samplers(sampler_bindings),1430));1431}1432}14331434/// Accumulates texture binding arrays into binding resource arrays suitable1435/// for passing to `wgpu`.1436fn create_texture_binding_resource_arrays<'a, 'b>(1437&'a self,1438binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1439fallback_image: &'a FallbackImage,1440required_binding_array_size: Option<u32>,1441) {1442for (bindless_resource_type, fallback_image) in [1443(BindlessResourceType::Texture1d, &fallback_image.d1),1444(BindlessResourceType::Texture2d, &fallback_image.d2),1445(1446BindlessResourceType::Texture2dArray,1447&fallback_image.d2_array,1448),1449(BindlessResourceType::Texture3d, &fallback_image.d3),1450(BindlessResourceType::TextureCube, &fallback_image.cube),1451(1452BindlessResourceType::TextureCubeArray,1453&fallback_image.cube_array,1454),1455] {1456let mut texture_bindings = vec![];14571458let binding_number = bindless_resource_type1459.binding_number()1460.expect("Texture bindless resource type must have a binding number");14611462match self.textures.get(&bindless_resource_type) {1463Some(texture_bindless_binding_array) => {1464for maybe_bindless_binding in texture_bindless_binding_array.bindings.iter() {1465match *maybe_bindless_binding {1466Some(ref bindless_binding) => {1467texture_bindings.push(&*bindless_binding.resource);1468}1469None => texture_bindings.push(&*fallback_image.texture_view),1470}1471}1472}14731474None => {1475// Fill with a single fallback image.1476texture_bindings.push(&*fallback_image.texture_view);1477}1478}14791480if let Some(required_binding_array_size) = required_binding_array_size {1481texture_bindings.extend(iter::repeat_n(1482&*fallback_image.texture_view,1483required_binding_array_size as usize - texture_bindings.len(),1484));1485}14861487binding_resource_arrays.push((1488binding_number,1489BindingResourceArray::TextureViews(texture_bindings),1490));1491}1492}14931494/// Accumulates buffer binding arrays into binding resource arrays suitable1495/// for `wgpu`.1496fn create_buffer_binding_resource_arrays<'a, 'b>(1497&'a self,1498binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>,1499fallback_buffers: &'a HashMap<BindlessIndex, Buffer>,1500bindless_descriptor: &'a BindlessDescriptor,1501required_binding_array_size: Option<u32>,1502) {1503for bindless_buffer_descriptor in bindless_descriptor.buffers.iter() {1504let Some(buffer_bindless_binding_array) =1505self.buffers.get(&bindless_buffer_descriptor.bindless_index)1506else {1507// This is OK, because index buffers are present in1508// `BindlessDescriptor::buffers` but not in1509// `BindlessDescriptor::resources`.1510continue;1511};15121513let fallback_buffer = fallback_buffers1514.get(&bindless_buffer_descriptor.bindless_index)1515.expect("Fallback buffer should exist");15161517let mut buffer_bindings: Vec<_> = buffer_bindless_binding_array1518.bindings1519.iter()1520.map(|maybe_bindless_binding| {1521let buffer = match *maybe_bindless_binding {1522None => fallback_buffer,1523Some(ref bindless_binding) => &bindless_binding.resource,1524};1525BufferBinding {1526buffer,1527offset: 0,1528size: None,1529}1530})1531.collect();15321533if let Some(required_binding_array_size) = required_binding_array_size {1534buffer_bindings.extend(iter::repeat_n(1535BufferBinding {1536buffer: fallback_buffer,1537offset: 0,1538size: None,1539},1540required_binding_array_size as usize - buffer_bindings.len(),1541));1542}15431544binding_resource_arrays.push((1545&*buffer_bindless_binding_array.binding_number,1546BindingResourceArray::Buffers(buffer_bindings),1547));1548}1549}15501551/// Returns the [`BindGroup`] corresponding to this slab, if it's been1552/// prepared.1553fn bind_group(&self) -> Option<&BindGroup> {1554self.bind_group.as_ref()1555}15561557/// Returns the bindless index table containing the given bindless index.1558fn get_bindless_index_table(1559&self,1560bindless_index: BindlessIndex,1561) -> Option<&MaterialBindlessIndexTable> {1562let table_index = self1563.bindless_index_tables1564.binary_search_by(|bindless_index_table| {1565if bindless_index < bindless_index_table.index_range.start {1566Ordering::Less1567} else if bindless_index >= bindless_index_table.index_range.end {1568Ordering::Greater1569} else {1570Ordering::Equal1571}1572})1573.ok()?;1574self.bindless_index_tables.get(table_index)1575}1576}15771578impl<R> MaterialBindlessBindingArray<R>1579where1580R: GetBindingResourceId,1581{1582/// Creates a new [`MaterialBindlessBindingArray`] with the given binding1583/// number, managing resources of the given type.1584fn new(1585binding_number: BindingNumber,1586resource_type: BindlessResourceType,1587) -> MaterialBindlessBindingArray<R> {1588MaterialBindlessBindingArray {1589binding_number,1590bindings: vec![],1591resource_type,1592resource_to_slot: HashMap::default(),1593free_slots: vec![],1594len: 0,1595}1596}15971598/// Returns the slot corresponding to the given resource, if that resource1599/// is located in this binding array.1600///1601/// If the resource isn't in this binding array, this method returns `None`.1602fn find(&self, binding_resource_id: BindingResourceId) -> Option<u32> {1603self.resource_to_slot.get(&binding_resource_id).copied()1604}16051606/// Inserts a bindless resource into a binding array and returns the index1607/// of the slot it was inserted into.1608fn insert(&mut self, binding_resource_id: BindingResourceId, resource: R) -> u32 {1609match self.resource_to_slot.entry(binding_resource_id) {1610bevy_platform::collections::hash_map::Entry::Occupied(o) => {1611let slot = *o.get();16121613self.bindings[slot as usize]1614.as_mut()1615.expect("A slot in the resource_to_slot map should have a value")1616.ref_count += 1;16171618slot1619}1620bevy_platform::collections::hash_map::Entry::Vacant(v) => {1621let slot = self.free_slots.pop().unwrap_or(self.len);1622v.insert(slot);16231624if self.bindings.len() < slot as usize + 1 {1625self.bindings.resize_with(slot as usize + 1, || None);1626}1627self.bindings[slot as usize] = Some(MaterialBindlessBinding::new(resource));16281629self.len += 1;1630slot1631}1632}1633}16341635/// Removes a reference to an object from the slot.1636///1637/// If the reference count dropped to 0 and the object was freed, this1638/// method returns true. If the object was still referenced after removing1639/// it, returns false.1640fn remove(&mut self, slot: u32) -> bool {1641let maybe_binding = &mut self.bindings[slot as usize];1642let binding = maybe_binding1643.as_mut()1644.expect("Attempted to free an already-freed binding");16451646binding.ref_count -= 1;1647if binding.ref_count != 0 {1648return false;1649}16501651let binding_resource_id = binding.resource.binding_resource_id(self.resource_type);1652self.resource_to_slot.remove(&binding_resource_id);16531654*maybe_binding = None;1655self.free_slots.push(slot);1656self.len -= 1;1657true1658}1659}16601661impl<R> MaterialBindlessBinding<R>1662where1663R: GetBindingResourceId,1664{1665/// Creates a new [`MaterialBindlessBinding`] for a freshly-added resource.1666///1667/// The reference count is initialized to 1.1668fn new(resource: R) -> MaterialBindlessBinding<R> {1669MaterialBindlessBinding {1670resource,1671ref_count: 1,1672}1673}1674}16751676/// Returns true if the material will *actually* use bindless resources or false1677/// if it won't.1678///1679/// This takes the platform support (or lack thereof) for bindless resources1680/// into account.1681pub fn material_uses_bindless_resources<M>(render_device: &RenderDevice) -> bool1682where1683M: Material,1684{1685M::bindless_slot_count().is_some_and(|bindless_slot_count| {1686M::bindless_supported(render_device) && bindless_slot_count.resolve() > 11687})1688}16891690impl MaterialBindlessSlab {1691/// Creates a new [`MaterialBindlessSlab`] for a material with the given1692/// bindless descriptor.1693///1694/// We use this when no existing slab could hold a material to be allocated.1695fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab {1696let mut buffers = HashMap::default();1697let mut samplers = HashMap::default();1698let mut textures = HashMap::default();1699let mut data_buffers = HashMap::default();17001701for (bindless_index, bindless_resource_type) in1702bindless_descriptor.resources.iter().enumerate()1703{1704let bindless_index = BindlessIndex(bindless_index as u32);1705match *bindless_resource_type {1706BindlessResourceType::None => {}1707BindlessResourceType::Buffer => {1708let binding_number = bindless_descriptor1709.buffers1710.iter()1711.find(|bindless_buffer_descriptor| {1712bindless_buffer_descriptor.bindless_index == bindless_index1713})1714.expect(1715"Bindless buffer descriptor matching that bindless index should be \1716present",1717)1718.binding_number;1719buffers.insert(1720bindless_index,1721MaterialBindlessBindingArray::new(binding_number, *bindless_resource_type),1722);1723}1724BindlessResourceType::DataBuffer => {1725// Copy the data in.1726let buffer_descriptor = bindless_descriptor1727.buffers1728.iter()1729.find(|bindless_buffer_descriptor| {1730bindless_buffer_descriptor.bindless_index == bindless_index1731})1732.expect(1733"Bindless buffer descriptor matching that bindless index should be \1734present",1735);1736data_buffers.insert(1737bindless_index,1738MaterialDataBuffer::new(1739buffer_descriptor.binding_number,1740buffer_descriptor1741.size1742.expect("Data buffers should have a size")1743as u32,1744),1745);1746}1747BindlessResourceType::SamplerFiltering1748| BindlessResourceType::SamplerNonFiltering1749| BindlessResourceType::SamplerComparison => {1750samplers.insert(1751*bindless_resource_type,1752MaterialBindlessBindingArray::new(1753*bindless_resource_type.binding_number().unwrap(),1754*bindless_resource_type,1755),1756);1757}1758BindlessResourceType::Texture1d1759| BindlessResourceType::Texture2d1760| BindlessResourceType::Texture2dArray1761| BindlessResourceType::Texture3d1762| BindlessResourceType::TextureCube1763| BindlessResourceType::TextureCubeArray => {1764textures.insert(1765*bindless_resource_type,1766MaterialBindlessBindingArray::new(1767*bindless_resource_type.binding_number().unwrap(),1768*bindless_resource_type,1769),1770);1771}1772}1773}17741775let bindless_index_tables = bindless_descriptor1776.index_tables1777.iter()1778.map(MaterialBindlessIndexTable::new)1779.collect();17801781MaterialBindlessSlab {1782bind_group: None,1783bindless_index_tables,1784samplers,1785textures,1786buffers,1787data_buffers,1788free_slots: vec![],1789live_allocation_count: 0,1790allocated_resource_count: 0,1791}1792}1793}17941795pub fn init_fallback_bindless_resources(mut commands: Commands, render_device: Res<RenderDevice>) {1796commands.insert_resource(FallbackBindlessResources {1797filtering_sampler: render_device.create_sampler(&SamplerDescriptor {1798label: Some("fallback filtering sampler"),1799..default()1800}),1801non_filtering_sampler: render_device.create_sampler(&SamplerDescriptor {1802label: Some("fallback non-filtering sampler"),1803mag_filter: FilterMode::Nearest,1804min_filter: FilterMode::Nearest,1805mipmap_filter: MipmapFilterMode::Nearest,1806..default()1807}),1808comparison_sampler: render_device.create_sampler(&SamplerDescriptor {1809label: Some("fallback comparison sampler"),1810compare: Some(CompareFunction::Always),1811..default()1812}),1813});1814}18151816impl MaterialBindGroupNonBindlessAllocator {1817/// Creates a new [`MaterialBindGroupNonBindlessAllocator`] managing the1818/// bind groups for a single non-bindless material.1819fn new(label: &'static str) -> MaterialBindGroupNonBindlessAllocator {1820MaterialBindGroupNonBindlessAllocator {1821label,1822bind_groups: vec![],1823to_prepare: HashSet::default(),1824free_indices: vec![],1825}1826}18271828/// Inserts a bind group, either unprepared or prepared, into this allocator1829/// and returns a [`MaterialBindingId`].1830///1831/// The returned [`MaterialBindingId`] can later be used to fetch the bind1832/// group.1833fn allocate(&mut self, bind_group: MaterialNonBindlessAllocatedBindGroup) -> MaterialBindingId {1834let group_id = self1835.free_indices1836.pop()1837.unwrap_or(MaterialBindGroupIndex(self.bind_groups.len() as u32));1838if self.bind_groups.len() < *group_id as usize + 1 {1839self.bind_groups1840.resize_with(*group_id as usize + 1, || None);1841}18421843if matches!(1844bind_group,1845MaterialNonBindlessAllocatedBindGroup::Unprepared { .. }1846) {1847self.to_prepare.insert(group_id);1848}18491850self.bind_groups[*group_id as usize] = Some(bind_group);18511852MaterialBindingId {1853group: group_id,1854slot: default(),1855}1856}18571858/// Inserts an unprepared bind group into this allocator and returns a1859/// [`MaterialBindingId`].1860fn allocate_unprepared(1861&mut self,1862unprepared_bind_group: UnpreparedBindGroup,1863bind_group_layout: BindGroupLayoutDescriptor,1864) -> MaterialBindingId {1865self.allocate(MaterialNonBindlessAllocatedBindGroup::Unprepared {1866bind_group: unprepared_bind_group,1867layout: bind_group_layout,1868})1869}18701871/// Inserts an prepared bind group into this allocator and returns a1872/// [`MaterialBindingId`].1873fn allocate_prepared(&mut self, prepared_bind_group: PreparedBindGroup) -> MaterialBindingId {1874self.allocate(MaterialNonBindlessAllocatedBindGroup::Prepared {1875bind_group: prepared_bind_group,1876uniform_buffers: vec![],1877})1878}18791880/// Deallocates the bind group with the given binding ID.1881fn free(&mut self, binding_id: MaterialBindingId) {1882debug_assert_eq!(binding_id.slot, MaterialBindGroupSlot(0));1883debug_assert!(self.bind_groups[*binding_id.group as usize].is_some());1884self.bind_groups[*binding_id.group as usize] = None;1885self.to_prepare.remove(&binding_id.group);1886self.free_indices.push(binding_id.group);1887}18881889/// Returns a wrapper around the bind group with the given index.1890fn get(&self, group: MaterialBindGroupIndex) -> Option<MaterialNonBindlessSlab<'_>> {1891self.bind_groups[group.0 as usize]1892.as_ref()1893.map(|bind_group| match bind_group {1894MaterialNonBindlessAllocatedBindGroup::Prepared { bind_group, .. } => {1895MaterialNonBindlessSlab::Prepared(bind_group)1896}1897MaterialNonBindlessAllocatedBindGroup::Unprepared { .. } => {1898MaterialNonBindlessSlab::Unprepared1899}1900})1901}19021903/// Prepares any as-yet unprepared bind groups that this allocator is1904/// managing.1905///1906/// Unprepared bind groups can be added to this allocator with1907/// [`Self::allocate_unprepared`]. Such bind groups will defer being1908/// prepared until the next time this method is called.1909fn prepare_bind_groups(1910&mut self,1911render_device: &RenderDevice,1912pipeline_cache: &PipelineCache,1913) {1914for bind_group_index in mem::take(&mut self.to_prepare) {1915let Some(MaterialNonBindlessAllocatedBindGroup::Unprepared {1916bind_group: unprepared_bind_group,1917layout: bind_group_layout,1918}) = mem::take(&mut self.bind_groups[*bind_group_index as usize])1919else {1920panic!("Allocation didn't exist or was already prepared");1921};19221923// Pack any `Data` into uniform buffers.1924let mut uniform_buffers = vec![];1925for (index, binding) in unprepared_bind_group.bindings.iter() {1926let OwnedBindingResource::Data(ref owned_data) = *binding else {1927continue;1928};1929let label = format!("material uniform data {}", *index);1930let uniform_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {1931label: Some(&label),1932contents: &owned_data.0,1933usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,1934});1935uniform_buffers.push(uniform_buffer);1936}19371938// Create bind group entries.1939let mut bind_group_entries = vec![];1940let mut uniform_buffers_iter = uniform_buffers.iter();1941for (index, binding) in unprepared_bind_group.bindings.iter() {1942match *binding {1943OwnedBindingResource::Data(_) => {1944bind_group_entries.push(BindGroupEntry {1945binding: *index,1946resource: uniform_buffers_iter1947.next()1948.expect("We should have created uniform buffers for each `Data`")1949.as_entire_binding(),1950});1951}1952_ => bind_group_entries.push(BindGroupEntry {1953binding: *index,1954resource: binding.get_binding(),1955}),1956}1957}19581959// Create the bind group.1960let bind_group = render_device.create_bind_group(1961self.label,1962&pipeline_cache.get_bind_group_layout(&bind_group_layout),1963&bind_group_entries,1964);19651966self.bind_groups[*bind_group_index as usize] =1967Some(MaterialNonBindlessAllocatedBindGroup::Prepared {1968bind_group: PreparedBindGroup {1969bindings: unprepared_bind_group.bindings,1970bind_group,1971},1972uniform_buffers,1973});1974}1975}1976}19771978impl<'a> MaterialSlab<'a> {1979/// Returns the [`BindGroup`] corresponding to this slab, if it's been1980/// prepared.1981///1982/// You can prepare bind groups by calling1983/// [`MaterialBindGroupAllocator::prepare_bind_groups`]. If the bind group1984/// isn't ready, this method returns `None`.1985pub fn bind_group(&self) -> Option<&'a BindGroup> {1986match self.0 {1987MaterialSlabImpl::Bindless(material_bindless_slab) => {1988material_bindless_slab.bind_group()1989}1990MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared(1991prepared_bind_group,1992)) => Some(&prepared_bind_group.bind_group),1993MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared) => None,1994}1995}1996}19971998impl MaterialDataBuffer {1999/// Creates a new [`MaterialDataBuffer`] managing a buffer of elements of2000/// size `aligned_element_size` that will be bound to the given binding2001/// number.2002fn new(binding_number: BindingNumber, aligned_element_size: u32) -> MaterialDataBuffer {2003MaterialDataBuffer {2004binding_number,2005buffer: RetainedRawBufferVec::new(BufferUsages::STORAGE),2006aligned_element_size,2007free_slots: vec![],2008len: 0,2009}2010}20112012/// Allocates a slot for a new piece of data, copies the data into that2013/// slot, and returns the slot ID.2014///2015/// The size of the piece of data supplied to this method must equal the2016/// [`Self::aligned_element_size`] provided to [`MaterialDataBuffer::new`].2017fn insert(&mut self, data: &[u8]) -> u32 {2018// Make sure the data is of the right length.2019debug_assert_eq!(data.len(), self.aligned_element_size as usize);20202021// Grab a slot.2022let slot = self.free_slots.pop().unwrap_or(self.len);20232024// Calculate the range we're going to copy to.2025let start = slot as usize * self.aligned_element_size as usize;2026let end = (slot as usize + 1) * self.aligned_element_size as usize;20272028// Resize the buffer if necessary.2029if self.buffer.len() < end {2030self.buffer.reserve_internal(end);2031}2032while self.buffer.values().len() < end {2033self.buffer.push(0);2034}20352036// Copy in the data.2037self.buffer.values_mut()[start..end].copy_from_slice(data);20382039// Mark the buffer dirty, and finish up.2040self.len += 1;2041self.buffer.dirty = BufferDirtyState::NeedsReserve;2042slot2043}20442045/// Marks the given slot as free.2046fn remove(&mut self, slot: u32) {2047self.free_slots.push(slot);2048self.len -= 1;2049}2050}205120522053