Skip to main content

bevy_render/mesh/
allocator.rs

1//! Manages mesh vertex and index buffers.
2
3use alloc::borrow::Cow;
4use bevy_mesh::Indices;
5
6use bevy_app::{App, Plugin};
7use bevy_asset::AssetId;
8use bevy_derive::{Deref, DerefMut};
9use bevy_ecs::{
10    resource::Resource,
11    schedule::IntoScheduleConfigs as _,
12    system::{Res, ResMut},
13    world::{FromWorld, World},
14};
15use wgpu::{BufferUsages, DownlevelFlags, COPY_BUFFER_ALIGNMENT};
16
17#[cfg(feature = "morph")]
18use bevy_mesh::morph::MorphAttributes;
19
20use crate::{
21    mesh::{Mesh, MeshVertexBufferLayouts, RenderMesh},
22    render_asset::{prepare_assets, ExtractedAssets},
23    renderer::{RenderAdapter, RenderDevice, RenderQueue},
24    slab_allocator::{
25        Slab, SlabAllocationBufferSlice, SlabAllocator, SlabAllocatorSettings, SlabId, SlabItem,
26        SlabItemLayout,
27    },
28    GpuResourceAppExt, Render, RenderApp, RenderSystems,
29};
30
31/// A plugin that manages GPU memory for mesh data.
32pub struct MeshAllocatorPlugin;
33
34/// Manages the assignment of mesh data to GPU buffers.
35///
36/// The Bevy renderer tries to pack vertex and index data for multiple meshes
37/// together so that multiple meshes can be drawn back-to-back without any
38/// rebinding. This resource manages these buffers.
39///
40/// The [`MeshAllocatorSettings`] allows you to tune the behavior of the
41/// allocator for better performance with your use case. Most applications won't
42/// need to change the settings from their default values.
43///
44#[derive(Resource, Deref, DerefMut)]
45pub struct MeshAllocator {
46    /// Holds all buffers and offset allocators.
47    #[deref]
48    slab_allocator: SlabAllocator<MeshSlabItem>,
49
50    /// Whether we can pack multiple vertex arrays into a single slab on this
51    /// platform.
52    ///
53    /// This corresponds to [`DownlevelFlags::BASE_VERTEX`], which is unset on
54    /// WebGL 2. On this platform, we must give each vertex array its own
55    /// buffer, because we can't adjust the first vertex when we perform a draw.
56    general_vertex_slabs_supported: bool,
57}
58
59/// Tunable parameters that customize the behavior of the allocator.
60///
61/// Generally, these parameters adjust the tradeoff between memory fragmentation
62/// and speed. You can adjust them as desired for your application. Most
63/// applications can stick with the default values.
64#[derive(Resource, Deref, DerefMut)]
65pub struct MeshAllocatorSettings {
66    #[deref]
67    pub slab_allocator_settings: SlabAllocatorSettings,
68
69    /// Additional buffer usages to add to any vertex or index buffers created.
70    pub extra_buffer_usages: BufferUsages,
71}
72
73impl Default for MeshAllocatorSettings {
74    fn default() -> MeshAllocatorSettings {
75        MeshAllocatorSettings {
76            slab_allocator_settings: SlabAllocatorSettings::default(),
77            extra_buffer_usages: BufferUsages::empty(),
78        }
79    }
80}
81
82/// The [`ElementLayout`] for morph displacements.
83///
84/// All morph displacements currently have the same element layout, so we only
85/// need one of these.
86#[cfg(feature = "morph")]
87static MORPH_ATTRIBUTE_ELEMENT_LAYOUT: ElementLayout = ElementLayout {
88    class: ElementClass::MorphTarget,
89    size: size_of::<MorphAttributes>() as u64,
90    elements_per_slot: 1,
91};
92
93/// The ID of a single slab.
94pub type MeshSlabId = SlabId<MeshSlabItem>;
95
96/// The slab buffer and location within that slab in which each mesh is
97/// allocated.
98pub type MeshBufferSlice<'a> = SlabAllocationBufferSlice<'a, MeshSlabItem>;
99
100/// The [`SlabItem`] implementation that describes the information needed to
101/// allocate and free meshes.
102pub struct MeshSlabItem;
103
104impl SlabItem for MeshSlabItem {
105    type Key = MeshAllocationKey;
106    type Layout = ElementLayout;
107    fn label() -> Cow<'static, str> {
108        "mesh".into()
109    }
110}
111
112/// IDs of the slabs associated with a single mesh.
113#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
114pub struct MeshSlabs {
115    /// The slab storing the mesh's vertex data.
116    pub vertex_slab_id: MeshSlabId,
117    /// The slab storing the mesh's index data, if the mesh is indexed.
118    pub index_slab_id: Option<MeshSlabId>,
119    /// The slab storing the mesh's morph target displacements, if the mesh has
120    /// morph targets.
121    #[cfg(feature = "morph")]
122    pub morph_target_slab_id: Option<MeshSlabId>,
123}
124
125impl Slab<MeshSlabItem> {
126    /// Returns the type of buffer that this is: vertex, index, or morph target.
127    #[cfg(feature = "morph")]
128    pub fn element_class(&self) -> ElementClass {
129        self.element_layout().class
130    }
131}
132
133/// The handle used to retrieve a single mesh allocation.
134#[derive(Clone, Copy, PartialEq, Eq, Hash)]
135pub struct MeshAllocationKey {
136    /// The ID of the mesh asset.
137    pub mesh_id: AssetId<Mesh>,
138    /// The type of data: vertex data, index data, or morph data.
139    pub class: ElementClass,
140}
141
142impl MeshAllocationKey {
143    /// Creates a new [`MeshAllocationKey`] for the given mesh asset ID and
144    /// class.
145    pub fn new(mesh_id: AssetId<Mesh>, class: ElementClass) -> Self {
146        Self { mesh_id, class }
147    }
148}
149
150/// The type of element that a mesh slab can store.
151#[derive(Clone, Copy, PartialEq, Eq, Hash)]
152pub enum ElementClass {
153    /// Data for a vertex.
154    Vertex,
155    /// A vertex index.
156    Index,
157    #[cfg(feature = "morph")]
158    /// Displacement data for a morph target.
159    MorphTarget,
160}
161
162/// Information about the size of individual elements (vertices or indices)
163/// within a slab.
164///
165/// Slab objects are allocated in units of *slots*. Usually, each element takes
166/// up one slot, and so elements and slots are equivalent. Occasionally,
167/// however, a slot may consist of 2 or even 4 elements. This occurs when the
168/// size of an element isn't divisible by [`COPY_BUFFER_ALIGNMENT`]. When we
169/// resize buffers, we perform GPU-to-GPU copies to shuffle the existing
170/// elements into their new positions, and such copies must be on
171/// [`COPY_BUFFER_ALIGNMENT`] boundaries. Slots solve this problem by
172/// guaranteeing that the size of an allocation quantum is divisible by both the
173/// size of an element and [`COPY_BUFFER_ALIGNMENT`], so we can relocate it
174/// freely.
175#[derive(Clone, Copy, PartialEq, Eq, Hash)]
176pub struct ElementLayout {
177    /// Either a vertex or an index.
178    class: ElementClass,
179
180    /// The size in bytes of a single element (vertex or index).
181    size: u64,
182
183    /// The number of elements that make up a single slot.
184    ///
185    /// Usually, this is 1, but it can be different if [`ElementLayout::size`]
186    /// isn't divisible by 4. See the comment in [`ElementLayout`] for more
187    /// details.
188    elements_per_slot: u32,
189}
190
191impl Plugin for MeshAllocatorPlugin {
192    fn build(&self, app: &mut App) {
193        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
194            return;
195        };
196
197        render_app
198            .init_resource::<MeshAllocatorSettings>()
199            .add_systems(
200                Render,
201                allocate_and_free_meshes
202                    .in_set(RenderSystems::PrepareAssets)
203                    .before(prepare_assets::<RenderMesh>),
204            );
205    }
206
207    fn finish(&self, app: &mut App) {
208        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
209            return;
210        };
211
212        // The `RenderAdapter` isn't available until now, so we can't do this in
213        // [`Plugin::build`].
214        render_app.init_gpu_resource::<MeshAllocator>();
215    }
216}
217
218impl FromWorld for MeshAllocator {
219    fn from_world(world: &mut World) -> Self {
220        // Note whether we're on WebGL 2. In this case, we must give every
221        // vertex array its own slab.
222        let render_adapter = world.resource::<RenderAdapter>();
223        let general_vertex_slabs_supported = render_adapter
224            .get_downlevel_capabilities()
225            .flags
226            .contains(DownlevelFlags::BASE_VERTEX);
227
228        // Take the `extra_buffer_usages` from the mesh allocator settings into
229        // account.
230        let mesh_allocator_settings = world.resource::<MeshAllocatorSettings>();
231        let mut slab_allocator = SlabAllocator::new();
232        slab_allocator.extra_buffer_usages |= mesh_allocator_settings.extra_buffer_usages;
233
234        Self {
235            slab_allocator,
236            general_vertex_slabs_supported,
237        }
238    }
239}
240
241/// A system that processes newly-extracted or newly-removed meshes and writes
242/// their data into buffers or frees their data as appropriate.
243pub fn allocate_and_free_meshes(
244    mut mesh_allocator: ResMut<MeshAllocator>,
245    mesh_allocator_settings: Res<MeshAllocatorSettings>,
246    extracted_meshes: Res<ExtractedAssets<RenderMesh>>,
247    mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
248    render_device: Res<RenderDevice>,
249    render_queue: Res<RenderQueue>,
250) {
251    // Process removed or modified meshes.
252    mesh_allocator.free_meshes(&extracted_meshes);
253
254    // Process newly-added or modified meshes.
255    mesh_allocator.allocate_meshes(
256        &mesh_allocator_settings,
257        &extracted_meshes,
258        &mut mesh_vertex_buffer_layouts,
259        &render_device,
260        &render_queue,
261    );
262}
263
264impl MeshAllocator {
265    /// Returns the buffer and range within that buffer of the vertex data for
266    /// the mesh with the given ID.
267    ///
268    /// If the mesh wasn't allocated, returns None.
269    pub fn mesh_vertex_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
270        self.slab_allocation_slice(
271            &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
272            *self.mesh_id_to_vertex_slab(mesh_id)?,
273        )
274    }
275
276    /// Returns the buffer and range within that buffer of the index data for
277    /// the mesh with the given ID.
278    ///
279    /// If the mesh has no index data or wasn't allocated, returns None.
280    pub fn mesh_index_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
281        self.slab_allocation_slice(
282            &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
283            *self.mesh_id_to_index_slab(mesh_id)?,
284        )
285    }
286
287    /// Returns the buffer and range within that buffer of the morph target data
288    /// for the mesh with the given ID.
289    ///
290    /// If the mesh has no morph target data or wasn't allocated, returns None.
291    #[cfg(feature = "morph")]
292    pub fn mesh_morph_target_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
293        self.slab_allocation_slice(
294            &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
295            *self.mesh_id_to_morph_target_slab(mesh_id)?,
296        )
297    }
298
299    /// Returns the IDs of the vertex buffer and index buffer respectively for
300    /// the mesh with the given ID.
301    ///
302    /// If the mesh wasn't allocated, or has no index data in the case of the
303    /// index buffer, the corresponding element in the returned tuple will be
304    /// None.
305    pub fn mesh_slabs(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshSlabs> {
306        Some(MeshSlabs {
307            vertex_slab_id: self.mesh_id_to_vertex_slab(mesh_id).cloned()?,
308            index_slab_id: self.mesh_id_to_index_slab(mesh_id).cloned(),
309            #[cfg(feature = "morph")]
310            morph_target_slab_id: self.mesh_id_to_morph_target_slab(mesh_id).cloned(),
311        })
312    }
313
314    /// Returns the number of index allocations that this mesh allocator
315    /// manages.
316    pub fn index_allocation_count(&self) -> usize {
317        self.key_to_slab
318            .keys()
319            .filter(|key| key.class == ElementClass::Index)
320            .count()
321    }
322
323    /// Given the ID of a mesh, returns the ID of the slab that contains the
324    /// vertex data for that mesh, if it exists.
325    fn mesh_id_to_vertex_slab(&self, mesh_id: &AssetId<Mesh>) -> Option<&SlabId<MeshSlabItem>> {
326        self.key_to_slab
327            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::Vertex))
328    }
329
330    /// Given the ID of a mesh, returns the ID of the slab that contains the
331    /// index data for that mesh, if it exists.
332    fn mesh_id_to_index_slab(&self, mesh_id: &AssetId<Mesh>) -> Option<&SlabId<MeshSlabItem>> {
333        self.key_to_slab
334            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::Index))
335    }
336
337    /// Given the ID of a mesh, returns the ID of the slab that contains the
338    /// morph target data for that mesh, if it exists.
339    #[cfg(feature = "morph")]
340    fn mesh_id_to_morph_target_slab(
341        &self,
342        mesh_id: &AssetId<Mesh>,
343    ) -> Option<&SlabId<MeshSlabItem>> {
344        self.key_to_slab
345            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget))
346    }
347
348    /// Returns an iterator over all slabs that contain morph targets.
349    #[cfg(feature = "morph")]
350    pub fn morph_target_slabs(&self) -> impl Iterator<Item = MeshSlabId> {
351        self.slabs.iter().filter_map(|(slab_id, slab)| {
352            if matches!(slab.element_class(), ElementClass::MorphTarget) {
353                Some(*slab_id)
354            } else {
355                None
356            }
357        })
358    }
359
360    /// Processes newly-loaded meshes, allocating room in the slabs for their
361    /// mesh data and performing upload operations as appropriate.
362    fn allocate_meshes(
363        &mut self,
364        mesh_allocator_settings: &MeshAllocatorSettings,
365        extracted_meshes: &ExtractedAssets<RenderMesh>,
366        mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
367        render_device: &RenderDevice,
368        render_queue: &RenderQueue,
369    ) {
370        let mut allocation_stage = self.slab_allocator.stage_allocation();
371
372        // Loop over each mesh that was extracted this frame.
373        for (mesh_id, mesh) in &extracted_meshes.extracted {
374            let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64;
375            if vertex_buffer_size == 0 {
376                continue;
377            }
378
379            // Allocate vertex data. Note that we can only pack mesh vertex data
380            // together if the platform supports it.
381            let vertex_element_layout = ElementLayout::vertex(mesh_vertex_buffer_layouts, mesh);
382            if self.general_vertex_slabs_supported {
383                allocation_stage.allocate(
384                    &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
385                    vertex_buffer_size,
386                    vertex_element_layout,
387                    mesh_allocator_settings,
388                );
389            } else {
390                allocation_stage.allocate_large(
391                    &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
392                    vertex_element_layout,
393                );
394            }
395
396            // Allocate index data.
397            if let (Some(index_buffer_data), Some(index_element_layout)) =
398                (mesh.get_index_buffer_bytes(), ElementLayout::index(mesh))
399            {
400                allocation_stage.allocate(
401                    &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
402                    index_buffer_data.len() as u64,
403                    index_element_layout,
404                    mesh_allocator_settings,
405                );
406            }
407
408            // Allocate morph target data.
409            #[cfg(feature = "morph")]
410            if let Some(morph_targets) = mesh.get_morph_targets() {
411                allocation_stage.allocate(
412                    &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
413                    morph_targets.len() as u64 * size_of::<MorphAttributes>() as u64,
414                    MORPH_ATTRIBUTE_ELEMENT_LAYOUT,
415                    mesh_allocator_settings,
416                );
417            }
418        }
419
420        // Perform growth.
421        allocation_stage.commit(render_device, render_queue);
422
423        // Copy new mesh data in.
424        for (mesh_id, mesh) in &extracted_meshes.extracted {
425            self.copy_mesh_vertex_data(mesh_id, mesh, render_device, render_queue);
426            self.copy_mesh_index_data(mesh_id, mesh, render_device, render_queue);
427            #[cfg(feature = "morph")]
428            self.copy_mesh_morph_target_data(mesh_id, mesh, render_device, render_queue);
429        }
430    }
431
432    /// Copies vertex array data from a mesh into the appropriate spot in the
433    /// slab.
434    fn copy_mesh_vertex_data(
435        &mut self,
436        mesh_id: &AssetId<Mesh>,
437        mesh: &Mesh,
438        render_device: &RenderDevice,
439        render_queue: &RenderQueue,
440    ) {
441        // Call the generic function.
442        self.copy_element_data(
443            &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
444            mesh.get_vertex_buffer_size(),
445            |slice| mesh.write_packed_vertex_buffer_data(slice),
446            render_device,
447            render_queue,
448        );
449    }
450
451    /// Copies index array data from a mesh into the appropriate spot in the
452    /// slab.
453    fn copy_mesh_index_data(
454        &mut self,
455        mesh_id: &AssetId<Mesh>,
456        mesh: &Mesh,
457        render_device: &RenderDevice,
458        render_queue: &RenderQueue,
459    ) {
460        let Some(index_data) = mesh.get_index_buffer_bytes() else {
461            return;
462        };
463
464        // Call the generic function.
465        self.copy_element_data(
466            &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
467            index_data.len(),
468            |mut slice| slice.copy_from_slice(index_data),
469            render_device,
470            render_queue,
471        );
472    }
473
474    /// Copies morph target array data from a mesh into the appropriate spot in
475    /// the slab.
476    #[cfg(feature = "morph")]
477    fn copy_mesh_morph_target_data(
478        &mut self,
479        mesh_id: &AssetId<Mesh>,
480        mesh: &Mesh,
481        render_device: &RenderDevice,
482        render_queue: &RenderQueue,
483    ) {
484        let Some(morph_targets) = mesh.get_morph_targets() else {
485            return;
486        };
487
488        // Call the generic function.
489        self.copy_element_data(
490            &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
491            size_of_val(morph_targets),
492            |mut slice| slice.copy_from_slice(bytemuck::cast_slice(morph_targets)),
493            render_device,
494            render_queue,
495        );
496    }
497
498    /// Frees allocations for meshes that were removed or modified this frame.
499    fn free_meshes(&mut self, extracted_meshes: &ExtractedAssets<RenderMesh>) {
500        let mut deallocation_stage = self.slab_allocator.stage_deallocation();
501
502        // TODO: Consider explicitly reusing allocations for changed meshes of
503        // the same size
504        let meshes_to_free = extracted_meshes
505            .removed
506            .iter()
507            .chain(extracted_meshes.modified.iter());
508
509        for mesh_id in meshes_to_free {
510            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::Vertex));
511            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::Index));
512            #[cfg(feature = "morph")]
513            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget));
514        }
515
516        deallocation_stage.commit();
517    }
518}
519
520impl ElementLayout {
521    /// Creates an [`ElementLayout`] for mesh data of the given class (vertex or
522    /// index) with the given byte size.
523    fn new(class: ElementClass, size: u64) -> ElementLayout {
524        const {
525            assert!(4 == COPY_BUFFER_ALIGNMENT);
526        }
527        // this is equivalent to `4 / gcd(4,size)` but lets us not implement gcd.
528        // ping @atlv if above assert ever fails (likely never)
529        let elements_per_slot = [1, 4, 2, 4][size as usize & 3];
530        ElementLayout {
531            class,
532            size,
533            // Make sure that slot boundaries begin and end on
534            // `COPY_BUFFER_ALIGNMENT`-byte (4-byte) boundaries.
535            elements_per_slot,
536        }
537    }
538
539    /// Creates the appropriate [`ElementLayout`] for the given mesh's vertex
540    /// data.
541    fn vertex(
542        mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
543        mesh: &Mesh,
544    ) -> ElementLayout {
545        let mesh_vertex_buffer_layout =
546            mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts);
547        ElementLayout::new(
548            ElementClass::Vertex,
549            mesh_vertex_buffer_layout.0.layout().array_stride,
550        )
551    }
552
553    /// Creates the appropriate [`ElementLayout`] for the given mesh's index
554    /// data.
555    fn index(mesh: &Mesh) -> Option<ElementLayout> {
556        let size = match mesh.indices()? {
557            Indices::U16(_) => 2,
558            Indices::U32(_) => 4,
559        };
560        Some(ElementLayout::new(ElementClass::Index, size))
561    }
562}
563
564impl SlabItemLayout for ElementLayout {
565    fn size(&self) -> u64 {
566        self.size
567    }
568
569    fn elements_per_slot(&self) -> u32 {
570        self.elements_per_slot
571    }
572
573    fn buffer_usages(&self) -> BufferUsages {
574        self.class.buffer_usages()
575    }
576}
577
578impl ElementClass {
579    /// Returns the `wgpu` [`BufferUsages`] appropriate for a buffer of this
580    /// class.
581    fn buffer_usages(&self) -> BufferUsages {
582        match *self {
583            ElementClass::Vertex => BufferUsages::VERTEX,
584            ElementClass::Index => BufferUsages::INDEX,
585            #[cfg(feature = "morph")]
586            ElementClass::MorphTarget => BufferUsages::STORAGE,
587        }
588    }
589}