1use std::collections::{BTreeMap, BTreeSet};
2
3use bevy::{
4 app::SubApp,
5 prelude::*,
6 render::{
7 render_resource::{
8 BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
9 BindGroupLayoutEntry, BindingResource, BindingType, Buffer, BufferBindingType,
10 PipelineCache, ShaderStages,
11 },
12 renderer::RenderDevice,
13 },
14};
15
16pub(crate) const AUTO_BINDING_VISIBILITY: ShaderStages =
19 ShaderStages::FRAGMENT.union(ShaderStages::COMPUTE);
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub enum AutoBufferKind {
24 Uniform,
25 Storage { read_only: bool },
26}
27
28#[derive(Resource, Default)]
33pub struct AutoBufferLayouts(pub BTreeMap<u32, BTreeMap<u32, AutoBufferKind>>);
34
35#[derive(Resource, Default)]
39pub struct AutoBufferCompiledLayouts(pub BTreeMap<u32, BindGroupLayout>);
40
41#[derive(Resource, Default)]
46pub struct AutoBufferLayoutDescriptors(pub Vec<BindGroupLayoutDescriptor>);
47
48#[derive(Resource, Default)]
53pub struct AutoBufferBindGroups(pub BTreeMap<u32, BindGroup>);
54
55#[derive(Clone)]
57pub enum BoundResource {
58 Buffer(Buffer),
59 }
61
62impl BoundResource {
63 fn binding(&self) -> BindingResource<'_> {
64 match self {
65 BoundResource::Buffer(buffer) => buffer.as_entire_binding(),
66 }
67 }
68}
69
70#[derive(Resource, Default)]
76pub struct BindingTable {
77 entries: BTreeMap<(u32, u32), BoundResource>,
78 dirty_groups: BTreeSet<u32>,
79}
80
81impl BindingTable {
82 pub fn set_buffer(&mut self, group: u32, binding: u32, buffer: &Buffer) {
85 let unchanged = matches!(
86 self.entries.get(&(group, binding)),
87 Some(BoundResource::Buffer(existing)) if existing.id() == buffer.id()
88 );
89 if !unchanged {
90 self.entries
91 .insert((group, binding), BoundResource::Buffer(buffer.clone()));
92 self.dirty_groups.insert(group);
93 }
94 }
95}
96
97pub(crate) fn init_resources(render_app: &mut SubApp) {
99 render_app.init_resource::<AutoBufferLayouts>();
100 render_app.init_resource::<AutoBufferCompiledLayouts>();
101 render_app.init_resource::<AutoBufferLayoutDescriptors>();
102 render_app.init_resource::<AutoBufferBindGroups>();
103 render_app.init_resource::<BindingTable>();
104}
105
106pub(crate) fn register_binding(
108 render_app: &mut SubApp,
109 group_index: u32,
110 binding_index: u32,
111 kind: AutoBufferKind,
112) {
113 init_resources(render_app);
114 render_app
115 .world_mut()
116 .resource_mut::<AutoBufferLayouts>()
117 .0
118 .entry(group_index)
119 .or_default()
120 .insert(binding_index, kind);
121}
122
123pub(crate) fn compile_layouts(
125 pipeline_cache: Res<PipelineCache>,
126 auto_buffer_layouts: Res<AutoBufferLayouts>,
127 mut compiled_layouts: ResMut<AutoBufferCompiledLayouts>,
128 mut descriptors: ResMut<AutoBufferLayoutDescriptors>,
129) {
130 let keys: Vec<u32> = auto_buffer_layouts.0.keys().cloned().collect();
132 debug_assert!(
133 keys.windows(2).all(|w| w[1] == w[0] + 1),
134 "register_uniform_buffer/register_storage_buffer/register_array_buffer/register_gpu_buffer group indices must be contiguous (no gaps)"
135 );
136
137 for (&group_index, binding_map) in auto_buffer_layouts.0.iter() {
138 let entries: Vec<BindGroupLayoutEntry> = binding_map
139 .iter()
140 .map(|(&binding, &kind)| BindGroupLayoutEntry {
141 binding,
142 visibility: AUTO_BINDING_VISIBILITY,
143 ty: match kind {
144 AutoBufferKind::Uniform => BindingType::Buffer {
145 ty: BufferBindingType::Uniform,
146 has_dynamic_offset: false,
147 min_binding_size: None,
148 },
149 AutoBufferKind::Storage { read_only } => BindingType::Buffer {
150 ty: BufferBindingType::Storage { read_only },
151 has_dynamic_offset: false,
152 min_binding_size: None,
153 },
154 },
155 count: None,
156 })
157 .collect();
158 let desc = BindGroupLayoutDescriptor::new("auto_buffer_layout", &entries);
159 compiled_layouts
160 .0
161 .insert(group_index, pipeline_cache.get_bind_group_layout(&desc));
162 descriptors.0.push(desc);
163 }
164}
165
166pub(crate) fn finalize_bind_groups(
169 mut auto_bind_groups: ResMut<AutoBufferBindGroups>,
170 mut table: ResMut<BindingTable>,
171 compiled_layouts: Res<AutoBufferCompiledLayouts>,
172 auto_layouts: Res<AutoBufferLayouts>,
173 render_device: Res<RenderDevice>,
174) {
175 if table.dirty_groups.is_empty() {
176 return;
177 }
178 let BindingTable {
179 entries,
180 dirty_groups,
181 } = &mut *table;
182
183 dirty_groups.retain(|&group_index| {
184 let (Some(expected), Some(layout)) = (
185 auto_layouts.0.get(&group_index),
186 compiled_layouts.0.get(&group_index),
187 ) else {
188 return true;
189 };
190 let Some(bind_entries) = expected
191 .keys()
192 .map(|&binding| {
193 entries
194 .get(&(group_index, binding))
195 .map(|resource| BindGroupEntry {
196 binding,
197 resource: resource.binding(),
198 })
199 })
200 .collect::<Option<Vec<_>>>()
201 else {
202 return true;
203 };
204
205 let bind_group =
206 render_device.create_bind_group("auto_buffer_bind_group", layout, &bind_entries);
207 auto_bind_groups.0.insert(group_index, bind_group);
208 false
209 });
210}
211
212pub(crate) fn all_groups_bound(
214 layouts: &AutoBufferLayouts,
215 bind_groups: &AutoBufferBindGroups,
216) -> bool {
217 layouts
218 .0
219 .keys()
220 .all(|group| bind_groups.0.contains_key(group))
221}