bevy/crates/bevy_pbr/src/material_bind_groups.rs
Benjamin Brienen 0362abd4f4
Make extract_mesh_materials and MaterialBindGroupAllocator public (#16982)
# Objective

Fixes #16730

## Solution

Make the relevant functions public. (`MaterialBindGroupAllocator` itself
was already `pub`)
2024-12-30 05:57:11 +00:00

852 lines
30 KiB
Rust

//! Material bind group management for bindless resources.
//!
//! In bindless mode, Bevy's renderer groups materials into small bind groups.
//! This allocator manages each bind group, assigning slots to materials as
//! appropriate.
use crate::Material;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
system::Resource,
world::{FromWorld, World},
};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
render_resource::{
BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, BindingResource,
BindingType, Buffer, BufferBinding, BufferInitDescriptor, BufferUsages,
OwnedBindingResource, Sampler, SamplerDescriptor, TextureViewDimension,
UnpreparedBindGroup, WgpuSampler, WgpuTextureView,
},
renderer::RenderDevice,
settings::WgpuFeatures,
texture::FallbackImage,
};
use bevy_utils::{default, tracing::error, HashMap};
use core::{any, iter, marker::PhantomData, num::NonZero};
/// An object that creates and stores bind groups for a single material type.
///
/// This object collects bindless materials into groups as appropriate and
/// assigns slots as materials are created.
#[derive(Resource)]
pub struct MaterialBindGroupAllocator<M>
where
M: Material,
{
/// The data that the allocator keeps about each bind group.
bind_groups: Vec<MaterialBindGroup<M>>,
/// Stores IDs of material bind groups that have at least one slot
/// available.
free_bind_groups: Vec<u32>,
/// The layout for this bind group.
bind_group_layout: BindGroupLayout,
/// Dummy buffers that are assigned to unused slots.
fallback_buffers: MaterialFallbackBuffers,
/// Whether this material is actually using bindless resources.
///
/// This takes the availability of bindless resources on this platform into
/// account.
bindless_enabled: bool,
phantom: PhantomData<M>,
}
/// Information that the allocator keeps about each bind group.
pub enum MaterialBindGroup<M>
where
M: Material,
{
/// Information that the allocator keeps about each bind group with bindless
/// textures in use.
Bindless(MaterialBindlessBindGroup<M>),
/// Information that the allocator keeps about each bind group for which
/// bindless textures are not in use.
NonBindless(MaterialNonBindlessBindGroup<M>),
}
/// Information that the allocator keeps about each bind group with bindless
/// textures in use.
pub struct MaterialBindlessBindGroup<M>
where
M: Material,
{
/// The actual bind group.
pub bind_group: Option<BindGroup>,
/// The bind group data for each slot.
///
/// This is `None` if the slot is unallocated and `Some` if the slot is
/// full.
unprepared_bind_groups: Vec<Option<UnpreparedBindGroup<M::Data>>>,
/// A bitfield that contains a 0 if the slot is free or a 1 if the slot is
/// full.
///
/// We keep this value so that we can quickly find the next free slot when
/// we go to allocate.
used_slot_bitmap: u32,
}
/// Information that the allocator keeps about each bind group for which
/// bindless textures are not in use.
///
/// When a bindless texture isn't in use, bind groups and material instances are
/// in 1:1 correspondence, and therefore there's only a single slot for extra
/// material data here.
pub struct MaterialNonBindlessBindGroup<M>
where
M: Material,
{
/// The single allocation in a non-bindless bind group.
allocation: MaterialNonBindlessBindGroupAllocation<M>,
}
/// The single allocation in a non-bindless bind group.
enum MaterialNonBindlessBindGroupAllocation<M>
where
M: Material,
{
/// The allocation is free.
Unallocated,
/// The allocation has been allocated, but not yet initialized.
Allocated,
/// The allocation is full and contains both a bind group and extra data.
Initialized(BindGroup, M::Data),
}
/// Where the GPU data for a material is located.
///
/// In bindless mode, materials are gathered into bind groups, and the slot is
/// necessary to locate the material data within that group. If not in bindless
/// mode, bind groups and materials are in 1:1 correspondence, and the slot
/// index is always 0.
#[derive(Clone, Copy, Debug, Default, Reflect)]
pub struct MaterialBindingId {
/// The index of the bind group (slab) where the GPU data is located.
pub group: MaterialBindGroupIndex,
/// The slot within that bind group.
pub slot: MaterialBindGroupSlot,
}
/// The index of each material bind group.
///
/// In bindless mode, each bind group contains multiple materials. In
/// non-bindless mode, each bind group contains only one material.
#[derive(Clone, Copy, Debug, Default, Reflect, PartialEq, Deref, DerefMut)]
#[reflect(Default)]
pub struct MaterialBindGroupIndex(pub u32);
impl From<u32> for MaterialBindGroupIndex {
fn from(value: u32) -> Self {
MaterialBindGroupIndex(value)
}
}
/// The index of the slot containing material data within each material bind
/// group.
///
/// In bindless mode, this slot is needed to locate the material data in each
/// bind group, since multiple materials are packed into a single slab. In
/// non-bindless mode, this slot is always 0.
#[derive(Clone, Copy, Debug, Default, Reflect, Deref, DerefMut)]
#[reflect(Default)]
pub struct MaterialBindGroupSlot(pub u16);
impl From<u32> for MaterialBindGroupSlot {
fn from(value: u32) -> Self {
MaterialBindGroupSlot(value as u16)
}
}
impl From<MaterialBindGroupSlot> for u32 {
fn from(value: MaterialBindGroupSlot) -> Self {
value.0 as u32
}
}
/// A temporary data structure that contains references to bindless resources.
///
/// We need this because the `wgpu` bindless API takes a slice of references.
/// Thus we need to create intermediate vectors of bindless resources in order
/// to satisfy the lifetime requirements.
enum BindingResourceArray<'a> {
Buffers(Vec<BufferBinding<'a>>),
TextureViews(TextureViewDimension, Vec<&'a WgpuTextureView>),
Samplers(Vec<&'a WgpuSampler>),
}
/// Contains dummy resources that we use to pad out bindless arrays.
///
/// On DX12, every binding array slot must be filled, so we have to fill unused
/// slots.
#[derive(Resource)]
pub struct FallbackBindlessResources {
/// A dummy sampler that we fill unused slots in bindless sampler arrays
/// with.
fallback_sampler: Sampler,
}
struct MaterialFallbackBuffers(HashMap<u32, Buffer>);
/// The minimum byte size of each fallback buffer.
const MIN_BUFFER_SIZE: u64 = 16;
impl<M> MaterialBindGroupAllocator<M>
where
M: Material,
{
/// Creates or recreates any bind groups that were modified this frame.
pub fn prepare_bind_groups(
&mut self,
render_device: &RenderDevice,
fallback_image: &FallbackImage,
fallback_resources: &FallbackBindlessResources,
) {
for bind_group in &mut self.bind_groups {
bind_group.rebuild_bind_group_if_necessary(
render_device,
&self.bind_group_layout,
fallback_image,
fallback_resources,
&self.fallback_buffers,
);
}
}
/// Returns the bind group with the given index, if it exists.
#[inline]
pub fn get(&self, index: MaterialBindGroupIndex) -> Option<&MaterialBindGroup<M>> {
self.bind_groups.get(index.0 as usize)
}
/// Allocates a new binding slot and returns its ID.
pub fn allocate(&mut self) -> MaterialBindingId {
let group_index = self.free_bind_groups.pop().unwrap_or_else(|| {
let group_index = self.bind_groups.len() as u32;
self.bind_groups
.push(MaterialBindGroup::new(self.bindless_enabled));
group_index
});
let bind_group = &mut self.bind_groups[group_index as usize];
let slot_index = bind_group.allocate();
if !bind_group.is_full() {
self.free_bind_groups.push(group_index);
}
MaterialBindingId {
group: group_index.into(),
slot: slot_index,
}
}
/// Assigns an unprepared bind group to the group and slot specified in the
/// [`MaterialBindingId`].
pub fn init(
&mut self,
render_device: &RenderDevice,
material_binding_id: MaterialBindingId,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
) {
self.bind_groups[material_binding_id.group.0 as usize].init(
render_device,
&self.bind_group_layout,
material_binding_id.slot,
unprepared_bind_group,
);
}
/// Fills a slot directly with a custom bind group.
///
/// This is only a meaningful operation for non-bindless bind groups. It's
/// rarely used, but see the `texture_binding_array` example for an example
/// demonstrating how this feature might see use in practice.
pub fn init_custom(
&mut self,
material_binding_id: MaterialBindingId,
bind_group: BindGroup,
bind_group_data: M::Data,
) {
self.bind_groups[material_binding_id.group.0 as usize]
.init_custom(bind_group, bind_group_data);
}
/// Marks the slot corresponding to the given [`MaterialBindingId`] as free.
pub fn free(&mut self, material_binding_id: MaterialBindingId) {
let bind_group = &mut self.bind_groups[material_binding_id.group.0 as usize];
let was_full = bind_group.is_full();
bind_group.free(material_binding_id.slot);
// If the group that this material belonged to was full, it now contains
// at least one free slot, so add the group to the `free_bind_groups`
// list.
if was_full {
debug_assert!(!self.free_bind_groups.contains(&material_binding_id.group.0));
self.free_bind_groups.push(*material_binding_id.group);
}
}
}
impl<M> MaterialBindGroup<M>
where
M: Material,
{
/// Creates a new material bind group.
fn new(bindless: bool) -> MaterialBindGroup<M> {
if bindless {
MaterialBindGroup::Bindless(MaterialBindlessBindGroup::new())
} else {
MaterialBindGroup::NonBindless(MaterialNonBindlessBindGroup::new())
}
}
/// Allocates a new binding slot and returns its ID.
fn allocate(&mut self) -> MaterialBindGroupSlot {
match *self {
MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => {
material_bindless_bind_group.allocate()
}
MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => {
material_non_bindless_bind_group.allocate()
}
}
}
/// Assigns an unprepared bind group to the group and slot specified in the
/// [`MaterialBindingId`].
fn init(
&mut self,
render_device: &RenderDevice,
bind_group_layout: &BindGroupLayout,
slot: MaterialBindGroupSlot,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
) {
match *self {
MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => {
material_bindless_bind_group.init(
render_device,
bind_group_layout,
slot,
unprepared_bind_group,
);
}
MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => {
material_non_bindless_bind_group.init(
render_device,
bind_group_layout,
slot,
unprepared_bind_group,
);
}
}
}
/// Fills a slot directly with a custom bind group.
///
/// This is only a meaningful operation for non-bindless bind groups. It's
/// rarely used, but see the `texture_binding_array` example for an example
/// demonstrating how this feature might see use in practice.
fn init_custom(&mut self, bind_group: BindGroup, extra_data: M::Data) {
match *self {
MaterialBindGroup::Bindless(_) => {
error!("Custom bind groups aren't supported in bindless mode");
}
MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => {
material_non_bindless_bind_group.init_custom(bind_group, extra_data);
}
}
}
/// Marks the slot corresponding to the given [`MaterialBindGroupSlot`] as
/// free.
fn free(&mut self, material_bind_group_slot: MaterialBindGroupSlot) {
match *self {
MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => {
material_bindless_bind_group.free(material_bind_group_slot);
}
MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => {
material_non_bindless_bind_group.free(material_bind_group_slot);
}
}
}
/// Returns the actual bind group, or `None` if it hasn't been created yet.
pub fn get_bind_group(&self) -> Option<&BindGroup> {
match *self {
MaterialBindGroup::Bindless(ref material_bindless_bind_group) => {
material_bindless_bind_group.get_bind_group()
}
MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => {
material_non_bindless_bind_group.get_bind_group()
}
}
}
/// Returns true if all the slots are full or false if at least one slot in
/// this bind group is free.
fn is_full(&self) -> bool {
match *self {
MaterialBindGroup::Bindless(ref material_bindless_bind_group) => {
material_bindless_bind_group.is_full()
}
MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => {
material_non_bindless_bind_group.is_full()
}
}
}
/// Recreates the bind group for this material bind group containing the
/// data for every material in it.
fn rebuild_bind_group_if_necessary(
&mut self,
render_device: &RenderDevice,
bind_group_layout: &BindGroupLayout,
fallback_image: &FallbackImage,
fallback_bindless_resources: &FallbackBindlessResources,
fallback_buffers: &MaterialFallbackBuffers,
) {
match *self {
MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => {
material_bindless_bind_group.rebuild_bind_group_if_necessary(
render_device,
bind_group_layout,
fallback_image,
fallback_bindless_resources,
fallback_buffers,
);
}
MaterialBindGroup::NonBindless(_) => {}
}
}
/// Returns the associated extra data for the material with the given slot.
pub fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data {
match *self {
MaterialBindGroup::Bindless(ref material_bindless_bind_group) => {
material_bindless_bind_group.get_extra_data(slot)
}
MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => {
material_non_bindless_bind_group.get_extra_data(slot)
}
}
}
}
impl<M> MaterialBindlessBindGroup<M>
where
M: Material,
{
/// Returns a new bind group.
fn new() -> MaterialBindlessBindGroup<M> {
let count = M::bindless_slot_count().unwrap_or(1);
MaterialBindlessBindGroup {
bind_group: None,
unprepared_bind_groups: iter::repeat_with(|| None).take(count as usize).collect(),
used_slot_bitmap: 0,
}
}
/// Allocates a new slot and returns its index.
///
/// This bind group must not be full.
fn allocate(&mut self) -> MaterialBindGroupSlot {
debug_assert!(!self.is_full());
// Mark the slot as used.
let slot = self.used_slot_bitmap.trailing_ones();
self.used_slot_bitmap |= 1 << slot;
slot.into()
}
/// Assigns the given unprepared bind group to the given slot.
fn init(
&mut self,
_: &RenderDevice,
_: &BindGroupLayout,
slot: MaterialBindGroupSlot,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
) {
self.unprepared_bind_groups[slot.0 as usize] = Some(unprepared_bind_group);
// Invalidate the cached bind group so that we rebuild it again.
self.bind_group = None;
}
/// Marks the given slot as free.
fn free(&mut self, slot: MaterialBindGroupSlot) {
self.unprepared_bind_groups[slot.0 as usize] = None;
self.used_slot_bitmap &= !(1 << slot.0);
// Invalidate the cached bind group so that we rebuild it again.
self.bind_group = None;
}
/// Returns true if all the slots are full or false if at least one slot in
/// this bind group is free.
fn is_full(&self) -> bool {
self.used_slot_bitmap == (1 << (self.unprepared_bind_groups.len() as u32)) - 1
}
/// Returns the actual bind group, or `None` if it hasn't been created yet.
fn get_bind_group(&self) -> Option<&BindGroup> {
self.bind_group.as_ref()
}
/// Recreates the bind group for this material bind group containing the
/// data for every material in it.
fn rebuild_bind_group_if_necessary(
&mut self,
render_device: &RenderDevice,
bind_group_layout: &BindGroupLayout,
fallback_image: &FallbackImage,
fallback_bindless_resources: &FallbackBindlessResources,
fallback_buffers: &MaterialFallbackBuffers,
) {
if self.bind_group.is_some() {
return;
}
let Some(first_bind_group) = self
.unprepared_bind_groups
.iter()
.find_map(|slot| slot.as_ref())
else {
return;
};
// Creates the intermediate binding resource vectors.
let Some(binding_resource_arrays) = self.recreate_binding_resource_arrays(
first_bind_group,
fallback_image,
fallback_bindless_resources,
fallback_buffers,
) else {
return;
};
// Now build the actual resource arrays for `wgpu`.
let entries = binding_resource_arrays
.iter()
.map(|&(&binding, ref binding_resource_array)| BindGroupEntry {
binding,
resource: match *binding_resource_array {
BindingResourceArray::Buffers(ref vec) => {
BindingResource::BufferArray(&vec[..])
}
BindingResourceArray::TextureViews(_, ref vec) => {
BindingResource::TextureViewArray(&vec[..])
}
BindingResourceArray::Samplers(ref vec) => {
BindingResource::SamplerArray(&vec[..])
}
},
})
.collect::<Vec<_>>();
self.bind_group =
Some(render_device.create_bind_group(M::label(), bind_group_layout, &entries));
}
/// Recreates the binding arrays for each material in this bind group.
fn recreate_binding_resource_arrays<'a>(
&'a self,
first_bind_group: &'a UnpreparedBindGroup<M::Data>,
fallback_image: &'a FallbackImage,
fallback_bindless_resources: &'a FallbackBindlessResources,
fallback_buffers: &'a MaterialFallbackBuffers,
) -> Option<Vec<(&'a u32, BindingResourceArray<'a>)>> {
// Initialize the arrays.
let mut binding_resource_arrays = first_bind_group
.bindings
.iter()
.map(|(index, binding)| match *binding {
OwnedBindingResource::Buffer(..) => (index, BindingResourceArray::Buffers(vec![])),
OwnedBindingResource::TextureView(dimension, _) => {
(index, BindingResourceArray::TextureViews(dimension, vec![]))
}
OwnedBindingResource::Sampler(..) => {
(index, BindingResourceArray::Samplers(vec![]))
}
})
.collect::<Vec<_>>();
for maybe_unprepared_bind_group in self.unprepared_bind_groups.iter() {
match *maybe_unprepared_bind_group {
None => {
// Push dummy resources for this slot.
for binding_resource_array in &mut binding_resource_arrays {
match *binding_resource_array {
(binding, BindingResourceArray::Buffers(ref mut vec)) => {
vec.push(BufferBinding {
buffer: &fallback_buffers.0[binding],
offset: 0,
size: None,
});
}
(
_,
BindingResourceArray::TextureViews(texture_dimension, ref mut vec),
) => vec.push(&fallback_image.get(texture_dimension).texture_view),
(_, BindingResourceArray::Samplers(ref mut vec)) => {
vec.push(&fallback_bindless_resources.fallback_sampler);
}
}
}
}
Some(ref unprepared_bind_group) => {
// Push the resources for this slot.
//
// All materials in this group must have the same type of
// binding (buffer, texture view, sampler) in each bind
// group entry.
for (
binding_index,
(&mut (binding, ref mut binding_resource_array), (_, binding_resource)),
) in binding_resource_arrays
.iter_mut()
.zip(unprepared_bind_group.bindings.0.iter())
.enumerate()
{
match (binding_resource_array, binding_resource) {
(
&mut BindingResourceArray::Buffers(ref mut vec),
OwnedBindingResource::Buffer(buffer),
) => match NonZero::new(buffer.size()) {
None => vec.push(BufferBinding {
buffer: &fallback_buffers.0[binding],
offset: 0,
size: None,
}),
Some(size) => vec.push(BufferBinding {
buffer,
offset: 0,
size: Some(size),
}),
},
(
&mut BindingResourceArray::TextureViews(_, ref mut vec),
OwnedBindingResource::TextureView(_, texture_view),
) => vec.push(texture_view),
(
&mut BindingResourceArray::Samplers(ref mut vec),
OwnedBindingResource::Sampler(sampler),
) => vec.push(sampler),
_ => {
error!(
"Mismatched bind group layouts for material \
{} at bind group {}; can't combine bind \
groups into a single bindless bind group!",
any::type_name::<M>(),
binding_index,
);
return None;
}
}
}
}
}
}
Some(binding_resource_arrays)
}
/// Returns the associated extra data for the material with the given slot.
fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data {
&self.unprepared_bind_groups[slot.0 as usize]
.as_ref()
.unwrap()
.data
}
}
impl<M> MaterialNonBindlessBindGroup<M>
where
M: Material,
{
/// Creates a new material bind group.
fn new() -> MaterialNonBindlessBindGroup<M> {
MaterialNonBindlessBindGroup {
allocation: MaterialNonBindlessBindGroupAllocation::Unallocated,
}
}
/// Allocates a new slot and returns its index.
///
/// This bind group must not be full.
fn allocate(&mut self) -> MaterialBindGroupSlot {
debug_assert!(!self.is_full());
self.allocation = MaterialNonBindlessBindGroupAllocation::Allocated;
MaterialBindGroupSlot(0)
}
/// Assigns an unprepared bind group to the group and slot specified in the
/// [`MaterialBindingId`].
///
/// For non-bindless bind groups, we go ahead and create the bind group
/// immediately.
fn init(
&mut self,
render_device: &RenderDevice,
bind_group_layout: &BindGroupLayout,
_: MaterialBindGroupSlot,
unprepared_bind_group: UnpreparedBindGroup<M::Data>,
) {
let entries = unprepared_bind_group
.bindings
.iter()
.map(|(index, binding)| BindGroupEntry {
binding: *index,
resource: binding.get_binding(),
})
.collect::<Vec<_>>();
self.allocation = MaterialNonBindlessBindGroupAllocation::Initialized(
render_device.create_bind_group(M::label(), bind_group_layout, &entries),
unprepared_bind_group.data,
);
}
/// Fills the slot directly with a custom bind group.
///
/// This is only a meaningful operation for non-bindless bind groups. It's
/// rarely used, but see the `texture_binding_array` example for an example
/// demonstrating how this feature might see use in practice.
fn init_custom(&mut self, bind_group: BindGroup, extra_data: M::Data) {
self.allocation =
MaterialNonBindlessBindGroupAllocation::Initialized(bind_group, extra_data);
}
/// Deletes the stored bind group.
fn free(&mut self, _: MaterialBindGroupSlot) {
self.allocation = MaterialNonBindlessBindGroupAllocation::Unallocated;
}
/// Returns true if the slot is full or false if it's free.
fn is_full(&self) -> bool {
!matches!(
self.allocation,
MaterialNonBindlessBindGroupAllocation::Unallocated
)
}
/// Returns the actual bind group, or `None` if it hasn't been created yet.
fn get_bind_group(&self) -> Option<&BindGroup> {
match self.allocation {
MaterialNonBindlessBindGroupAllocation::Unallocated
| MaterialNonBindlessBindGroupAllocation::Allocated => None,
MaterialNonBindlessBindGroupAllocation::Initialized(ref bind_group, _) => {
Some(bind_group)
}
}
}
/// Returns the associated extra data for the material.
fn get_extra_data(&self, _: MaterialBindGroupSlot) -> &M::Data {
match self.allocation {
MaterialNonBindlessBindGroupAllocation::Initialized(_, ref extra_data) => extra_data,
MaterialNonBindlessBindGroupAllocation::Unallocated
| MaterialNonBindlessBindGroupAllocation::Allocated => {
panic!("Bind group not initialized")
}
}
}
}
impl<M> FromWorld for MaterialBindGroupAllocator<M>
where
M: Material,
{
fn from_world(world: &mut World) -> Self {
// Create a new bind group allocator.
let render_device = world.resource::<RenderDevice>();
let bind_group_layout_entries = M::bind_group_layout_entries(render_device, false);
let bind_group_layout =
render_device.create_bind_group_layout(M::label(), &bind_group_layout_entries);
let fallback_buffers =
MaterialFallbackBuffers::new(render_device, &bind_group_layout_entries);
MaterialBindGroupAllocator {
bind_groups: vec![],
free_bind_groups: vec![],
bind_group_layout,
fallback_buffers,
bindless_enabled: material_uses_bindless_resources::<M>(render_device),
phantom: PhantomData,
}
}
}
/// Returns true if the material will *actually* use bindless resources or false
/// if it won't.
///
/// This takes the platform support (or lack thereof) for bindless resources
/// into account.
pub fn material_uses_bindless_resources<M>(render_device: &RenderDevice) -> bool
where
M: Material,
{
M::bindless_slot_count().is_some()
&& render_device
.features()
.contains(WgpuFeatures::BUFFER_BINDING_ARRAY | WgpuFeatures::TEXTURE_BINDING_ARRAY)
}
impl FromWorld for FallbackBindlessResources {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
FallbackBindlessResources {
fallback_sampler: render_device.create_sampler(&SamplerDescriptor {
label: Some("fallback sampler"),
..default()
}),
}
}
}
impl MaterialFallbackBuffers {
/// Creates a new set of fallback buffers containing dummy allocations.
///
/// We populate unused bind group slots with these.
fn new(
render_device: &RenderDevice,
bind_group_layout_entries: &[BindGroupLayoutEntry],
) -> MaterialFallbackBuffers {
let mut fallback_buffers = HashMap::default();
for bind_group_layout_entry in bind_group_layout_entries {
// Create a dummy buffer of the appropriate size.
let BindingType::Buffer {
min_binding_size, ..
} = bind_group_layout_entry.ty
else {
continue;
};
let mut size: u64 = match min_binding_size {
None => 0,
Some(min_binding_size) => min_binding_size.into(),
};
size = size.max(MIN_BUFFER_SIZE);
fallback_buffers.insert(
bind_group_layout_entry.binding,
render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("fallback buffer"),
contents: &vec![0; size as usize],
usage: BufferUsages::UNIFORM | BufferUsages::STORAGE,
}),
);
}
MaterialFallbackBuffers(fallback_buffers)
}
}