Skip to main content

bevy_render/
gpu_component_array_buffer.rs

1//! Lightweight storage of per-mesh-instance data.
2
3use crate::storage::{ShaderBuffer, ShaderBufferData};
4
5use alloc::borrow::Cow;
6use bevy_app::{App, Plugin, PostUpdate};
7use bevy_asset::{Assets, Handle, RenderAssetUsages};
8use bevy_ecs::{
9    component::Component,
10    entity::{EntityHashMap, EntityHashSet},
11    lifecycle::RemovedComponents,
12    prelude::Entity,
13    query::{QueryFilter, QueryItem, ReadOnlyQueryData},
14    resource::Resource,
15    system::{Commands, If, Local, Query, ResMut},
16};
17use bevy_log::warn;
18use bevy_mesh::MeshTag;
19use bevy_platform::collections::AlignedVec;
20use bytemuck::{AnyBitPattern, NoUninit};
21use core::marker::PhantomData;
22use wgpu::BufferUsages;
23
24/// Buffer sizes are rounded up to the nearest power of this value.
25///
26/// The value of 1.5 was chosen to provide a good balance between performance
27/// and fragmentation.
28const BUFFER_ALLOCATION_GROWTH_FACTOR: f64 = 1.5;
29
30pub struct GpuComponentArrayBufferPlugin<C>(PhantomData<C>)
31where
32    C: GpuComponentArrayBuffer;
33
34/// A lightweight mechanism to expose component data that may differ between
35/// mesh instances to the GPU in the form of a [`ShaderBuffer`].
36pub trait GpuComponentArrayBuffer: Component + Send + Sync + 'static {
37    /// The query to perform to update the component data.
38    type QueryData: ReadOnlyQueryData;
39    /// An optional filter to apply to the query.
40    type QueryFilter: QueryFilter;
41    /// The packed GPU data.
42    type Out: NoUninit + AnyBitPattern;
43
44    /// Packs the component into a form suitable for the GPU.
45    ///
46    /// If `None` is returned, then the component data is removed from the
47    /// component array buffer entirely.
48    fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out>;
49
50    /// Override this function to supply a custom debugging label for the
51    /// buffer.
52    fn label() -> Cow<'static, str> {
53        Cow::Borrowed("GPU component array")
54    }
55
56    /// Override this function to provide a custom set of allowed buffer usages
57    /// for the buffer.
58    fn buffer_usage() -> BufferUsages {
59        BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC
60    }
61}
62
63/// A resource, part of the main world, that stores the [`ShaderBuffer`] for a
64/// [`GpuComponentArrayBuffer`] and maintains the mapping between entities and
65/// entries within it.
66#[derive(Resource)]
67pub struct GpuComponentArray<C>
68where
69    C: GpuComponentArrayBuffer,
70{
71    pub buffer: Handle<ShaderBuffer>,
72    entity_to_tag: EntityHashMap<u32>,
73    tag_to_entity: Vec<Entity>,
74    phantom: PhantomData<C>,
75}
76
77impl<C> Plugin for GpuComponentArrayBufferPlugin<C>
78where
79    C: GpuComponentArrayBuffer,
80{
81    fn build(&self, app: &mut App) {
82        // Automatically insert a `GpuComponentArray` resource, for convenience'
83        // sake.
84        let mut shader_buffer_assets = app.world_mut().resource_mut::<Assets<ShaderBuffer>>();
85        let gpu_component_array = GpuComponentArray::<C>::new(&mut shader_buffer_assets);
86
87        app.insert_resource(gpu_component_array)
88            .add_systems(PostUpdate, update_components::<C>);
89    }
90}
91
92impl<C> Default for GpuComponentArrayBufferPlugin<C>
93where
94    C: GpuComponentArrayBuffer,
95{
96    fn default() -> Self {
97        Self(PhantomData::<C>)
98    }
99}
100
101impl<C> GpuComponentArray<C>
102where
103    C: GpuComponentArrayBuffer,
104{
105    /// Creates a new [`GpuComponentArray`] and the [`ShaderBuffer`] it writes
106    /// into.
107    pub fn new(shader_buffer_assets: &mut Assets<ShaderBuffer>) -> Self {
108        let data = AlignedVec::from(bytemuck::zeroed_vec::<C::Out>(1));
109        let buffer_size = data.len() as u64;
110        let buffer = shader_buffer_assets.add(ShaderBuffer {
111            data: ShaderBufferData::Initialized { data, buffer_size },
112            label: C::label(),
113            buffer_usage: C::buffer_usage(),
114            asset_usage: RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
115            copy_on_resize: true,
116        });
117
118        GpuComponentArray {
119            buffer,
120            entity_to_tag: EntityHashMap::default(),
121            tag_to_entity: vec![],
122            phantom: PhantomData,
123        }
124    }
125}
126
127/// A main-world system that runs the [`GpuComponentArrayBuffer`] query,
128/// extracts the components, and writes them into the associated
129/// [`ShaderBuffer`].
130fn update_components<C>(
131    mut commands: Commands,
132    query: Query<(Entity, Option<&MeshTag>, C::QueryData), C::QueryFilter>,
133    mut component_array: If<ResMut<GpuComponentArray<C>>>,
134    mut shader_buffers: ResMut<Assets<ShaderBuffer>>,
135    mut removed_components: RemovedComponents<C>,
136    mut processed_entities: Local<EntityHashSet>,
137) where
138    C: GpuComponentArrayBuffer,
139{
140    let Some(mut buffer) = shader_buffers.get_mut(&mut component_array.buffer) else {
141        return;
142    };
143
144    processed_entities.clear();
145
146    for (entity, maybe_tag, item) in &query {
147        match C::extract_component(item) {
148            None => {
149                // Remove the component data. If we swapped in some new data to
150                // fill the gap, update the mesh tag of the entity that that new
151                // data corresponds to.
152                if let Some((displaced_entity, displaced_entity_new_tag)) =
153                    component_array.remove(&mut buffer, entity)
154                {
155                    commands
156                        .entity(displaced_entity)
157                        .insert(MeshTag::with_type::<GpuComponentArray<C>>(
158                            displaced_entity_new_tag,
159                        ));
160                }
161
162                commands.entity(entity).remove::<MeshTag>();
163            }
164
165            Some(data) => {
166                if let Some(tag) = maybe_tag
167                    && tag.type_is::<GpuComponentArray<C>>()
168                {
169                    component_array.set(&mut buffer, tag.value, data);
170                } else {
171                    // We're about to write a new mesh tag, so in debug mode
172                    // make sure the application hasn't put some other mesh tag
173                    // there.
174                    // This is a best-effort check and isn't foolproof but
175                    // should catch some accidental misuse.
176                    if let Some(tag) = maybe_tag
177                        && !tag.type_is::<GpuComponentArray<C>>()
178                    {
179                        warn!(
180                            "The entity {:?} has an existing `MeshTag`. \
181                             `GpuComponentArrayBuffer` has overwritten it.",
182                            entity
183                        );
184                    }
185
186                    // Give the object a new mesh tag. In debug mode, tag it
187                    // with our type so that we can catch conflicts (e.g.
188                    // multiple `GpuComponentArrayBuffer` components on the same
189                    // mesh, which isn't presently supported).
190                    let tag = component_array.len();
191                    component_array.push(&mut buffer, entity, data);
192                    commands
193                        .entity(entity)
194                        .insert(MeshTag::with_type::<GpuComponentArray<C>>(tag as u32));
195                }
196            }
197        }
198
199        processed_entities.insert(entity);
200    }
201
202    // Only remove from the component array if we didn't pick up the entity
203    // above.
204    // It's possible that the component was removed and then re-added in the
205    // same frame.
206    for entity in removed_components
207        .read()
208        .filter(|entity| !processed_entities.contains(entity))
209    {
210        if let Some((displaced_entity, displaced_entity_new_tag)) =
211            component_array.remove(&mut buffer, entity)
212        {
213            commands
214                .entity(displaced_entity)
215                .insert(MeshTag::with_type::<GpuComponentArray<C>>(
216                    displaced_entity_new_tag,
217                ));
218        }
219    }
220}
221
222impl<C> GpuComponentArray<C>
223where
224    C: GpuComponentArrayBuffer,
225{
226    /// Returns the number of items that this [`GpuComponentArray`] is managing.
227    fn len(&self) -> usize {
228        self.tag_to_entity.len()
229    }
230
231    /// Returns true if this [`GpuComponentArray`] is managing no items or false
232    /// if it's managing at least one item.
233    fn is_empty(&self) -> bool {
234        self.tag_to_entity.is_empty()
235    }
236
237    /// Adds the `data` corresponding to the given entity to the end of the
238    /// given shader buffer.
239    ///
240    /// Data for the given entity must not already be present in the buffer.
241    fn push(&mut self, buffer: &mut ShaderBuffer, entity: Entity, data: C::Out) {
242        let ShaderBufferData::Initialized {
243            data: ref mut data_buffer,
244            ref mut buffer_size,
245        } = buffer.data
246        else {
247            panic!(
248                "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
249                created with `ShaderBufferData::Initialized`"
250            );
251        };
252        if self.is_empty() {
253            data_buffer.clear();
254        }
255
256        let tag = self.tag_to_entity.len() as u32;
257
258        let needed_buffer_len = tag as usize + 1;
259        let current_buffer_len = data_buffer.len() / size_of::<C::Out>();
260        if needed_buffer_len > current_buffer_len {
261            let next_buffer_len = round_buffer_size_up(needed_buffer_len);
262            data_buffer.resize(next_buffer_len * size_of::<C::Out>(), 0);
263            *buffer_size = data_buffer.len() as u64;
264        }
265        data_buffer.cast_slice_mut()[tag as usize] = data;
266
267        let prev_tag = self.entity_to_tag.insert(entity, tag);
268        self.tag_to_entity.push(entity);
269
270        debug_assert!(prev_tag.is_none());
271    }
272
273    fn get<'a>(&'_ self, buffer: &'a ShaderBuffer, tag: u32) -> &'a C::Out {
274        let Some(data_buffer) = buffer.cast_slice() else {
275            panic!(
276                "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
277                created with `ShaderBufferData::Initialized`"
278            );
279        };
280        &data_buffer[tag as usize]
281    }
282
283    fn set(&mut self, buffer: &mut ShaderBuffer, tag: u32, data: C::Out) {
284        let Some(data_buffer) = buffer.cast_slice_mut() else {
285            panic!(
286                "Shader buffers created for use in a `GpuComponentArrayBuffer` must have been \
287                created with `ShaderBufferData::Initialized`"
288            );
289        };
290        data_buffer[tag as usize] = data;
291    }
292
293    /// Removes the data corresponding to the given entity from the component
294    /// array.
295    ///
296    /// This method swaps in data if needed to fill the gap and returns the
297    /// entity and corresponding data that was swapped in if any.
298    ///
299    /// If no data was present for this entity, this function may panic.
300    fn remove(
301        &mut self,
302        buffer: &mut ShaderBuffer,
303        entity_to_remove: Entity,
304    ) -> Option<(Entity, u32)> {
305        debug_assert!(!self.tag_to_entity.is_empty());
306
307        let displaced_tag = self.tag_to_entity.len() as u32 - 1;
308        let displaced_data = *self.get(buffer, displaced_tag);
309        let displaced_entity = *self.tag_to_entity.last().unwrap();
310
311        let tag_to_remove = self
312            .entity_to_tag
313            .remove(&entity_to_remove)
314            .expect("Entity must have been present");
315        let removed_entity = self.tag_to_entity.swap_remove(tag_to_remove as usize);
316        debug_assert_eq!(entity_to_remove, removed_entity);
317
318        if tag_to_remove == displaced_tag {
319            return None;
320        }
321
322        *self.entity_to_tag.get_mut(&displaced_entity).unwrap() = tag_to_remove;
323        self.set(buffer, tag_to_remove, displaced_data);
324        Some((displaced_entity, tag_to_remove))
325    }
326}
327
328/// Rounds the buffer size up to a round size in order to amortize reallocation
329/// when the buffer repeatedly grows.
330///
331/// The nearest power of [`BUFFER_ALLOCATION_GROWTH_FACTOR`], which need not be
332/// an integer, is chosen.
333fn round_buffer_size_up(original_size: usize) -> usize {
334    if original_size <= 1 {
335        return 1;
336    }
337
338    // Round up to the nearest power of `BUFFER_ALLOCATION_GROWTH_FACTOR`.
339    let exponent = (original_size as f64).ln() / BUFFER_ALLOCATION_GROWTH_FACTOR.ln();
340    BUFFER_ALLOCATION_GROWTH_FACTOR.powi(exponent.ceil() as i32) as usize
341}
342#[cfg(test)]
343mod tests {
344    use std::marker::PhantomData;
345
346    use bevy_asset::{Handle, RenderAssetUsages};
347    use bevy_ecs::{
348        component::Component,
349        entity::{Entity, EntityHashMap, EntityIndex},
350        query::QueryItem,
351    };
352    use bytemuck::{Pod, Zeroable};
353    use encase::ShaderType;
354    use nonmax::NonMaxU32;
355
356    use crate::storage::ShaderBuffer;
357
358    use super::{GpuComponentArray, GpuComponentArrayBuffer};
359
360    /// A mock CPU-side component that we pretend to extract to the GPU.
361    #[derive(Component)]
362    struct MockComponent;
363
364    /// The mock GPU-side version of [`MockComponent`].
365    #[derive(Clone, Copy, Default, PartialEq, Pod, Zeroable, ShaderType, Debug)]
366    #[repr(C)]
367    struct MockComponentData {
368        a: u32,
369        b: u32,
370    }
371
372    impl MockComponentData {
373        fn new(a: u32, b: u32) -> Self {
374            Self { a, b }
375        }
376    }
377
378    impl GpuComponentArrayBuffer for MockComponent {
379        type QueryData = ();
380        type QueryFilter = ();
381        type Out = MockComponentData;
382
383        fn extract_component(_: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
384            None
385        }
386    }
387
388    /// Shared data that we use to perform our unit tests.
389    struct TestData {
390        /// The component array that the [`MockComponent`]s will be packed into.
391        gpu_component_array: GpuComponentArray<MockComponent>,
392        /// The GPU buffer that [`Self::gpu_component_array`] writes into.
393        buffer: ShaderBuffer,
394
395        entity_a: Entity,
396        entity_b: Entity,
397        entity_c: Entity,
398
399        entity_data_a: MockComponentData,
400        entity_data_b: MockComponentData,
401        entity_data_c: MockComponentData,
402    }
403
404    impl TestData {
405        fn new() -> TestData {
406            TestData {
407                gpu_component_array: GpuComponentArray::<MockComponent> {
408                    buffer: Handle::default(),
409                    entity_to_tag: EntityHashMap::default(),
410                    tag_to_entity: vec![],
411                    phantom: PhantomData,
412                },
413                buffer: ShaderBuffer::new::<MockComponentData>(vec![], RenderAssetUsages::all()),
414
415                // Create some entities.
416                entity_a: Entity::from_index(EntityIndex::new(NonMaxU32::new(1).unwrap())),
417                entity_b: Entity::from_index(EntityIndex::new(NonMaxU32::new(2).unwrap())),
418                entity_c: Entity::from_index(EntityIndex::new(NonMaxU32::new(3).unwrap())),
419
420                // Create some data to go with the entities.
421                entity_data_a: MockComponentData::new(11, 111),
422                entity_data_b: MockComponentData::new(22, 222),
423                entity_data_c: MockComponentData::new(33, 333),
424            }
425        }
426
427        /// Verifies that the GPU component array contains the given expected
428        /// data, in that order.
429        fn check(&self, expected_data: &[(Entity, MockComponentData)]) {
430            assert_eq!(
431                self.gpu_component_array.entity_to_tag.len(),
432                expected_data.len()
433            );
434            assert_eq!(
435                self.gpu_component_array.tag_to_entity.len(),
436                expected_data.len()
437            );
438            assert!(
439                self.buffer.buffer_size()
440                    >= (expected_data.len() * size_of::<MockComponentData>()) as u64
441            );
442
443            for (tag, (entity, data)) in expected_data.iter().enumerate() {
444                assert_eq!(
445                    self.gpu_component_array.entity_to_tag.get(entity),
446                    Some(&(tag as u32))
447                );
448                assert_eq!(
449                    self.gpu_component_array.tag_to_entity.get(tag),
450                    Some(entity)
451                );
452                assert_eq!(self.gpu_component_array.get(&self.buffer, tag as u32), data);
453            }
454        }
455    }
456
457    /// Checks that an empty GPU component array is correct.
458    #[test]
459    fn empty_gpu_component_array() {
460        let test_data = TestData::new();
461        test_data.check(&[]);
462    }
463
464    /// Check that a GPU component array is correct after pushing data for some
465    /// components onto it.
466    #[test]
467    fn push_onto_gpu_component_array() {
468        let mut test_data = TestData::new();
469        test_data.gpu_component_array.push(
470            &mut test_data.buffer,
471            test_data.entity_a,
472            test_data.entity_data_a,
473        );
474        test_data.check(&[(test_data.entity_a, test_data.entity_data_a)]);
475        test_data.gpu_component_array.push(
476            &mut test_data.buffer,
477            test_data.entity_b,
478            test_data.entity_data_b,
479        );
480        test_data.check(&[
481            (test_data.entity_a, test_data.entity_data_a),
482            (test_data.entity_b, test_data.entity_data_b),
483        ]);
484        test_data.gpu_component_array.push(
485            &mut test_data.buffer,
486            test_data.entity_c,
487            test_data.entity_data_c,
488        );
489        test_data.check(&[
490            (test_data.entity_a, test_data.entity_data_a),
491            (test_data.entity_b, test_data.entity_data_b),
492            (test_data.entity_c, test_data.entity_data_c),
493        ]);
494    }
495
496    // Check that a GPU component array is correct after pushing data for some
497    // components onto it, then mutating one of those components.
498    #[test]
499    fn set_element_in_gpu_component_array() {
500        let mut test_data = TestData::new();
501        test_data.gpu_component_array.push(
502            &mut test_data.buffer,
503            test_data.entity_a,
504            test_data.entity_data_a,
505        );
506        test_data.gpu_component_array.push(
507            &mut test_data.buffer,
508            test_data.entity_b,
509            test_data.entity_data_b,
510        );
511        test_data.gpu_component_array.push(
512            &mut test_data.buffer,
513            test_data.entity_c,
514            test_data.entity_data_c,
515        );
516
517        let entity_data_b_alt = MockComponentData::new(2222, 22222);
518
519        test_data
520            .gpu_component_array
521            .set(&mut test_data.buffer, 1, entity_data_b_alt);
522        test_data.check(&[
523            (test_data.entity_a, test_data.entity_data_a),
524            (test_data.entity_b, entity_data_b_alt),
525            (test_data.entity_c, test_data.entity_data_c),
526        ]);
527    }
528
529    // Check that a GPU component array is correct after pushing data for some
530    // components onto it, then removing the final component.
531    #[test]
532    fn remove_element_from_end_of_gpu_component_array() {
533        let mut test_data = TestData::new();
534        test_data.gpu_component_array.push(
535            &mut test_data.buffer,
536            test_data.entity_a,
537            test_data.entity_data_a,
538        );
539        test_data.gpu_component_array.push(
540            &mut test_data.buffer,
541            test_data.entity_b,
542            test_data.entity_data_b,
543        );
544        test_data.gpu_component_array.push(
545            &mut test_data.buffer,
546            test_data.entity_c,
547            test_data.entity_data_c,
548        );
549
550        let maybe_displaced_element = test_data
551            .gpu_component_array
552            .remove(&mut test_data.buffer, test_data.entity_c);
553        assert_eq!(maybe_displaced_element, None);
554
555        test_data.check(&[
556            (test_data.entity_a, test_data.entity_data_a),
557            (test_data.entity_b, test_data.entity_data_b),
558        ]);
559    }
560
561    // Check that a GPU component array is correct after pushing data for some
562    // components onto it, then removing the first component.
563    #[test]
564    fn remove_element_from_start_of_gpu_component_array() {
565        let mut test_data = TestData::new();
566        test_data.gpu_component_array.push(
567            &mut test_data.buffer,
568            test_data.entity_a,
569            test_data.entity_data_a,
570        );
571        test_data.gpu_component_array.push(
572            &mut test_data.buffer,
573            test_data.entity_b,
574            test_data.entity_data_b,
575        );
576        test_data.gpu_component_array.push(
577            &mut test_data.buffer,
578            test_data.entity_c,
579            test_data.entity_data_c,
580        );
581
582        let maybe_displaced_element = test_data
583            .gpu_component_array
584            .remove(&mut test_data.buffer, test_data.entity_a);
585        assert_eq!(maybe_displaced_element, Some((test_data.entity_c, 0)));
586
587        test_data.check(&[
588            (test_data.entity_c, test_data.entity_data_c),
589            (test_data.entity_b, test_data.entity_data_b),
590        ]);
591    }
592}