diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 3017ea3f12dab..78a038fa55fbb 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 103475f8baa60..1f9b8fb6084df 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 321ad57937aaf..f85102f0c88ca 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::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 20c3b87bb6d88..8e277a2e50c59 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 178b9680caa1b..17de4bafcab2f 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 6572499b18a71..f33ee07e9419e 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 2a68125ddcd9e..51bef6ed7178a 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 84e8b3fc76b82..270fd86cb1705 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 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 879c59fabd11e..8369904c6d9fc 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 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 bb6d9212b729e..d7197cbd515a7 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 a5f296b566a65..89414ea603948 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 c3b8cc876c30a..35cbab7db624b 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 ca1df0b3b3d60..b7f881069585f 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 6f87a178c2326..d73797e68aac2 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 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 6e8965038d303..702d9e7d7173c 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 5f257e3c1a878..113cef88e9665 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 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 402ba81328cf8..c1c4a9f370bb7 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 348d112099e1a..9f17afc930ae6 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 dd88584dcf27f..57f29a2dfc76c 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 bf3501c2a0600..f8fdaf87905bb 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 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 f7b0dedc4f075..04e0bf969afcd 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 aada1df1b7275..7948f44f288c3 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 921750684b51a..530d07a9daf45 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 81afe9b662288..df16d67b178a4 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 90ee8b7896170..811be1b403c10 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 6a1542a025fab..3f45521fa997c 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()