From 44424391fe217d9b0b4b0be2649b44a994983a59 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`. --- crates/bevy_asset/src/assets.rs | 26 ++-- crates/bevy_asset/src/event.rs | 9 ++ crates/bevy_asset/src/handle.rs | 4 +- crates/bevy_asset/src/lib.rs | 20 ++- .../bevy_core_pipeline/src/tonemapping/mod.rs | 4 +- crates/bevy_gizmos/src/lib.rs | 24 ++-- crates/bevy_gltf/src/loader.rs | 8 +- crates/bevy_pbr/src/material.rs | 2 + crates/bevy_render/src/mesh/mesh/mod.rs | 48 ++++--- crates/bevy_render/src/mesh/morph.rs | 12 +- crates/bevy_render/src/mesh/shape/capsule.rs | 18 ++- crates/bevy_render/src/mesh/shape/cylinder.rs | 18 ++- .../bevy_render/src/mesh/shape/icosphere.rs | 18 ++- crates/bevy_render/src/mesh/shape/mod.rs | 41 ++++-- .../src/mesh/shape/regular_polygon.rs | 18 ++- crates/bevy_render/src/mesh/shape/torus.rs | 18 ++- crates/bevy_render/src/mesh/shape/uvsphere.rs | 18 ++- crates/bevy_render/src/render_asset.rs | 90 +++++++----- .../src/render_resource/pipeline_cache.rs | 2 + .../src/texture/compressed_image_saver.rs | 1 + .../src/texture/exr_texture_loader.rs | 16 ++- .../bevy_render/src/texture/fallback_image.rs | 12 +- .../src/texture/hdr_texture_loader.rs | 16 ++- crates/bevy_render/src/texture/image.rs | 44 +++--- .../bevy_render/src/texture/image_loader.rs | 4 + .../src/texture/image_texture_conversion.rs | 16 ++- .../bevy_render/src/view/window/screenshot.rs | 2 + .../src/dynamic_texture_atlas_builder.rs | 14 +- crates/bevy_sprite/src/mesh2d/material.rs | 3 +- crates/bevy_sprite/src/render/mod.rs | 5 +- .../bevy_sprite/src/texture_atlas_builder.rs | 2 + crates/bevy_text/src/font.rs | 2 + crates/bevy_text/src/font_atlas.rs | 3 + crates/bevy_ui/src/render/mod.rs | 1 + .../src/render/ui_material_pipeline.rs | 4 +- crates/bevy_ui/src/widget/text.rs | 2 +- examples/2d/mesh2d_manual.rs | 10 +- examples/3d/3d_shapes.rs | 6 +- examples/3d/anti_aliasing.rs | 2 + examples/3d/generate_custom_mesh.rs | 11 +- examples/3d/lines.rs | 27 ++-- examples/3d/tonemapping.rs | 2 + examples/animation/custom_skinned_mesh.rs | 136 +++++++++--------- .../shader/compute_shader_game_of_life.rs | 2 + examples/stress_tests/bevymark.rs | 6 +- examples/stress_tests/many_cubes.rs | 6 +- 46 files changed, 496 insertions(+), 257 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 3017ea3f12..78a038fa55 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,5 +1,7 @@ -use crate::{self as bevy_asset, LoadState}; -use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle}; +use crate::{self as bevy_asset}; +use crate::{ + Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, LoadState, UntypedHandle, +}; use bevy_ecs::{ prelude::EventWriter, system::{Res, ResMut, Resource}, @@ -484,9 +486,7 @@ impl Assets { } /// A system that synchronizes the state of assets in this collection with the [`AssetServer`]. This manages - /// [`Handle`] drop events and adds queued [`AssetEvent`] values to their [`Events`] resource. - /// - /// [`Events`]: bevy_ecs::event::Events + /// [`Handle`] drop events. pub fn track_assets(mut assets: ResMut, asset_server: Res) { let assets = &mut *assets; // note that we must hold this lock for the entire duration of this function to ensure @@ -496,10 +496,13 @@ impl Assets { let mut infos = asset_server.data.infos.write(); let mut not_ready = Vec::new(); while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() { - let id = drop_event.id; + let id = drop_event.id.typed(); + + assets.queued_events.push(AssetEvent::Unused { id }); + if drop_event.asset_server_managed { - let untyped = id.untyped(TypeId::of::()); - if let Some(info) = infos.get(untyped) { + let untyped_id = drop_event.id.untyped(TypeId::of::()); + if let Some(info) = infos.get(untyped_id) { if info.load_state == LoadState::Loading || info.load_state == LoadState::NotLoaded { @@ -507,13 +510,14 @@ impl Assets { continue; } } - if infos.process_handle_drop(untyped) { - assets.remove_dropped(id.typed()); + if infos.process_handle_drop(untyped_id) { + assets.remove_dropped(id); } } else { - assets.remove_dropped(id.typed()); + assets.remove_dropped(id); } } + // TODO: this is _extremely_ inefficient find a better fix // This will also loop failed assets indefinitely. Is that ok? for event in not_ready { diff --git a/crates/bevy_asset/src/event.rs b/crates/bevy_asset/src/event.rs index 96f10a9c6f..a7c1ce422b 100644 --- a/crates/bevy_asset/src/event.rs +++ b/crates/bevy_asset/src/event.rs @@ -11,6 +11,8 @@ pub enum AssetEvent { Modified { id: AssetId }, /// Emitted whenever an [`Asset`] is removed. Removed { id: AssetId }, + /// Emitted when the last [`super::Handle::Strong`] of an [`Asset`] is dropped. + Unused { id: AssetId }, /// Emitted whenever an [`Asset`] has been fully loaded (including its dependencies and all "recursive dependencies"). LoadedWithDependencies { id: AssetId }, } @@ -35,6 +37,11 @@ impl AssetEvent { pub fn is_removed(&self, asset_id: impl Into>) -> bool { matches!(self, AssetEvent::Removed { id } if *id == asset_id.into()) } + + /// Returns `true` if this event is [`AssetEvent::Unused`] and matches the given `id`. + pub fn is_unused(&self, asset_id: impl Into>) -> bool { + matches!(self, AssetEvent::Unused { id } if *id == asset_id.into()) + } } impl Clone for AssetEvent { @@ -51,6 +58,7 @@ impl Debug for AssetEvent { Self::Added { id } => f.debug_struct("Added").field("id", id).finish(), Self::Modified { id } => f.debug_struct("Modified").field("id", id).finish(), Self::Removed { id } => f.debug_struct("Removed").field("id", id).finish(), + Self::Unused { id } => f.debug_struct("Unused").field("id", id).finish(), Self::LoadedWithDependencies { id } => f .debug_struct("LoadedWithDependencies") .field("id", id) @@ -65,6 +73,7 @@ impl PartialEq for AssetEvent { (Self::Added { id: l_id }, Self::Added { id: r_id }) | (Self::Modified { id: l_id }, Self::Modified { id: r_id }) | (Self::Removed { id: l_id }, Self::Removed { id: r_id }) + | (Self::Unused { id: l_id }, Self::Unused { id: r_id }) | ( Self::LoadedWithDependencies { id: l_id }, Self::LoadedWithDependencies { id: r_id }, diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 103475f8ba..1f9b8fb608 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -124,7 +124,7 @@ impl std::fmt::Debug for StrongHandle { #[reflect(Component)] pub enum Handle { /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept - /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata. + /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata. Strong(Arc), /// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`], /// nor will it keep assets alive. @@ -189,7 +189,7 @@ impl Handle { /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for - /// [`Handle::Weak`]. + /// [`Handle::Weak`]. #[inline] pub fn untyped(self) -> UntypedHandle { self.into() diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 321ad57937..f85102f0c8 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -100,7 +100,7 @@ pub enum AssetMode { /// /// When developing an app, you should enable the `asset_processor` cargo feature, which will run the asset processor at startup. This should generally /// be used in combination with the `file_watcher` cargo feature, which enables hot-reloading of assets that have changed. When both features are enabled, - /// changes to "original/source assets" will be detected, the asset will be re-processed, and then the final processed asset will be hot-reloaded in the app. + /// changes to "original/source assets" will be detected, the asset will be re-processed, and then the final processed asset will be hot-reloaded in the app. /// /// [`AssetMeta`]: meta::AssetMeta /// [`AssetSource`]: io::AssetSource @@ -872,13 +872,23 @@ mod tests { id: id_results.d_id, }, AssetEvent::Modified { id: a_id }, + AssetEvent::Unused { id: a_id }, AssetEvent::Removed { id: a_id }, - AssetEvent::Removed { + AssetEvent::Unused { id: id_results.b_id, }, AssetEvent::Removed { + id: id_results.b_id, + }, + AssetEvent::Unused { id: id_results.c_id, }, + AssetEvent::Removed { + id: id_results.c_id, + }, + AssetEvent::Unused { + id: id_results.d_id, + }, AssetEvent::Removed { id: id_results.d_id, }, @@ -1062,7 +1072,11 @@ mod tests { // remove event is emitted app.update(); let events = std::mem::take(&mut app.world.resource_mut::().0); - let expected_events = vec![AssetEvent::Added { id }, AssetEvent::Removed { id }]; + let expected_events = vec![ + AssetEvent::Added { id }, + AssetEvent::Unused { id }, + AssetEvent::Removed { id }, + ]; assert_eq!(events, expected_events); let dep_handle = app.world.resource::().load(dep_path); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 20c3b87bb6..8e277a2e50 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -6,7 +6,7 @@ use bevy_reflect::Reflect; use bevy_render::camera::Camera; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::extract_resource::{ExtractResource, ExtractResourcePlugin}; -use bevy_render::render_asset::RenderAssets; +use bevy_render::render_asset::{RenderAssetPersistencePolicy, RenderAssets}; use bevy_render::render_resource::binding_types::{ sampler, texture_2d, texture_3d, uniform_buffer, }; @@ -356,6 +356,7 @@ fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image { CompressedImageFormats::NONE, false, image_sampler, + RenderAssetPersistencePolicy::Unload, ) .unwrap() } @@ -381,5 +382,6 @@ pub fn lut_placeholder() -> Image { }, sampler: ImageSampler::Default, texture_view_descriptor: None, + cpu_persistent_access: RenderAssetPersistencePolicy::Unload, } } diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 178b9680ca..17de4bafca 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -51,7 +51,10 @@ use bevy_render::{ color::Color, extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, primitives::Aabb, - render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_asset::{ + PrepareAssetError, RenderAsset, RenderAssetPersistencePolicy, RenderAssetPlugin, + RenderAssets, + }, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, @@ -365,28 +368,25 @@ struct GpuLineGizmo { } impl RenderAsset for LineGizmo { - type ExtractedAsset = LineGizmo; - type PreparedAsset = GpuLineGizmo; - type Param = SRes; - fn extract_asset(&self) -> Self::ExtractedAsset { - self.clone() + fn persistence_policy(&self) -> RenderAssetPersistencePolicy { + RenderAssetPersistencePolicy::Unload } fn prepare_asset( - line_gizmo: Self::ExtractedAsset, + self, render_device: &mut SystemParamItem, - ) -> Result> { - let position_buffer_data = cast_slice(&line_gizmo.positions); + ) -> Result> { + let position_buffer_data = cast_slice(&self.positions); let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("LineGizmo Position Buffer"), contents: position_buffer_data, }); - let color_buffer_data = cast_slice(&line_gizmo.colors); + let color_buffer_data = cast_slice(&self.colors); let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("LineGizmo Color Buffer"), @@ -396,8 +396,8 @@ impl RenderAsset for LineGizmo { Ok(GpuLineGizmo { position_buffer, color_buffer, - vertex_count: line_gizmo.positions.len() as u32, - strip: line_gizmo.strip, + vertex_count: self.positions.len() as u32, + strip: self.strip, }) } } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 6572499b18..f33ee07e94 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/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), }) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 2a68125ddc..51bef6ed71 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -822,6 +822,7 @@ pub fn extract_materials( let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { + #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); @@ -830,6 +831,7 @@ pub fn extract_materials( changed_assets.remove(id); removed.push(*id); } + AssetEvent::Unused { .. } => {} AssetEvent::LoadedWithDependencies { .. } => { // TODO: handle this } diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 84e8b3fc76..270fd86cb1 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -5,7 +5,7 @@ pub use wgpu::PrimitiveTopology; use crate::{ prelude::Image, primitives::Aabb, - render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPersistencePolicy, RenderAssets}, render_resource::{Buffer, TextureView, VertexBufferLayout}, renderer::RenderDevice, }; @@ -48,9 +48,10 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// ``` /// # use bevy_render::mesh::{Mesh, Indices}; /// # use bevy_render::render_resource::PrimitiveTopology; +/// # use bevy_render::render_asset::RenderAssetPersistencePolicy; /// fn create_simple_parallelogram() -> Mesh { /// // Create a new mesh using a triangle list topology, where each set of 3 vertices composes a triangle. -/// Mesh::new(PrimitiveTopology::TriangleList) +/// Mesh::new(PrimitiveTopology::TriangleList, RenderAssetPersistencePolicy::Unload) /// // Add 4 vertices, each with its own position attribute (coordinate in /// // 3D space), for each of the corners of the parallelogram. /// .with_inserted_attribute( @@ -108,8 +109,6 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// - Vertex winding order: by default, `StandardMaterial.cull_mode` is [`Some(Face::Back)`](crate::render_resource::Face), /// which means that Bevy would *only* render the "front" of each triangle, which /// is the side of the triangle from where the vertices appear in a *counter-clockwise* order. -/// -// TODO: allow values to be unloaded after been submitting to the GPU to conserve memory #[derive(Asset, Debug, Clone, Reflect)] pub struct Mesh { #[reflect(ignore)] @@ -123,6 +122,7 @@ pub struct Mesh { indices: Option, morph_targets: Option>, morph_target_names: Option>, + pub cpu_persistent_access: RenderAssetPersistencePolicy, } impl Mesh { @@ -183,13 +183,17 @@ impl Mesh { /// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the /// renderer knows how to treat the vertex data. Most of the time this will be /// [`PrimitiveTopology::TriangleList`]. - pub fn new(primitive_topology: PrimitiveTopology) -> Self { + pub fn new( + primitive_topology: PrimitiveTopology, + cpu_persistent_access: RenderAssetPersistencePolicy, + ) -> Self { Mesh { primitive_topology, attributes: Default::default(), indices: None, morph_targets: None, morph_target_names: None, + cpu_persistent_access, } } @@ -1057,50 +1061,48 @@ pub enum GpuBufferInfo { } impl RenderAsset for Mesh { - type ExtractedAsset = Mesh; type PreparedAsset = GpuMesh; type Param = (SRes, SRes>); - /// Clones the mesh. - fn extract_asset(&self) -> Self::ExtractedAsset { - self.clone() + fn persistence_policy(&self) -> RenderAssetPersistencePolicy { + self.cpu_persistent_access } /// Converts the extracted mesh a into [`GpuMesh`]. fn prepare_asset( - mesh: Self::ExtractedAsset, + self, (render_device, images): &mut SystemParamItem, - ) -> Result> { - let vertex_buffer_data = mesh.get_vertex_buffer_data(); + ) -> Result> { + let vertex_buffer_data = self.get_vertex_buffer_data(); let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("Mesh Vertex Buffer"), contents: &vertex_buffer_data, }); - let buffer_info = if let Some(data) = mesh.get_index_buffer_bytes() { + let buffer_info = if let Some(data) = self.get_index_buffer_bytes() { GpuBufferInfo::Indexed { buffer: render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::INDEX, contents: data, label: Some("Mesh Index Buffer"), }), - count: mesh.indices().unwrap().len() as u32, - index_format: mesh.indices().unwrap().into(), + count: self.indices().unwrap().len() as u32, + index_format: self.indices().unwrap().into(), } } else { GpuBufferInfo::NonIndexed }; - let mesh_vertex_buffer_layout = mesh.get_mesh_vertex_buffer_layout(); + let mesh_vertex_buffer_layout = self.get_mesh_vertex_buffer_layout(); Ok(GpuMesh { vertex_buffer, - vertex_count: mesh.count_vertices() as u32, + vertex_count: self.count_vertices() as u32, buffer_info, - primitive_topology: mesh.primitive_topology(), + primitive_topology: self.primitive_topology(), layout: mesh_vertex_buffer_layout, - morph_targets: mesh + morph_targets: self .morph_targets .and_then(|mt| images.get(&mt).map(|i| i.texture_view.clone())), }) @@ -1231,12 +1233,16 @@ fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTang #[cfg(test)] mod tests { use super::Mesh; + use crate::render_asset::RenderAssetPersistencePolicy; use wgpu::PrimitiveTopology; #[test] #[should_panic] fn panic_invalid_format() { - let _mesh = Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]); + let _mesh = Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]); } } diff --git a/crates/bevy_render/src/mesh/morph.rs b/crates/bevy_render/src/mesh/morph.rs index cb523113be..bde10bd3ca 100644 --- a/crates/bevy_render/src/mesh/morph.rs +++ b/crates/bevy_render/src/mesh/morph.rs @@ -1,5 +1,6 @@ use crate::{ mesh::Mesh, + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::Image, }; @@ -67,6 +68,7 @@ impl MorphTargetImage { pub fn new( targets: impl ExactSizeIterator>, vertex_count: usize, + cpu_persistent_access: RenderAssetPersistencePolicy, ) -> Result { let max = MAX_TEXTURE_WIDTH; let target_count = targets.len(); @@ -101,7 +103,13 @@ impl MorphTargetImage { height, depth_or_array_layers: target_count as u32, }; - let image = Image::new(extents, TextureDimension::D3, data, TextureFormat::R32Float); + let image = Image::new( + extents, + TextureDimension::D3, + data, + TextureFormat::R32Float, + cpu_persistent_access, + ); Ok(MorphTargetImage(image)) } } @@ -114,7 +122,7 @@ impl MorphTargetImage { /// This exists because Bevy's [`Mesh`] corresponds to a _single_ surface / material, whereas morph targets /// as defined in the GLTF spec exist on "multi-primitive meshes" (where each primitive is its own surface with its own material). /// Therefore in Bevy [`MorphWeights`] an a parent entity are the "canonical weights" from a GLTF perspective, which then -/// synchronized to child [`Handle`] / [`MeshMorphWeights`] (which correspond to "primitives" / "surfaces" from a GLTF perspective). +/// synchronized to child [`Handle`] / [`MeshMorphWeights`] (which correspond to "primitives" / "surfaces" from a GLTF perspective). /// /// Add this to the parent of one or more [`Entities`](`Entity`) with a [`Handle`] with a [`MeshMorphWeights`]. /// diff --git a/crates/bevy_render/src/mesh/shape/capsule.rs b/crates/bevy_render/src/mesh/shape/capsule.rs index d438ed28bc..00dc9fd285 100644 --- a/crates/bevy_render/src/mesh/shape/capsule.rs +++ b/crates/bevy_render/src/mesh/shape/capsule.rs @@ -1,4 +1,7 @@ -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use bevy_math::{Vec2, Vec3}; use wgpu::PrimitiveTopology; @@ -364,10 +367,13 @@ impl From for Mesh { assert_eq!(vs.len(), vert_len); assert_eq!(tris.len(), fs_len); - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts) - .with_indices(Some(Indices::U32(tris))) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts) + .with_indices(Some(Indices::U32(tris))) } } diff --git a/crates/bevy_render/src/mesh/shape/cylinder.rs b/crates/bevy_render/src/mesh/shape/cylinder.rs index a4d517ac73..7f959628bc 100644 --- a/crates/bevy_render/src/mesh/shape/cylinder.rs +++ b/crates/bevy_render/src/mesh/shape/cylinder.rs @@ -1,4 +1,7 @@ -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use wgpu::PrimitiveTopology; /// A cylinder which stands on the XZ plane @@ -118,10 +121,13 @@ impl From for Mesh { build_cap(true); build_cap(false); - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(Indices::U32(indices))) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(Indices::U32(indices))) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_render/src/mesh/shape/icosphere.rs b/crates/bevy_render/src/mesh/shape/icosphere.rs index 457ea0f826..0ee936d218 100644 --- a/crates/bevy_render/src/mesh/shape/icosphere.rs +++ b/crates/bevy_render/src/mesh/shape/icosphere.rs @@ -1,4 +1,7 @@ -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use hexasphere::shapes::IcoSphere; use thiserror::Error; use wgpu::PrimitiveTopology; @@ -103,10 +106,13 @@ impl TryFrom for Mesh { let indices = Indices::U32(indices); - Ok(Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)) + Ok(Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)) } } diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index c9f6b9e149..0c421c41a2 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -1,3 +1,5 @@ +use crate::render_asset::RenderAssetPersistencePolicy; + use super::{Indices, Mesh}; use bevy_math::*; @@ -120,11 +122,14 @@ impl From for Mesh { 20, 21, 22, 22, 23, 20, // bottom ]); - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(indices)) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(indices)) } } @@ -172,11 +177,14 @@ impl From for Mesh { let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } @@ -253,11 +261,14 @@ impl From for Mesh { } } - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(Indices::U32(indices))) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(Indices::U32(indices))) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_render/src/mesh/shape/regular_polygon.rs b/crates/bevy_render/src/mesh/shape/regular_polygon.rs index 879c59fabd..8369904c6d 100644 --- a/crates/bevy_render/src/mesh/shape/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/shape/regular_polygon.rs @@ -1,4 +1,7 @@ -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use wgpu::PrimitiveTopology; /// A regular polygon in the `XY` plane @@ -55,11 +58,14 @@ impl From for Mesh { indices.extend_from_slice(&[0, i + 1, i]); } - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) } } diff --git a/crates/bevy_render/src/mesh/shape/torus.rs b/crates/bevy_render/src/mesh/shape/torus.rs index 5254fcceeb..dc3664f626 100644 --- a/crates/bevy_render/src/mesh/shape/torus.rs +++ b/crates/bevy_render/src/mesh/shape/torus.rs @@ -1,4 +1,7 @@ -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use bevy_math::Vec3; use wgpu::PrimitiveTopology; @@ -84,10 +87,13 @@ impl From for Mesh { } } - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(Indices::U32(indices))) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(Indices::U32(indices))) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_render/src/mesh/shape/uvsphere.rs b/crates/bevy_render/src/mesh/shape/uvsphere.rs index b6b89ebc40..dd3e29fd0f 100644 --- a/crates/bevy_render/src/mesh/shape/uvsphere.rs +++ b/crates/bevy_render/src/mesh/shape/uvsphere.rs @@ -1,6 +1,9 @@ use wgpu::PrimitiveTopology; -use crate::mesh::{Indices, Mesh}; +use crate::{ + mesh::{Indices, Mesh}, + render_asset::RenderAssetPersistencePolicy, +}; use std::f32::consts::PI; /// A sphere made of sectors and stacks. @@ -80,10 +83,13 @@ impl From for Mesh { } } - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(Indices::U32(indices))) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + .with_indices(Some(Indices::U32(indices))) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index e97874544a..da0adac338 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -1,12 +1,14 @@ -use crate::{Extract, ExtractSchedule, Render, RenderApp, RenderSet}; +use crate::{ExtractSchedule, MainWorld, Render, RenderApp, RenderSet}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, AssetId, Assets}; use bevy_ecs::{ - prelude::*, + prelude::{Commands, EventReader, IntoSystemConfigs, ResMut, Resource}, schedule::SystemConfigs, - system::{StaticSystemParam, SystemParam, SystemParamItem}, + system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState}, }; +use bevy_reflect::Reflect; use bevy_utils::{thiserror::Error, HashMap, HashSet}; +use serde::{Deserialize, Serialize}; use std::marker::PhantomData; #[derive(Debug, Error)] @@ -19,27 +21,43 @@ pub enum PrepareAssetError { /// /// In the [`ExtractSchedule`] step the asset is transferred /// from the "main world" into the "render world". -/// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type -/// as the render asset itself. /// /// After that in the [`RenderSet::PrepareAssets`] step the extracted asset /// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`]. -pub trait RenderAsset: Asset { - /// The representation of the asset in the "render world". - type ExtractedAsset: Send + Sync + 'static; +pub trait RenderAsset: Asset + Clone { /// The GPU-representation of the asset. type PreparedAsset: Send + Sync + 'static; + /// Specifies all ECS data required by [`RenderAsset::prepare_asset`]. + /// /// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) [`SystemParam`]. type Param: SystemParam; - /// Converts the asset into a [`RenderAsset::ExtractedAsset`]. - fn extract_asset(&self) -> Self::ExtractedAsset; - /// Prepares the `extracted asset` for the GPU by transforming it into - /// a [`RenderAsset::PreparedAsset`]. Therefore ECS data may be accessed via the `param`. + + /// Whether or not to unload the asset after extracting it to the render world. + fn persistence_policy(&self) -> RenderAssetPersistencePolicy; + + /// Prepares the asset for the GPU by transforming it into a [`RenderAsset::PreparedAsset`]. + /// + /// ECS data may be accessed via `param`. fn prepare_asset( - extracted_asset: Self::ExtractedAsset, + self, param: &mut SystemParamItem, - ) -> Result>; + ) -> Result>; +} + +/// Whether or not to unload the [`RenderAsset`] after extracting it to the render world. +/// +/// Unloading the asset saves on memory, as for most cases it is no longer necessary to keep +/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you can no longer +/// access the asset from the CPU (via the `Assets` resource) once unloaded (without re-loading it). +/// +/// If you never need access to the asset from the CPU past the first frame it's loaded on, +/// or only need very infrequent access, then set this to Unload. Otherwise, set this to Keep. +#[derive(Reflect, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)] +pub enum RenderAssetPersistencePolicy { + #[default] + Unload, + Keep, } /// This plugin extracts the changed assets from the "app world" into the "render world" @@ -104,7 +122,7 @@ impl RenderAssetDependency for A { /// Temporarily stores the extracted and removed assets of the current frame. #[derive(Resource)] pub struct ExtractedAssets { - extracted: Vec<(AssetId, A::ExtractedAsset)>, + extracted: Vec<(AssetId, A)>, removed: Vec>, } @@ -160,19 +178,21 @@ impl RenderAssets { /// This system extracts all created or modified assets of the corresponding [`RenderAsset`] type /// into the "render world". -fn extract_render_asset( - mut commands: Commands, - mut events: Extract>>, - assets: Extract>>, -) { +fn extract_render_asset(mut commands: Commands, mut main_world: ResMut) { + let mut system_state: SystemState<(EventReader>, ResMut>)> = + SystemState::new(&mut main_world); + let (mut events, mut assets) = system_state.get_mut(&mut main_world); + let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { + #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); } - AssetEvent::Removed { id } => { + AssetEvent::Removed { .. } => {} + AssetEvent::Unused { id } => { changed_assets.remove(id); removed.push(*id); } @@ -185,7 +205,13 @@ fn extract_render_asset( let mut extracted_assets = Vec::new(); for id in changed_assets.drain() { if let Some(asset) = assets.get(id) { - extracted_assets.push((id, asset.extract_asset())); + if asset.persistence_policy() == RenderAssetPersistencePolicy::Unload { + if let Some(asset) = assets.remove(id) { + extracted_assets.push((id, asset)); + } + } else { + extracted_assets.push((id, asset.clone())); + } } } @@ -199,7 +225,7 @@ fn extract_render_asset( /// All assets that should be prepared next frame. #[derive(Resource)] pub struct PrepareNextFrameAssets { - assets: Vec<(AssetId, A::ExtractedAsset)>, + assets: Vec<(AssetId, A)>, } impl Default for PrepareNextFrameAssets { @@ -212,16 +238,16 @@ impl Default for PrepareNextFrameAssets { /// This system prepares all assets of the corresponding [`RenderAsset`] type /// which where extracted this frame for the GPU. -pub fn prepare_assets( - mut extracted_assets: ResMut>, - mut render_assets: ResMut>, - mut prepare_next_frame: ResMut>, - param: StaticSystemParam<::Param>, +pub fn prepare_assets( + mut extracted_assets: ResMut>, + mut render_assets: ResMut>, + mut prepare_next_frame: ResMut>, + param: StaticSystemParam<::Param>, ) { let mut param = param.into_inner(); let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (id, extracted_asset) in queued_assets { - match R::prepare_asset(extracted_asset, &mut param) { + match extracted_asset.prepare_asset(&mut param) { Ok(prepared_asset) => { render_assets.insert(id, prepared_asset); } @@ -231,12 +257,12 @@ pub fn prepare_assets( } } - for removed in std::mem::take(&mut extracted_assets.removed) { + for removed in extracted_assets.removed.drain(..) { render_assets.remove(removed); } - for (id, extracted_asset) in std::mem::take(&mut extracted_assets.extracted) { - match R::prepare_asset(extracted_asset, &mut param) { + for (id, extracted_asset) in extracted_assets.extracted.drain(..) { + match extracted_asset.prepare_asset(&mut param) { Ok(prepared_asset) => { render_assets.insert(id, prepared_asset); } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index bb6d9212b7..d7197cbd51 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -848,6 +848,7 @@ impl PipelineCache { mut events: Extract>>, ) { for event in events.read() { + #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { if let Some(shader) = shaders.get(*id) { @@ -855,6 +856,7 @@ impl PipelineCache { } } AssetEvent::Removed { id } => cache.remove_shader(*id), + AssetEvent::Unused { .. } => {} AssetEvent::LoadedWithDependencies { .. } => { // TODO: handle this } diff --git a/crates/bevy_render/src/texture/compressed_image_saver.rs b/crates/bevy_render/src/texture/compressed_image_saver.rs index a5f296b566..89414ea603 100644 --- a/crates/bevy_render/src/texture/compressed_image_saver.rs +++ b/crates/bevy_render/src/texture/compressed_image_saver.rs @@ -56,6 +56,7 @@ impl AssetSaver for CompressedImageSaver { format: ImageFormatSetting::Format(ImageFormat::Basis), is_srgb, sampler: image.sampler.clone(), + cpu_persistent_access: image.cpu_persistent_access, }) } .boxed() diff --git a/crates/bevy_render/src/texture/exr_texture_loader.rs b/crates/bevy_render/src/texture/exr_texture_loader.rs index e5494e9935..925a468a51 100644 --- a/crates/bevy_render/src/texture/exr_texture_loader.rs +++ b/crates/bevy_render/src/texture/exr_texture_loader.rs @@ -1,10 +1,14 @@ -use crate::texture::{Image, TextureFormatPixelInfo}; +use crate::{ + render_asset::RenderAssetPersistencePolicy, + texture::{Image, TextureFormatPixelInfo}, +}; use bevy_asset::{ io::{AsyncReadExt, Reader}, AssetLoader, LoadContext, }; use bevy_utils::BoxedFuture; use image::ImageDecoder; +use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu::{Extent3d, TextureDimension, TextureFormat}; @@ -12,6 +16,11 @@ use wgpu::{Extent3d, TextureDimension, TextureFormat}; #[derive(Clone, Default)] pub struct ExrTextureLoader; +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct ExrTextureLoaderSettings { + pub cpu_persistent_access: RenderAssetPersistencePolicy, +} + /// Possible errors that can be produced by [`ExrTextureLoader`] #[non_exhaustive] #[derive(Debug, Error)] @@ -24,13 +33,13 @@ pub enum ExrTextureLoaderError { impl AssetLoader for ExrTextureLoader { type Asset = Image; - type Settings = (); + type Settings = ExrTextureLoaderSettings; type Error = ExrTextureLoaderError; fn load<'a>( &'a self, reader: &'a mut Reader, - _settings: &'a Self::Settings, + settings: &'a Self::Settings, _load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result> { Box::pin(async move { @@ -63,6 +72,7 @@ impl AssetLoader for ExrTextureLoader { TextureDimension::D2, buf, format, + settings.cpu_persistent_access, )) }) } diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index e4afc211a7..911881f12f 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -1,4 +1,6 @@ -use crate::{render_resource::*, texture::DefaultImageSampler}; +use crate::{ + render_asset::RenderAssetPersistencePolicy, render_resource::*, texture::DefaultImageSampler, +}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::{FromWorld, Res, ResMut}, @@ -76,7 +78,13 @@ fn fallback_image_new( let image_dimension = dimension.compatible_texture_dimension(); let mut image = if create_texture_with_data { let data = vec![value; format.pixel_size()]; - Image::new_fill(extents, image_dimension, &data, format) + Image::new_fill( + extents, + image_dimension, + &data, + format, + RenderAssetPersistencePolicy::Unload, + ) } else { let mut image = Image::default(); image.texture_descriptor.dimension = TextureDimension::D2; diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index e54b38b806..5358b6fb44 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,5 +1,9 @@ -use crate::texture::{Image, TextureFormatPixelInfo}; +use crate::{ + render_asset::RenderAssetPersistencePolicy, + texture::{Image, TextureFormatPixelInfo}, +}; use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu::{Extent3d, TextureDimension, TextureFormat}; @@ -7,6 +11,11 @@ use wgpu::{Extent3d, TextureDimension, TextureFormat}; #[derive(Clone, Default)] pub struct HdrTextureLoader; +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct HdrTextureLoaderSettings { + pub cpu_persistent_access: RenderAssetPersistencePolicy, +} + #[non_exhaustive] #[derive(Debug, Error)] pub enum HdrTextureLoaderError { @@ -18,12 +27,12 @@ pub enum HdrTextureLoaderError { impl AssetLoader for HdrTextureLoader { type Asset = Image; - type Settings = (); + type Settings = HdrTextureLoaderSettings; type Error = HdrTextureLoaderError; fn load<'a>( &'a self, reader: &'a mut Reader, - _settings: &'a (), + settings: &'a Self::Settings, _load_context: &'a mut LoadContext, ) -> bevy_utils::BoxedFuture<'a, Result> { Box::pin(async move { @@ -59,6 +68,7 @@ impl AssetLoader for HdrTextureLoader { TextureDimension::D2, rgba_data, format, + settings.cpu_persistent_access, )) }) } diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index c3b8cc876c..35cbab7db6 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -6,7 +6,7 @@ use super::dds::*; use super::ktx2::*; use crate::{ - render_asset::{PrepareAssetError, RenderAsset}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPersistencePolicy}, render_resource::{Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::BevyDefault, @@ -110,6 +110,7 @@ pub struct Image { /// The [`ImageSampler`] to use during rendering. pub sampler: ImageSampler, pub texture_view_descriptor: Option>, + pub cpu_persistent_access: RenderAssetPersistencePolicy, } /// Used in [`Image`], this determines what image sampler to use when rendering. The default setting, @@ -466,6 +467,7 @@ impl Default for Image { }, sampler: ImageSampler::Default, texture_view_descriptor: None, + cpu_persistent_access: RenderAssetPersistencePolicy::Unload, } } } @@ -481,6 +483,7 @@ impl Image { dimension: TextureDimension, data: Vec, format: TextureFormat, + cpu_persistent_access: RenderAssetPersistencePolicy, ) -> Self { debug_assert_eq!( size.volume() * format.pixel_size(), @@ -494,6 +497,7 @@ impl Image { image.texture_descriptor.dimension = dimension; image.texture_descriptor.size = size; image.texture_descriptor.format = format; + image.cpu_persistent_access = cpu_persistent_access; image } @@ -507,10 +511,12 @@ impl Image { dimension: TextureDimension, pixel: &[u8], format: TextureFormat, + cpu_persistent_access: RenderAssetPersistencePolicy, ) -> Self { let mut value = Image::default(); value.texture_descriptor.format = format; value.texture_descriptor.dimension = dimension; + value.cpu_persistent_access = cpu_persistent_access; value.resize(size); debug_assert_eq!( @@ -631,7 +637,9 @@ impl Image { } _ => None, }) - .map(|(dyn_img, is_srgb)| Self::from_dynamic(dyn_img, is_srgb)) + .map(|(dyn_img, is_srgb)| { + Self::from_dynamic(dyn_img, is_srgb, self.cpu_persistent_access) + }) } /// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image` @@ -642,6 +650,7 @@ impl Image { #[allow(unused_variables)] supported_compressed_formats: CompressedImageFormats, is_srgb: bool, image_sampler: ImageSampler, + cpu_persistent_access: RenderAssetPersistencePolicy, ) -> Result { let format = image_type.to_image_format()?; @@ -670,7 +679,7 @@ impl Image { reader.set_format(image_crate_format); reader.no_limits(); let dyn_img = reader.decode()?; - Self::from_dynamic(dyn_img, is_srgb) + Self::from_dynamic(dyn_img, is_srgb, cpu_persistent_access) } }; image.sampler = image_sampler; @@ -803,7 +812,6 @@ pub struct GpuImage { } impl RenderAsset for Image { - type ExtractedAsset = Image; type PreparedAsset = GpuImage; type Param = ( SRes, @@ -811,34 +819,32 @@ impl RenderAsset for Image { SRes, ); - /// Clones the Image. - fn extract_asset(&self) -> Self::ExtractedAsset { - self.clone() + fn persistence_policy(&self) -> RenderAssetPersistencePolicy { + self.cpu_persistent_access } /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( - image: Self::ExtractedAsset, + self, (render_device, render_queue, default_sampler): &mut SystemParamItem, - ) -> Result> { + ) -> Result> { let texture = render_device.create_texture_with_data( render_queue, - &image.texture_descriptor, - &image.data, + &self.texture_descriptor, + &self.data, ); let texture_view = texture.create_view( - image - .texture_view_descriptor + self.texture_view_descriptor .or_else(|| Some(TextureViewDescriptor::default())) .as_ref() .unwrap(), ); let size = Vec2::new( - image.texture_descriptor.size.width as f32, - image.texture_descriptor.size.height as f32, + self.texture_descriptor.size.width as f32, + self.texture_descriptor.size.height as f32, ); - let sampler = match image.sampler { + let sampler = match self.sampler { ImageSampler::Default => (***default_sampler).clone(), ImageSampler::Descriptor(descriptor) => { render_device.create_sampler(&descriptor.as_wgpu()) @@ -848,10 +854,10 @@ impl RenderAsset for Image { Ok(GpuImage { texture, texture_view, - texture_format: image.texture_descriptor.format, + texture_format: self.texture_descriptor.format, sampler, size, - mip_level_count: image.texture_descriptor.mip_level_count, + mip_level_count: self.texture_descriptor.mip_level_count, }) } } @@ -918,6 +924,7 @@ impl CompressedImageFormats { mod test { use super::*; + use crate::render_asset::RenderAssetPersistencePolicy; #[test] fn image_size() { @@ -931,6 +938,7 @@ mod test { TextureDimension::D2, &[0, 0, 0, 255], TextureFormat::Rgba8Unorm, + RenderAssetPersistencePolicy::Unload, ); assert_eq!( Vec2::new(size.width as f32, size.height as f32), diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index ca1df0b3b3..b7f8810695 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -3,6 +3,7 @@ use bevy_ecs::prelude::{FromWorld, World}; use thiserror::Error; use crate::{ + render_asset::RenderAssetPersistencePolicy, renderer::RenderDevice, texture::{Image, ImageFormat, ImageType, TextureError}, }; @@ -57,6 +58,7 @@ pub struct ImageLoaderSettings { pub format: ImageFormatSetting, pub is_srgb: bool, pub sampler: ImageSampler, + pub cpu_persistent_access: RenderAssetPersistencePolicy, } impl Default for ImageLoaderSettings { @@ -65,6 +67,7 @@ impl Default for ImageLoaderSettings { format: ImageFormatSetting::default(), is_srgb: true, sampler: ImageSampler::Default, + cpu_persistent_access: RenderAssetPersistencePolicy::Unload, } } } @@ -104,6 +107,7 @@ impl AssetLoader for ImageLoader { self.supported_compressed_formats, settings.is_srgb, settings.sampler.clone(), + settings.cpu_persistent_access, ) .map_err(|err| FileTextureError { error: err, diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index 298c39219c..999cacc005 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -1,11 +1,18 @@ -use crate::texture::{Image, TextureFormatPixelInfo}; +use crate::{ + render_asset::RenderAssetPersistencePolicy, + texture::{Image, TextureFormatPixelInfo}, +}; use image::{DynamicImage, ImageBuffer}; use thiserror::Error; use wgpu::{Extent3d, TextureDimension, TextureFormat}; impl Image { /// Converts a [`DynamicImage`] to an [`Image`]. - pub fn from_dynamic(dyn_img: DynamicImage, is_srgb: bool) -> Image { + pub fn from_dynamic( + dyn_img: DynamicImage, + is_srgb: bool, + cpu_persistent_access: RenderAssetPersistencePolicy, + ) -> Image { use bevy_core::cast_slice; let width; let height; @@ -151,6 +158,7 @@ impl Image { TextureDimension::D2, data, format, + cpu_persistent_access, ) } @@ -214,6 +222,7 @@ mod test { use image::{GenericImage, Rgba}; use super::*; + use crate::render_asset::RenderAssetPersistencePolicy; #[test] fn two_way_conversion() { @@ -221,7 +230,8 @@ mod test { let mut initial = DynamicImage::new_rgba8(1, 1); initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200])); - let image = Image::from_dynamic(initial.clone(), true); + let image = + Image::from_dynamic(initial.clone(), true, RenderAssetPersistencePolicy::Unload); // NOTE: Fails if `is_srbg = false` or the dynamic image is of the type rgb8. assert_eq!(initial, image.try_into_dynamic().unwrap()); diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 6f87a178c2..d73797e68a 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -14,6 +14,7 @@ use wgpu::{ use crate::{ prelude::{Image, Shader}, + render_asset::RenderAssetPersistencePolicy, render_resource::{ binding_types::texture_2d, BindGroup, BindGroupLayout, BindGroupLayoutEntries, Buffer, CachedRenderPipelineId, FragmentState, PipelineCache, RenderPipelineDescriptor, @@ -363,6 +364,7 @@ pub(crate) fn collect_screenshots(world: &mut World) { wgpu::TextureDimension::D2, result, texture_format, + RenderAssetPersistencePolicy::Unload, )); }; diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index fcf19bba53..28db250887 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,7 +1,10 @@ use crate::TextureAtlas; use bevy_asset::Assets; use bevy_math::{IVec2, Rect, Vec2}; -use bevy_render::texture::{Image, TextureFormatPixelInfo}; +use bevy_render::{ + render_asset::RenderAssetPersistencePolicy, + texture::{Image, TextureFormatPixelInfo}, +}; use guillotiere::{size2, Allocation, AtlasAllocator}; /// Helper utility to update [`TextureAtlas`] on the fly. @@ -28,7 +31,9 @@ impl DynamicTextureAtlasBuilder { } /// Add a new texture to [`TextureAtlas`]. - /// It is user's responsibility to pass in the correct [`TextureAtlas`] + /// It is user's responsibility to pass in the correct [`TextureAtlas`], + /// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`] + /// set to [`RenderAssetPersistencePolicy::Keep`] pub fn add_texture( &mut self, texture_atlas: &mut TextureAtlas, @@ -41,6 +46,11 @@ impl DynamicTextureAtlasBuilder { )); if let Some(allocation) = allocation { let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap(); + assert_eq!( + atlas_texture.cpu_persistent_access, + RenderAssetPersistencePolicy::Keep + ); + self.place_texture(atlas_texture, allocation, texture); let mut rect: Rect = to_rect(allocation.rectangle); rect.max -= self.padding as f32; diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 6e8965038d..702d9e7d71 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -515,6 +515,7 @@ pub fn extract_materials_2d( let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { + #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); @@ -523,7 +524,7 @@ pub fn extract_materials_2d( changed_assets.remove(id); removed.push(*id); } - + AssetEvent::Unused { .. } => {} AssetEvent::LoadedWithDependencies { .. } => { // TODO: handle this } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 5f257e3c1a..113cef88e9 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -588,8 +588,9 @@ pub fn prepare_sprites( // If an image has changed, the GpuImage has (probably) changed for event in &events.images { match event { - AssetEvent::Added {..} | - // images don't have dependencies + AssetEvent::Added { .. } | + AssetEvent::Unused { .. } | + // Images don't have dependencies AssetEvent::LoadedWithDependencies { .. } => {} AssetEvent::Modified { id } | AssetEvent::Removed { id } => { image_bind_groups.values.remove(id); diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 50ae821510..07a5e911e6 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -2,6 +2,7 @@ use bevy_asset::{AssetId, Assets}; use bevy_log::{debug, error, warn}; use bevy_math::{Rect, UVec2, Vec2}; use bevy_render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::{Image, TextureFormatPixelInfo}, }; @@ -208,6 +209,7 @@ impl TextureAtlasBuilder { self.format.pixel_size() * (current_width * current_height) as usize ], self.format, + RenderAssetPersistencePolicy::Unload, ); Some(rect_placements) } diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index 1b91430932..eba165395e 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -2,6 +2,7 @@ use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph}; use bevy_asset::Asset; use bevy_reflect::TypePath; use bevy_render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::Image, }; @@ -44,6 +45,7 @@ impl Font { .flat_map(|a| vec![255, 255, 255, (*a * 255.0) as u8]) .collect::>(), TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, ) } } diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index cdf9e09588..e46bf3cd53 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -2,6 +2,7 @@ use ab_glyph::{GlyphId, Point}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; use bevy_render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::Image, }; @@ -60,6 +61,8 @@ impl FontAtlas { TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, + // Need to keep this image CPU persistent in order to add additional glyphs later on + RenderAssetPersistencePolicy::Keep, )); let texture_atlas = TextureAtlas::new_empty(atlas_texture, size); Self { diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 402ba81328..c1c4a9f370 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -789,6 +789,7 @@ pub fn prepare_uinodes( for event in &events.images { match event { AssetEvent::Added { .. } | + AssetEvent::Unused { .. } | // Images don't have dependencies AssetEvent::LoadedWithDependencies { .. } => {} AssetEvent::Modified { id } | AssetEvent::Removed { id } => { diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 348d112099..9f17afc930 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -615,6 +615,7 @@ pub fn extract_ui_materials( let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { + #[allow(clippy::match_same_arms)] match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); @@ -623,8 +624,9 @@ pub fn extract_ui_materials( changed_assets.remove(id); removed.push(*id); } + AssetEvent::Unused { .. } => {} AssetEvent::LoadedWithDependencies { .. } => { - // not implemented + // TODO: handle this } } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index dd88584dcf..57f29a2dfc 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -216,7 +216,7 @@ fn queue_text( /// ## World Resources /// /// [`ResMut>`](Assets) -- This system only adds new [`Image`] assets. -/// It does not modify or observe existing ones. +/// It does not modify or observe existing ones. The exception is when adding new glyphs to a [`bevy_text::FontAtlas`]. #[allow(clippy::too_many_arguments)] pub fn text_system( mut textures: ResMut>, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index bf3501c2a0..f8fdaf8790 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -10,6 +10,7 @@ use bevy::{ prelude::*, render::{ mesh::{Indices, MeshVertexAttribute}, + render_asset::RenderAssetPersistencePolicy, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ @@ -47,8 +48,13 @@ fn star( // We will specify here what kind of topology is used to define the mesh, // that is, how triangles are built from the vertices. We will use a // triangle list, meaning that each vertex of the triangle has to be - // specified. - let mut star = Mesh::new(PrimitiveTopology::TriangleList); + // specified. We set `cpu_persistent_access` to unload, meaning this mesh + // will not be accessible in future frames from the `meshes` resource, in + // order to save on memory once it has been uploaded to the GPU. + let mut star = Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ); // Vertices need to have a position attribute. We will use the following // vertices (I hope you can spot the star in the schema). diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 999d11a74c..8bf490b04f 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -5,7 +5,10 @@ use std::f32::consts::PI; use bevy::{ prelude::*, - render::render_resource::{Extent3d, TextureDimension, TextureFormat}, + render::{ + render_asset::RenderAssetPersistencePolicy, + render_resource::{Extent3d, TextureDimension, TextureFormat}, + }, }; fn main() { @@ -117,5 +120,6 @@ fn uv_debug_texture() -> Image { TextureDimension::D2, &texture_data, TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, ) } diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index f7b0dedc4f..04e0bf969a 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -13,6 +13,7 @@ use bevy::{ pbr::CascadeShadowConfigBuilder, prelude::*, render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::{ImageSampler, ImageSamplerDescriptor}, }, @@ -378,6 +379,7 @@ fn uv_debug_texture() -> Image { TextureDimension::D2, &texture_data, TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, ); img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor::default()); img diff --git a/examples/3d/generate_custom_mesh.rs b/examples/3d/generate_custom_mesh.rs index aada1df1b7..7948f44f28 100644 --- a/examples/3d/generate_custom_mesh.rs +++ b/examples/3d/generate_custom_mesh.rs @@ -2,9 +2,11 @@ // ! assign a custom UV mapping for a custom texture, // ! and how to change the UV mapping at run-time. use bevy::prelude::*; -use bevy::render::mesh::Indices; -use bevy::render::mesh::VertexAttributeValues; -use bevy::render::render_resource::PrimitiveTopology; +use bevy::render::{ + mesh::{Indices, VertexAttributeValues}, + render_asset::RenderAssetPersistencePolicy, + render_resource::PrimitiveTopology, +}; // Define a "marker" component to mark the custom mesh. Marker components are often used in Bevy for // filtering entities in queries with With, they're usually not queried directly since they don't contain information within them. @@ -120,7 +122,8 @@ fn input_handler( #[rustfmt::skip] fn create_cube_mesh() -> Mesh { - Mesh::new(PrimitiveTopology::TriangleList) + // Keep the mesh data accessible in future frames to be able to mutate it in toggle_texture. + Mesh::new(PrimitiveTopology::TriangleList, RenderAssetPersistencePolicy::Keep) .with_inserted_attribute( Mesh::ATTRIBUTE_POSITION, // Each array is an [x, y, z] coordinate in local space. diff --git a/examples/3d/lines.rs b/examples/3d/lines.rs index 4a71bd78ae..e12202c8ad 100644 --- a/examples/3d/lines.rs +++ b/examples/3d/lines.rs @@ -6,6 +6,7 @@ use bevy::{ reflect::TypePath, render::{ mesh::{MeshVertexBufferLayout, PrimitiveTopology}, + render_asset::RenderAssetPersistencePolicy, render_resource::{ AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError, @@ -94,11 +95,14 @@ impl From for Mesh { fn from(line: LineList) -> Self { let vertices: Vec<_> = line.lines.into_iter().flat_map(|(a, b)| [a, b]).collect(); - // This tells wgpu that the positions are list of lines - // where every pair is a start and end point - Mesh::new(PrimitiveTopology::LineList) - // Add the vertices positions as an attribute - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + Mesh::new( + // This tells wgpu that the positions are list of lines + // where every pair is a start and end point + PrimitiveTopology::LineList, + RenderAssetPersistencePolicy::Unload, + ) + // Add the vertices positions as an attribute + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) } } @@ -110,10 +114,13 @@ pub struct LineStrip { impl From for Mesh { fn from(line: LineStrip) -> Self { - // This tells wgpu that the positions are a list of points - // where a line will be drawn between each consecutive point - Mesh::new(PrimitiveTopology::LineStrip) - // Add the point positions as an attribute - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, line.points) + Mesh::new( + // This tells wgpu that the positions are a list of points + // where a line will be drawn between each consecutive point + PrimitiveTopology::LineStrip, + RenderAssetPersistencePolicy::Unload, + ) + // Add the point positions as an attribute + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, line.points) } } diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index 921750684b..530d07a9da 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -7,6 +7,7 @@ use bevy::{ prelude::*, reflect::TypePath, render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{AsBindGroup, Extent3d, ShaderRef, TextureDimension, TextureFormat}, texture::{ImageSampler, ImageSamplerDescriptor}, view::ColorGrading, @@ -678,6 +679,7 @@ fn uv_debug_texture() -> Image { TextureDimension::D2, &texture_data, TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, ); img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor::default()); img diff --git a/examples/animation/custom_skinned_mesh.rs b/examples/animation/custom_skinned_mesh.rs index 7f349e2846..a5c23af8db 100644 --- a/examples/animation/custom_skinned_mesh.rs +++ b/examples/animation/custom_skinned_mesh.rs @@ -6,9 +6,12 @@ use std::f32::consts::*; use bevy::{ pbr::AmbientLight, prelude::*, - render::mesh::{ - skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - Indices, PrimitiveTopology, VertexAttributeValues, + render::{ + mesh::{ + skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, + Indices, PrimitiveTopology, VertexAttributeValues, + }, + render_asset::RenderAssetPersistencePolicy, }, }; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -52,68 +55,71 @@ fn setup( ])); // Create a mesh - let mesh = Mesh::new(PrimitiveTopology::TriangleList) - // Set mesh vertex positions - .with_inserted_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 0.5, 0.0], - [1.0, 0.5, 0.0], - [0.0, 1.0, 0.0], - [1.0, 1.0, 0.0], - [0.0, 1.5, 0.0], - [1.0, 1.5, 0.0], - [0.0, 2.0, 0.0], - [1.0, 2.0, 0.0], - ], - ) - // Set mesh vertex normals - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10]) - // Set mesh vertex joint indices for mesh skinning. - // Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader - // as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component. - // This means that a maximum of 4 joints can affect a single vertex. - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_INDEX, - // Need to be explicit here as [u16; 4] could be either Uint16x4 or Unorm16x4. - VertexAttributeValues::Uint16x4(vec![ - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - ]), - ) - // Set mesh vertex joint weights for mesh skinning. - // Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it. - // The sum of these weights should equal to 1. - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_WEIGHT, - vec![ - [1.00, 0.00, 0.0, 0.0], - [1.00, 0.00, 0.0, 0.0], - [0.75, 0.25, 0.0, 0.0], - [0.75, 0.25, 0.0, 0.0], - [0.50, 0.50, 0.0, 0.0], - [0.50, 0.50, 0.0, 0.0], - [0.25, 0.75, 0.0, 0.0], - [0.25, 0.75, 0.0, 0.0], - [0.00, 1.00, 0.0, 0.0], - [0.00, 1.00, 0.0, 0.0], - ], - ) - // Tell bevy to construct triangles from a list of vertex indices, - // where each 3 vertex indices form an triangle. - .with_indices(Some(Indices::U16(vec![ - 0, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8, - ]))); + let mesh = Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetPersistencePolicy::Unload, + ) + // Set mesh vertex positions + .with_inserted_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 0.5, 0.0], + [1.0, 0.5, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.5, 0.0], + [1.0, 1.5, 0.0], + [0.0, 2.0, 0.0], + [1.0, 2.0, 0.0], + ], + ) + // Set mesh vertex normals + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10]) + // Set mesh vertex joint indices for mesh skinning. + // Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader + // as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component. + // This means that a maximum of 4 joints can affect a single vertex. + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_INDEX, + // Need to be explicit here as [u16; 4] could be either Uint16x4 or Unorm16x4. + VertexAttributeValues::Uint16x4(vec![ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + ]), + ) + // Set mesh vertex joint weights for mesh skinning. + // Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it. + // The sum of these weights should equal to 1. + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_WEIGHT, + vec![ + [1.00, 0.00, 0.0, 0.0], + [1.00, 0.00, 0.0, 0.0], + [0.75, 0.25, 0.0, 0.0], + [0.75, 0.25, 0.0, 0.0], + [0.50, 0.50, 0.0, 0.0], + [0.50, 0.50, 0.0, 0.0], + [0.25, 0.75, 0.0, 0.0], + [0.25, 0.75, 0.0, 0.0], + [0.00, 1.00, 0.0, 0.0], + [0.00, 1.00, 0.0, 0.0], + ], + ) + // Tell bevy to construct triangles from a list of vertex indices, + // where each 3 vertex indices form an triangle. + .with_indices(Some(Indices::U16(vec![ + 0, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8, + ]))); let mesh = meshes.add(mesh); diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 81afe9b662..df16d67b17 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -7,6 +7,7 @@ use bevy::{ prelude::*, render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, + render_asset::RenderAssetPersistencePolicy, render_asset::RenderAssets, render_graph::{self, RenderGraph}, render_resource::{binding_types::texture_storage_2d, *}, @@ -48,6 +49,7 @@ fn setup(mut commands: Commands, mut images: ResMut>) { TextureDimension::D2, &[0, 0, 0, 255], TextureFormat::Rgba8Unorm, + RenderAssetPersistencePolicy::Unload, ); image.texture_descriptor.usage = TextureUsages::COPY_DST | TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING; diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 90ee8b7896..811be1b403 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -8,7 +8,10 @@ use argh::FromArgs; use bevy::{ diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - render::render_resource::{Extent3d, TextureDimension, TextureFormat}, + render::{ + render_asset::RenderAssetPersistencePolicy, + render_resource::{Extent3d, TextureDimension, TextureFormat}, + }, sprite::{MaterialMesh2dBundle, Mesh2dHandle}, utils::Duration, window::{PresentMode, WindowResolution}, @@ -542,6 +545,7 @@ fn init_textures(textures: &mut Vec>, args: &Args, images: &mut As TextureDimension::D2, &pixel, TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, ))); } } diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 6a1542a025..3f45521fa9 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -15,7 +15,10 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, math::{DVec2, DVec3}, prelude::*, - render::render_resource::{Extent3d, TextureDimension, TextureFormat}, + render::{ + render_asset::RenderAssetPersistencePolicy, + render_resource::{Extent3d, TextureDimension, TextureFormat}, + }, window::{PresentMode, WindowPlugin, WindowResolution}, }; use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; @@ -198,6 +201,7 @@ fn init_textures(args: &Args, images: &mut Assets) -> Vec> TextureDimension::D2, pixel, TextureFormat::Rgba8UnormSrgb, + RenderAssetPersistencePolicy::Unload, )) }) .collect()