wde_pbr/deferred/batches/
build_batches.rs1use std::collections::{HashMap, HashSet};
2
3use bevy::prelude::*;
4use wde_renderer::prelude::*;
5
6use crate::prelude::{
7 CastShadow, PbrMaterial, PbrMaterial3d, PbrSsboTransformUuid, PbrUuidRegistry
8};
9
10#[derive(Component)]
11pub struct ExtractedPbrInstance {
12 mesh_id: AssetId<Mesh>,
13 material_id: AssetId<PbrMaterial>
14}
15
16#[derive(Clone, Copy)]
17pub(crate) struct TrackedBatchEntity {
18 pub(crate) key: BatchKey,
19 transform_id: u32,
20 pub(crate) main_entity: Entity
21}
22
23#[derive(Component)]
24pub(crate) struct ExtractedPbrInstanceToRetryMarker;
25
26#[derive(Component, Default, Reflect)]
29#[reflect(Component)]
30#[require(PbrSsboTransformUuid)]
31pub struct PbrBatchesMarker;
32impl SyncComponent for PbrBatchesMarker {
33 type Target = Self;
34}
35impl ExtractComponent for PbrBatchesMarker {
36 type QueryData = (&'static Mesh3d, &'static PbrMaterial3d<PbrMaterial>);
37 type QueryFilter = (
38 With<PbrBatchesMarker>,
39 With<Transform>,
40 Or<(Changed<Mesh3d>, Changed<PbrMaterial3d<PbrMaterial>>)>
41 );
42 type Out = ExtractedPbrInstance;
43
44 fn extract_component(
45 (mesh, material): QueryItem<'_, '_, Self::QueryData>
46 ) -> Option<Self::Out> {
47 Some(ExtractedPbrInstance {
48 mesh_id: mesh.0.id(),
49 material_id: material.0.id()
50 })
51 }
52}
53
54#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
56pub(crate) struct BatchKey {
57 pub(crate) mesh: AssetId<Mesh>,
58 pub(crate) material: AssetId<PbrMaterial>
59}
60pub(crate) struct Batch {
62 pub _key: BatchKey,
63 pub transform_ssbo_ids: Vec<u32>, pub instances_offset: u32 }
66#[derive(Resource, Default)]
68pub(crate) struct BatchList {
69 pub batches: HashMap<BatchKey, Batch>,
70 pub sorted_batches: Vec<BatchKey>, pub(crate) tracked_entities: HashMap<Entity, TrackedBatchEntity>,
72 pub shadow_casting_batches: HashSet<BatchKey>, pub dirty: bool }
75impl std::fmt::Debug for BatchList {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 let mut debug_struct = f.debug_struct("BatchList");
78 for batch_key in &self.sorted_batches {
79 if let Some(batch) = self.batches.get(batch_key) {
80 debug_struct.field(
81 &format!(
82 "Batch(mesh: {:?}, material: {:?})",
83 batch_key.mesh, batch_key.material
84 ),
85 &format!(
86 "Batch {{ transform_ssbo_ids: {:?}, instances_offset: {} }}",
87 batch.transform_ssbo_ids, batch.instances_offset
88 )
89 );
90 }
91 }
92 debug_struct.finish()
93 }
94}
95
96fn remove_tracked_entity(batches: &mut BatchList, entity: Entity) -> bool {
97 let Some(tracked) = batches.tracked_entities.remove(&entity) else {
98 return false;
99 };
100
101 let mut removed_any = false;
102 let mut remove_batch_key = false;
103 if let Some(batch) = batches.batches.get_mut(&tracked.key) {
104 let len_before = batch.transform_ssbo_ids.len();
105 batch
106 .transform_ssbo_ids
107 .retain(|id| *id != tracked.transform_id);
108 removed_any = batch.transform_ssbo_ids.len() != len_before;
109 remove_batch_key = batch.transform_ssbo_ids.is_empty();
110 }
111
112 if remove_batch_key {
113 batches.batches.remove(&tracked.key);
114 batches.sorted_batches.retain(|key| *key != tracked.key);
115 }
116
117 removed_any
118}
119
120pub(crate) fn build_batches(
121 mut commands: Commands,
122 mut batches: ResMut<BatchList>,
123 mut removed_extracted_instances: RemovedComponents<ExtractedPbrInstance>,
124 mut removed_transform_uuid: RemovedComponents<PbrSsboTransformUuid>,
125 extracted_instances: Query<
126 (
127 Entity,
128 MainEntity,
129 &PbrSsboTransformUuid,
130 &ExtractedPbrInstance
131 ),
132 Changed<ExtractedPbrInstance>
133 >,
134 extracted_instances_to_retry: Query<
135 (
136 Entity,
137 MainEntity,
138 &PbrSsboTransformUuid,
139 &ExtractedPbrInstance
140 ),
141 With<ExtractedPbrInstanceToRetryMarker>
142 >,
143 transform_registry: Res<PbrUuidRegistry>
144) {
145 let mut changed = false;
146
147 for entity in removed_extracted_instances.read() {
148 changed |= remove_tracked_entity(&mut batches, entity);
149 }
150 for entity in removed_transform_uuid.read() {
151 changed |= remove_tracked_entity(&mut batches, entity);
152 }
153
154 for (entity, main_entity, transform_uuid, instance) in extracted_instances
156 .into_iter()
157 .chain(extracted_instances_to_retry)
158 {
159 changed |= remove_tracked_entity(&mut batches, entity);
160
161 let transform_id = if let Some(id) = transform_registry.get(&transform_uuid.0) {
162 id
163 } else {
164 commands
166 .entity(entity)
167 .insert(ExtractedPbrInstanceToRetryMarker);
168 continue;
169 };
170
171 let key = BatchKey {
172 mesh: instance.mesh_id,
173 material: instance.material_id
174 };
175 batches
176 .batches
177 .entry(key)
178 .or_insert_with(|| Batch {
179 _key: key,
180 transform_ssbo_ids: Vec::new(),
181 instances_offset: 0
182 })
183 .transform_ssbo_ids
184 .push(transform_id);
185 batches.tracked_entities.insert(
186 entity,
187 TrackedBatchEntity {
188 key,
189 transform_id,
190 main_entity
191 }
192 );
193 if !batches.sorted_batches.contains(&key) {
194 batches.sorted_batches.push(key);
195 }
196 commands
197 .entity(entity)
198 .remove::<ExtractedPbrInstanceToRetryMarker>();
199 changed = true;
200 }
201 batches.dirty |= changed;
202
203 if changed {
205 batches.sorted_batches.sort_by(|a, b| {
206 a.material
207 .cmp(&b.material)
208 .then_with(|| a.mesh.cmp(&b.mesh))
209 });
210 }
211}
212
213pub(crate) fn update_shadow_casting_batches(
215 mut batches: ResMut<BatchList>,
216 cast_shadow_query: Query<MainEntity, With<CastShadow>>
217) {
218 let shadow_main_entities: HashSet<Entity> = cast_shadow_query.iter().collect();
219 let new_shadow_batches: HashSet<BatchKey> = batches
220 .tracked_entities
221 .iter()
222 .filter_map(|(_, tracked)| {
223 if shadow_main_entities.contains(&tracked.main_entity) {
224 Some(tracked.key)
225 } else {
226 None
227 }
228 })
229 .collect();
230 batches.shadow_casting_batches = new_shadow_batches;
231}
232
233pub(crate) struct BatchesPlugin;
234impl Plugin for BatchesPlugin {
235 fn build(&self, app: &mut App) {
236 app.add_plugins(ExtractComponentPlugin::<PbrBatchesMarker>::default());
237 app.get_sub_app_mut(RenderApp)
238 .unwrap()
239 .init_resource::<BatchList>();
240 }
241}