From d38fe29ad4f618c5e9e612698c5fc83e2f175c73 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) - 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 - 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. --- - 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`. - 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 | 18 ++- .../bevy_core_pipeline/src/tonemapping/mod.rs | 4 +- crates/bevy_gizmos/src/lib.rs | 24 ++-- crates/bevy_gltf/src/loader.rs | 6 +- 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 | 46 +++--- .../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, 495 insertions(+), 256 deletions(-) diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 34bd3e31750d2..fdd5b7415e97b 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 96f10a9c6f5ab..a7c1ce422b73a 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 5720b7479fe90..613d797d57bdf 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -123,7 +123,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. @@ -188,7 +188,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 { match self { diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 60885920c9bcd..cbd8ef73b7752 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -91,7 +91,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`]: crate::meta::AssetMeta /// [`AssetSource`]: crate::io::AssetSource @@ -863,13 +863,23 @@ mod tests { id: id_results.d_id, }, AssetEvent::Modified { id: a_id }, + AssetEvent::Unused { id: a_id }, AssetEvent::Removed { id: a_id }, + 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, }, @@ -1053,7 +1063,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 bb4dec7f3bc49..23f3503cfdc58 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::renderer::RenderDevice; use bevy_render::texture::{CompressedImageFormats, Image, ImageSampler, ImageType}; use bevy_render::view::{ViewTarget, ViewUniform}; @@ -387,6 +387,7 @@ fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image { CompressedImageFormats::NONE, false, image_sampler, + RenderAssetPersistencePolicy::Unload, ) .unwrap() } @@ -412,5 +413,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 446605b43e4f8..23e3481225384 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -49,7 +49,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::{ BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor, @@ -367,28 +370,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"), @@ -398,8 +398,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 44b11b1e7c51c..8864e5383c45e 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, @@ -352,7 +353,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() { @@ -396,6 +397,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); @@ -686,6 +688,7 @@ async fn load_image<'a, 'b>( supported_compressed_formats, is_srgb, ImageSampler::Descriptor(sampler_descriptor), + RenderAssetPersistencePolicy::Unload, )?; Ok(ImageOrPath::Image { image, @@ -707,6 +710,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 d5884bb65530c..242337f5fc9ba 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -809,6 +809,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); @@ -817,6 +818,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 3ba1998333bf4..fb3464b0deedc 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 { @@ -180,13 +180,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, } } @@ -1052,50 +1056,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())), }) @@ -1232,12 +1234,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 cb523113be231..bde10bd3cad69 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 d438ed28bc3ee..00dc9fd28542c 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 a4d517ac73952..7f959628bc796 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 457ea0f82661d..0ee936d218b2e 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 c9f6b9e1492bf..0c421c41a2a9a 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 c2e86368b15f3..f7f169429c696 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 @@ -53,11 +56,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 5254fcceebc01..dc3664f626bbc 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 b6b89ebc40157..dd3e29fd0f19d 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 e97874544a630..da0adac338dac 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 3eb6f8ae43ef9..164256fd7ccb6 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -844,6 +844,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) { @@ -851,6 +852,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 5fe533ba196c4..c7e60be4531cb 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 e5494e9935d52..925a468a51aa3 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 e4afc211a744c..911881f12fd8c 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 e54b38b806606..5358b6fb440f2 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 45867dd890538..b7fe241429749 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, @@ -109,7 +109,8 @@ pub struct Image { pub texture_descriptor: wgpu::TextureDescriptor<'static>, /// The [`ImageSampler`] to use during rendering. pub sampler: ImageSampler, - pub texture_view_descriptor: Option>, + 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, @@ -464,6 +465,7 @@ impl Default for Image { }, sampler: ImageSampler::Default, texture_view_descriptor: None, + cpu_persistent_access: RenderAssetPersistencePolicy::Unload, } } } @@ -479,6 +481,7 @@ impl Image { dimension: TextureDimension, data: Vec, format: TextureFormat, + cpu_persistent_access: RenderAssetPersistencePolicy, ) -> Self { debug_assert_eq!( size.volume() * format.pixel_size(), @@ -492,6 +495,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 } @@ -505,10 +509,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!( @@ -628,7 +634,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` @@ -639,6 +647,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()?; @@ -667,7 +676,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; @@ -800,7 +809,6 @@ pub struct GpuImage { } impl RenderAsset for Image { - type ExtractedAsset = Image; type PreparedAsset = GpuImage; type Param = ( SRes, @@ -808,34 +816,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()) @@ -845,10 +851,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, }) } } @@ -915,6 +921,7 @@ impl CompressedImageFormats { mod test { use super::*; + use crate::render_asset::RenderAssetPersistencePolicy; #[test] fn image_size() { @@ -928,6 +935,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 ec79c9d13e4cb..284b085896a02 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 298c39219c0cc..999cacc005672 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 db3a034744be0..9e1940eb2beef 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::{ BindGroup, BindGroupLayout, Buffer, CachedRenderPipelineId, FragmentState, PipelineCache, RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines, Texture, @@ -367,6 +368,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 fcf19bba537ae..28db250887205 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 30593c875e208..d65a3d052dd73 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -508,6 +508,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); @@ -516,7 +517,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 54048bfd448be..631129a9ba4da 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -607,8 +607,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 50ae821510b2b..07a5e911e69f9 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 1b91430932e3b..eba165395e200 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 cdf9e095889ba..e46bf3cd53615 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 fcf02d30ef470..1de9ec87c5836 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -790,6 +790,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 83ef5ef592c77..a7692eb3aad07 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -612,6 +612,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); @@ -620,8 +621,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 e3fc5eb659f74..2cc8a9408db2b 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 0b8856c4132de..8c7cc64003cb7 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -9,6 +9,7 @@ use bevy::{ prelude::*, render::{ mesh::{Indices, MeshVertexAttribute}, + render_asset::RenderAssetPersistencePolicy, render_asset::RenderAssets, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{ @@ -46,8 +47,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 999d11a74c878..8bf490b04fdbd 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 255ccf00bde18..5c3188ca1043e 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -17,6 +17,7 @@ use bevy::{ pbr::CascadeShadowConfigBuilder, prelude::*, render::{ + render_asset::RenderAssetPersistencePolicy, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::{ImageSampler, ImageSamplerDescriptor}, }, @@ -382,6 +383,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 a5be376f0719e..0edf56adb1d19 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 4a71bd78ae932..e12202c8ad5a0 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 1608640be4992..17856f95552f3 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 7f349e2846100..a5c23af8dbdd8 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 2f8a269e592b8..9407513d2889c 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::*, @@ -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 72baca9e7b1c0..3ac2ab1cd96c9 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 740b9bbcc7659..c5f62fe36c6b0 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()