wde_pbr/deferred/transform/
registry.rs1use bytemuck::Zeroable;
2use std::collections::HashMap;
3use uuid::Uuid;
4
5use bevy::prelude::*;
6use wde_camera::prelude::*;
7use wde_renderer::prelude::*;
8
9use crate::prelude::PbrSsboTransform;
10
11pub(crate) struct SsboTransformRegistryPlugin;
12impl Plugin for SsboTransformRegistryPlugin {
13 fn build(&self, app: &mut App) {
14 app.add_plugins((
15 ExtractComponentPlugin::<PbrSsboTransformUuid>::default(),
16 ExtractResourcePlugin::<PbrSsboTransformPendingRemovals>::default()
17 ))
18 .init_resource::<PbrSsboTransformPendingRemovals>()
19 .init_resource::<MainWorldEntityUuidMap>()
20 .add_systems(
21 Update,
22 (MainWorldEntityUuidMap::add, MainWorldEntityUuidMap::remove).chain()
23 );
24 app.get_sub_app_mut(RenderApp)
25 .unwrap()
26 .init_resource::<PbrUuidRegistry>()
27 .add_systems(Render, update_registry.in_set(RenderSet::Prepare));
28 }
29}
30
31#[derive(Component, Clone, Reflect)]
33#[reflect(Component)]
34pub struct PbrSsboTransformUuid(pub Uuid);
35impl Default for PbrSsboTransformUuid {
36 fn default() -> Self {
37 PbrSsboTransformUuid(Uuid::new_v4())
38 }
39}
40impl SyncComponent for PbrSsboTransformUuid {
41 type Target = Self;
42}
43impl ExtractComponent for PbrSsboTransformUuid {
44 type QueryData = (&'static GlobalTransform, &'static Self);
45 type QueryFilter = Changed<GlobalTransform>;
46 type Out = (GlobalTransform, Self);
47
48 fn extract_component(
49 (transform, uuid): QueryItem<'_, '_, Self::QueryData>
50 ) -> Option<Self::Out> {
51 Some((*transform, uuid.clone()))
52 }
53}
54
55#[derive(Resource, Default, Clone, ExtractResource)]
57struct PbrSsboTransformPendingRemovals(pub Vec<Uuid>);
58#[derive(Resource, Default)]
59struct MainWorldEntityUuidMap(HashMap<Entity, Uuid>);
60impl MainWorldEntityUuidMap {
61 pub fn add(
62 mut map: ResMut<Self>,
63 added: Query<(Entity, &PbrSsboTransformUuid), Added<PbrSsboTransformUuid>>
64 ) {
65 for (entity, uuid) in added.iter() {
66 map.0.insert(entity, uuid.0);
67 }
68 }
69
70 pub fn remove(
71 mut map: ResMut<Self>,
72 mut pending_removals: ResMut<PbrSsboTransformPendingRemovals>,
73 mut removed: RemovedComponents<PbrSsboTransformUuid>
74 ) {
75 pending_removals.0.clear();
76 for entity in removed.read() {
77 if let Some(uuid) = map.0.remove(&entity) {
78 pending_removals.0.push(uuid);
79 }
80 }
81 }
82}
83
84const MAX_UPLOAD_GAP: u32 = 64;
87
88#[derive(Resource, Default, Clone)]
90pub struct PbrUuidRegistry {
91 pub uuid_to_transform: HashMap<Uuid, u32>, pub available_transform_ids: Vec<u32>, pub next_transform_id: u32, transforms: Vec<TransformUniform>
96}
97impl PbrUuidRegistry {
98 pub fn get_or_add(&mut self, uuid: Uuid) -> u32 {
100 if let Some(&transform_id) = self.uuid_to_transform.get(&uuid) {
101 transform_id
102 } else {
103 let transform_id = self.available_transform_ids.pop().unwrap_or_else(|| {
104 let id = self.next_transform_id;
105 self.next_transform_id += 1;
106 id
107 });
108 self.uuid_to_transform.insert(uuid, transform_id);
109 transform_id
110 }
111 }
112
113 pub fn remove(&mut self, uuid: Uuid) {
115 if let Some(transform_id) = self.uuid_to_transform.remove(&uuid) {
116 self.available_transform_ids.push(transform_id);
117 }
118 }
119
120 pub fn get(&self, uuid: &Uuid) -> Option<u32> {
122 self.uuid_to_transform.get(uuid).copied()
123 }
124}
125
126#[derive(Component, Default)]
127struct ChangedTransformToRetryMarker;
128#[allow(clippy::type_complexity, clippy::too_many_arguments)]
129fn update_registry(
130 mut commands: Commands,
131 mut registry: ResMut<PbrUuidRegistry>,
132 pending_removals: Res<PbrSsboTransformPendingRemovals>,
133 changed_transforms: Query<
134 (Entity, &PbrSsboTransformUuid, &GlobalTransform),
135 (With<PbrSsboTransformUuid>, Changed<GlobalTransform>)
136 >,
137 changeds_to_retry: Query<
138 (Entity, &PbrSsboTransformUuid, &GlobalTransform),
139 (
140 With<PbrSsboTransformUuid>,
141 With<ChangedTransformToRetryMarker>
142 )
143 >,
144 ssbo: ResRenderData<PbrSsboTransform>,
145 buffers: Res<RenderAssets<GpuBuffer>>,
146 render_instance: Res<RenderInstance>
147) {
148 for uuid in &pending_removals.0 {
150 registry.remove(*uuid);
151 }
152
153 if changed_transforms.is_empty() && changeds_to_retry.is_empty() {
155 return;
156 }
157
158 let Some(ssbo_gpu) = ssbo
159 .iter()
160 .next()
161 .and_then(|(_, d)| buffers.get(&d.get_buffer(PbrSsboTransform::TRANSFORM_IDX)?))
162 .map(|gpu| &gpu.buffer)
163 else {
164 for (entity, _, _) in changed_transforms.iter() {
165 commands
166 .entity(entity)
167 .insert(ChangedTransformToRetryMarker);
168 }
169 return;
170 };
171 let mut changed_ids = Vec::new();
172 for (_, uuid, transform) in changed_transforms.iter().chain(changeds_to_retry.iter()) {
173 let transform_id = registry.get_or_add(uuid.0);
174 let index = transform_id as usize;
175 if registry.transforms.len() <= index {
176 registry
177 .transforms
178 .resize(index + 1, TransformUniform::zeroed());
179 }
180 registry.transforms[index] = TransformUniform::new(transform);
181 changed_ids.push(transform_id);
182 }
183 changed_ids.sort_unstable();
184 changed_ids.dedup();
185
186 let render_instance = render_instance.0.read().unwrap();
187 let mut ids = changed_ids.iter().copied().peekable();
188 while let Some(first) = ids.next() {
189 let mut last = first;
190 while let Some(next) = ids.next_if(|&id| id - last <= MAX_UPLOAD_GAP) {
191 last = next;
192 }
193 let range = first as usize..=last as usize;
194 ssbo_gpu.write(
195 &render_instance,
196 bytemuck::cast_slice(®istry.transforms[range]),
197 first as usize * std::mem::size_of::<TransformUniform>()
198 );
199 }
200 for (entity, _, _) in changeds_to_retry.iter() {
201 commands
202 .entity(entity)
203 .remove::<ChangedTransformToRetryMarker>();
204 }
205}