From cffc1b1f6c2b53a5d36d294eb31126067a744f19 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 2 Jan 2024 19:31:04 -0800 Subject: [PATCH] Unload render assets from RAM (#10520) # Objective - No point in keeping Meshes/Images in RAM once they're going to be sent to the GPU, and kept in VRAM. This saves a _significant_ amount of memory (several GBs) on scenes like bistro. - References - https://github.com/bevyengine/bevy/pull/1782 - https://github.com/bevyengine/bevy/pull/8624 ## Solution - Augment RenderAsset with the capability to unload the underlying asset after extracting to the render world. - Mesh/Image now have a cpu_persistent_access field. If this field is RenderAssetPersistencePolicy::Unload, the asset will be unloaded from Assets. - A new AssetEvent is sent upon dropping the last strong handle for the asset, which signals to the RenderAsset to remove the GPU version of the asset. --- ## Changelog - Added `AssetEvent::NoLongerUsed` and `AssetEvent::is_no_longer_used()`. This event is sent when the last strong handle of an asset is dropped. - Rewrote the API for `RenderAsset` to allow for unloading the asset data from the CPU. - Added `RenderAssetPersistencePolicy`. - Added `Mesh::cpu_persistent_access` for memory savings when the asset is not needed except for on the GPU. - Added `Image::cpu_persistent_access` for memory savings when the asset is not needed except for on the GPU. - Added `ImageLoaderSettings::cpu_persistent_access`. - Added `ExrTextureLoaderSettings`. - Added `HdrTextureLoaderSettings`. ## Migration Guide - Asset loaders (GLTF, etc) now load meshes and textures without `cpu_persistent_access`. These assets will be removed from `Assets` and `Assets` once `RenderAssets` and `RenderAssets` contain the GPU versions of these assets, in order to reduce memory usage. If you require access to the asset data from the CPU in future frames after the GLTF asset has been loaded, modify all dependent `Mesh` and `Image` assets and set `cpu_persistent_access` to `RenderAssetPersistencePolicy::Keep`. - `Mesh` now requires a new `cpu_persistent_access` field. Set it to `RenderAssetPersistencePolicy::Keep` to mimic the previous behavior. - `Image` now requires a new `cpu_persistent_access` field. Set it to `RenderAssetPersistencePolicy::Keep` to mimic the previous behavior. - `MorphTargetImage::new()` now requires a new `cpu_persistent_access` parameter. Set it to `RenderAssetPersistencePolicy::Keep` to mimic the previous behavior. - `DynamicTextureAtlasBuilder::add_texture()` now requires that the `TextureAtlas` you pass has an `Image` with `cpu_persistent_access: RenderAssetPersistencePolicy::Keep`. Ensure you construct the image properly for the texture atlas. - The `RenderAsset` trait has significantly changed, and requires adapting your existing implementations. - The trait now requires `Clone`. - The `ExtractedAsset` associated type has been removed (the type itself is now extracted). - The signature of `prepare_asset()` is slightly different - A new `persistence_policy()` method is now required (return RenderAssetPersistencePolicy::Unload to match the previous behavior). - Match on the new `NoLongerUsed` variant for exhaustive matches of `AssetEvent`. --- src/loader.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/loader.rs b/src/loader.rs index 6572499..f33ee07 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -22,6 +22,7 @@ use bevy_render::{ }, prelude::SpatialBundle, primitives::Aabb, + render_asset::RenderAssetPersistencePolicy, render_resource::{Face, PrimitiveTopology}, texture::{ CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings, @@ -120,7 +121,7 @@ pub struct GltfLoader { /// |s: &mut GltfLoaderSettings| { /// s.load_cameras = false; /// } -/// ); +/// ); /// ``` #[derive(Serialize, Deserialize)] pub struct GltfLoaderSettings { @@ -389,7 +390,7 @@ async fn load_gltf<'a, 'b, 'c>( let primitive_label = primitive_label(&gltf_mesh, &primitive); let primitive_topology = get_primitive_topology(primitive.mode())?; - let mut mesh = Mesh::new(primitive_topology); + let mut mesh = Mesh::new(primitive_topology, RenderAssetPersistencePolicy::Unload); // Read vertex attributes for (semantic, accessor) in primitive.attributes() { @@ -433,6 +434,7 @@ async fn load_gltf<'a, 'b, 'c>( let morph_target_image = MorphTargetImage::new( morph_target_reader.map(PrimitiveMorphAttributesIter), mesh.count_vertices(), + RenderAssetPersistencePolicy::Unload, )?; let handle = load_context.add_labeled_asset(morph_targets_label, morph_target_image.0); @@ -724,6 +726,7 @@ async fn load_image<'a, 'b>( supported_compressed_formats, is_srgb, ImageSampler::Descriptor(sampler_descriptor), + RenderAssetPersistencePolicy::Unload, )?; Ok(ImageOrPath::Image { image, @@ -745,6 +748,7 @@ async fn load_image<'a, 'b>( supported_compressed_formats, is_srgb, ImageSampler::Descriptor(sampler_descriptor), + RenderAssetPersistencePolicy::Unload, )?, label: texture_label(&gltf_texture), })