use std::collections::{BTreeMap, BTreeSet};
use bevy::{
app::SubApp,
prelude::*,
render::{
render_resource::{
BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBindingType,
PipelineCache, ShaderStages,
},
renderer::RenderDevice,
},
};
pub(crate) const AUTO_BINDING_VISIBILITY: ShaderStages =
ShaderStages::FRAGMENT.union(ShaderStages::COMPUTE);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AutoBufferKind {
Uniform,
Storage { read_only: bool },
}
#[derive(Resource, Default)]
pub struct AutoBufferLayouts(pub BTreeMap<u32, BTreeMap<u32, AutoBufferKind>>);
#[derive(Resource, Default)]
pub struct AutoBufferCompiledLayouts(pub BTreeMap<u32, BindGroupLayout>);
#[derive(Resource, Default)]
pub struct AutoBufferLayoutDescriptors(pub Vec<BindGroupLayoutDescriptor>);
#[derive(Resource, Default)]
pub struct AutoBufferBindGroups(pub BTreeMap<u32, BindGroup>);
#[derive(Clone)]
pub enum BoundResource {
Buffer(Buffer),
}
impl BoundResource {
fn binding(&self) -> BindingResource<'_> {
match self {
BoundResource::Buffer(buffer) => buffer.as_entire_binding(),
}
}
}
#[derive(Resource, Default)]
pub struct BindingTable {
entries: BTreeMap<(u32, u32), BoundResource>,
dirty_groups: BTreeSet<u32>,
}
impl BindingTable {
pub fn set_buffer(&mut self, group: u32, binding: u32, buffer: &Buffer) {
let unchanged = matches!(
self.entries.get(&(group, binding)),
Some(BoundResource::Buffer(existing)) if existing.id() == buffer.id()
);
if !unchanged {
self.entries
.insert((group, binding), BoundResource::Buffer(buffer.clone()));
self.dirty_groups.insert(group);
}
}
}
pub(crate) fn init_resources(render_app: &mut SubApp) {
render_app.init_resource::<AutoBufferLayouts>();
render_app.init_resource::<AutoBufferCompiledLayouts>();
render_app.init_resource::<AutoBufferLayoutDescriptors>();
render_app.init_resource::<AutoBufferBindGroups>();
render_app.init_resource::<BindingTable>();
}
pub(crate) fn register_binding(
render_app: &mut SubApp,
group_index: u32,
binding_index: u32,
kind: AutoBufferKind,
) {
init_resources(render_app);
render_app
.world_mut()
.resource_mut::<AutoBufferLayouts>()
.0
.entry(group_index)
.or_default()
.insert(binding_index, kind);
}
pub(crate) fn compile_layouts(
pipeline_cache: Res<PipelineCache>,
auto_buffer_layouts: Res<AutoBufferLayouts>,
mut compiled_layouts: ResMut<AutoBufferCompiledLayouts>,
mut descriptors: ResMut<AutoBufferLayoutDescriptors>,
) {
let keys: Vec<u32> = auto_buffer_layouts.0.keys().cloned().collect();
debug_assert!(
keys.windows(2).all(|w| w[1] == w[0] + 1),
"register_uniform_buffer/register_storage_buffer/register_array_buffer/register_gpu_buffer group indices must be contiguous (no gaps)"
);
for (&group_index, binding_map) in auto_buffer_layouts.0.iter() {
let entries: Vec<BindGroupLayoutEntry> = binding_map
.iter()
.map(|(&binding, &kind)| BindGroupLayoutEntry {
binding,
visibility: AUTO_BINDING_VISIBILITY,
ty: match kind {
AutoBufferKind::Uniform => BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
AutoBufferKind::Storage { read_only } => BindingType::Buffer {
ty: BufferBindingType::Storage { read_only },
has_dynamic_offset: false,
min_binding_size: None,
},
},
count: None,
})
.collect();
let desc = BindGroupLayoutDescriptor::new("auto_buffer_layout", &entries);
compiled_layouts
.0
.insert(group_index, pipeline_cache.get_bind_group_layout(&desc));
descriptors.0.push(desc);
}
}
pub(crate) fn finalize_bind_groups(
mut auto_bind_groups: ResMut<AutoBufferBindGroups>,
mut table: ResMut<BindingTable>,
compiled_layouts: Res<AutoBufferCompiledLayouts>,
auto_layouts: Res<AutoBufferLayouts>,
render_device: Res<RenderDevice>,
) {
if table.dirty_groups.is_empty() {
return;
}
let BindingTable {
entries,
dirty_groups,
} = &mut *table;
dirty_groups.retain(|&group_index| {
let (Some(expected), Some(layout)) = (
auto_layouts.0.get(&group_index),
compiled_layouts.0.get(&group_index),
) else {
return true;
};
let Some(bind_entries) = expected
.keys()
.map(|&binding| {
entries
.get(&(group_index, binding))
.map(|resource| BindGroupEntry {
binding,
resource: resource.binding(),
})
})
.collect::<Option<Vec<_>>>()
else {
return true;
};
let bind_group =
render_device.create_bind_group("auto_buffer_bind_group", layout, &bind_entries);
auto_bind_groups.0.insert(group_index, bind_group);
false
});
}
pub(crate) fn all_groups_bound(
layouts: &AutoBufferLayouts,
bind_groups: &AutoBufferBindGroups,
) -> bool {
layouts
.0
.keys()
.all(|group| bind_groups.0.contains_key(group))
}