diff --git a/benches/benches/bevy_render/extract_render_asset.rs b/benches/benches/bevy_render/extract_render_asset.rs index 350de956b4e75..adbd7a84ecb49 100644 --- a/benches/benches/bevy_render/extract_render_asset.rs +++ b/benches/benches/bevy_render/extract_render_asset.rs @@ -1,5 +1,7 @@ use bevy_app::{App, AppLabel}; -use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, Assets, RenderAssetUsages}; +use bevy_asset::{ + Asset, AssetApp, AssetEvent, AssetId, Assets, EmptyRetainedAsset, RenderAssetUsages, +}; use bevy_ecs::prelude::*; use bevy_reflect::TypePath; use bevy_render::{ @@ -17,12 +19,17 @@ struct DummyRenderAsset; impl RenderAsset for DummyRenderAsset { type SourceAsset = DummyAsset; + type RetainedAsset = EmptyRetainedAsset; type Param = (); fn asset_usage(_: &Self::SourceAsset) -> RenderAssetUsages { RenderAssetUsages::RENDER_WORLD } + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( _source_asset: Self::SourceAsset, _asset_id: AssetId, diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index 12268399ecf00..0e0e8ae8b7013 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -11,10 +11,38 @@ pub(crate) fn bevy_asset_path() -> Path { BevyManifest::shared(|manifest| manifest.get_path("bevy_asset")) } +const ASSET_ATTRIBUTE: &str = "asset"; const DEPENDENCY_ATTRIBUTE: &str = "dependency"; +mod kw { + syn::custom_keyword!(extractable); +} + +struct AssetAttributes { + extractable: bool, +} + +impl syn::parse::Parse for AssetAttributes { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut extractable: bool = false; + + while !input.is_empty() { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::extractable) { + input.parse::()?; + extractable = true; + } else { + return Err(lookahead.error()); + } + } + Ok(AssetAttributes { extractable }) + } +} + /// Implement the `Asset` trait. -#[proc_macro_derive(Asset, attributes(dependency))] +/// +/// Add `#[asset(extractable)]` to make the asset stored in `Extracable`. +#[proc_macro_derive(Asset, attributes(dependency, asset))] pub fn derive_asset(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let bevy_asset_path: Path = bevy_asset_path(); @@ -26,8 +54,25 @@ pub fn derive_asset(input: TokenStream) -> TokenStream { Err(err) => return err.into_compile_error().into(), }; + let mut storage_type = quote! { + type Storage = #struct_name #type_generics; + }; + if let Some(attr) = ast + .attrs + .iter() + .find(|attr| attr.path().is_ident(ASSET_ATTRIBUTE)) + && let Ok(attr) = attr.parse_args::() + && attr.extractable + { + storage_type = quote! { + type Storage = #bevy_asset_path::Extractable<#struct_name #type_generics>; + }; + } + TokenStream::from(quote! { - impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { } + impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { + #storage_type + } #dependency_visitor }) } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index f7aaba199e111..fa2cc201ba2b4 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -103,17 +103,20 @@ pub struct LoadedUntypedAsset { // PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead #[derive(Default)] -enum Entry { +enum Entry { /// None is an indicator that this entry does not have live handles. #[default] None, /// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`] - Some { value: Option, generation: u32 }, + Some { + value: Option, + generation: u32, + }, } /// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`]. struct DenseAssetStorage { - storage: Vec>, + storage: Vec>, len: u32, allocator: Arc, } @@ -143,18 +146,18 @@ impl DenseAssetStorage { pub(crate) fn insert( &mut self, index: AssetIndex, - asset: A, - ) -> Result { + asset: A::Storage, + ) -> Result, InvalidGenerationError> { self.flush(); let entry = &mut self.storage[index.index as usize]; if let Entry::Some { value, generation } = entry { if *generation == index.generation { - let exists = value.is_some(); + let old_value = value.replace(asset); + let exists = old_value.is_some(); if !exists { self.len += 1; } - *value = Some(asset); - Ok(exists) + Ok(old_value) } else { Err(InvalidGenerationError::Occupied { index, @@ -168,7 +171,7 @@ impl DenseAssetStorage { /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists). /// This will recycle the id and allow new entries to be inserted. - pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option { + pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option { self.remove_internal(index, |dense_storage| { dense_storage.storage[index.index as usize] = Entry::None; dense_storage.allocator.recycle(index); @@ -178,7 +181,7 @@ impl DenseAssetStorage { /// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists). /// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will /// not be reused until [`DenseAssetStorage::remove_dropped`] is called. - pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option { + pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option { self.remove_internal(index, |_| {}) } @@ -186,7 +189,7 @@ impl DenseAssetStorage { &mut self, index: AssetIndex, removed_action: impl FnOnce(&mut Self), - ) -> Option { + ) -> Option { self.flush(); let value = match &mut self.storage[index.index as usize] { Entry::None => return None, @@ -202,7 +205,7 @@ impl DenseAssetStorage { value } - pub(crate) fn get(&self, index: AssetIndex) -> Option<&A> { + pub(crate) fn get(&self, index: AssetIndex) -> Option<&A::Storage> { let entry = self.storage.get(index.index as usize)?; match entry { Entry::None => None, @@ -216,7 +219,7 @@ impl DenseAssetStorage { } } - pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A> { + pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A::Storage> { let entry = self.storage.get_mut(index.index as usize)?; match entry { Entry::None => None, @@ -287,7 +290,7 @@ impl DenseAssetStorage { #[derive(Resource)] pub struct Assets { dense_storage: DenseAssetStorage, - hash_map: HashMap, + hash_map: HashMap, handle_provider: AssetHandleProvider, queued_events: Vec>, /// Assets managed by the `Assets` struct with live strong `Handle`s @@ -332,9 +335,9 @@ impl Assets { asset: A, ) -> Result<(), InvalidGenerationError> { match id.into() { - AssetId::Index { index, .. } => self.insert_with_index(index, asset).map(|_| ()), + AssetId::Index { index, .. } => self.insert_with_index(index, asset.into()).map(|_| ()), AssetId::Uuid { uuid } => { - self.insert_with_uuid(uuid, asset); + self.insert_with_uuid(uuid, asset.into()); Ok(()) } } @@ -349,7 +352,7 @@ impl Assets { &mut self, id: impl Into>, insert_fn: impl FnOnce() -> A, - ) -> Result, InvalidGenerationError> { + ) -> Result, InvalidGenerationError> { let id: AssetId = id.into(); if self.get(id).is_none() { self.insert(id, insert_fn())?; @@ -369,8 +372,22 @@ impl Assets { } } - pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A) -> Option { - let result = self.hash_map.insert(uuid, asset); + pub(crate) fn insert_with_uuid_untracked( + &mut self, + uuid: Uuid, + asset: A::Storage, + ) -> Option { + self.hash_map.insert(uuid, asset) + } + pub(crate) fn insert_with_index_untracked( + &mut self, + index: AssetIndex, + asset: A::Storage, + ) -> Result, InvalidGenerationError> { + self.dense_storage.insert(index, asset) + } + pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A::Storage) -> Option { + let result = self.insert_with_uuid_untracked(uuid, asset); if result.is_some() { self.queued_events .push(AssetEvent::Modified { id: uuid.into() }); @@ -383,24 +400,24 @@ impl Assets { pub(crate) fn insert_with_index( &mut self, index: AssetIndex, - asset: A, - ) -> Result { - let replaced = self.dense_storage.insert(index, asset)?; - if replaced { + asset: A::Storage, + ) -> Result, InvalidGenerationError> { + let result = self.insert_with_index_untracked(index, asset)?; + if result.is_some() { self.queued_events .push(AssetEvent::Modified { id: index.into() }); } else { self.queued_events .push(AssetEvent::Added { id: index.into() }); } - Ok(replaced) + Ok(result) } /// Adds the given `asset` and allocates a new strong [`Handle`] for it. #[inline] pub fn add(&mut self, asset: impl Into) -> Handle { let index = self.dense_storage.allocator.reserve(); - self.insert_with_index(index, asset.into()).unwrap(); + self.insert_with_index(index, asset.into().into()).unwrap(); Handle::Strong(self.handle_provider.get_handle(index, false, None, None)) } @@ -427,7 +444,7 @@ impl Assets { /// Retrieves a reference to the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. #[inline] - pub fn get(&self, id: impl Into>) -> Option<&A> { + pub fn get(&self, id: impl Into>) -> Option<&A::Storage> { match id.into() { AssetId::Index { index, .. } => self.dense_storage.get(index), AssetId::Uuid { uuid } => self.hash_map.get(&uuid), @@ -437,7 +454,7 @@ impl Assets { /// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. #[inline] - pub fn get_mut(&mut self, id: impl Into>) -> Option> { + pub fn get_mut(&mut self, id: impl Into>) -> Option> { let id: AssetId = id.into(); let result = match id { AssetId::Index { index, .. } => self.dense_storage.get_mut(index), @@ -457,7 +474,7 @@ impl Assets { /// /// This is the same as [`Assets::get_mut`] except it doesn't emit [`AssetEvent::Modified`]. #[inline] - pub fn get_mut_untracked(&mut self, id: impl Into>) -> Option<&mut A> { + pub fn get_mut_untracked(&mut self, id: impl Into>) -> Option<&mut A::Storage> { let id: AssetId = id.into(); match id { AssetId::Index { index, .. } => self.dense_storage.get_mut(index), @@ -467,7 +484,7 @@ impl Assets { /// Removes (and returns) the [`Asset`] with the given `id`, if it exists. /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. - pub fn remove(&mut self, id: impl Into>) -> Option { + pub fn remove(&mut self, id: impl Into>) -> Option { let id: AssetId = id.into(); let result = self.remove_untracked(id); if result.is_some() { @@ -480,7 +497,7 @@ impl Assets { /// Note that this supports anything that implements `Into>`, which includes [`Handle`] and [`AssetId`]. /// /// This is the same as [`Assets::remove`] except it doesn't emit [`AssetEvent::Removed`]. - pub fn remove_untracked(&mut self, id: impl Into>) -> Option { + pub fn remove_untracked(&mut self, id: impl Into>) -> Option { let id: AssetId = id.into(); match id { AssetId::Index { index, .. } => { @@ -533,14 +550,14 @@ impl Assets { /// Returns an iterator over the [`AssetId`] and [`Asset`] ref of every asset in this collection. // PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits - pub fn iter(&self) -> impl Iterator, &A)> { + pub fn iter(&self) -> impl Iterator, &A::Storage)> { self.dense_storage .storage .iter() .enumerate() .filter_map(|(i, v)| match v { Entry::None => None, - Entry::Some { value, generation } => value.as_ref().map(|v| { + Entry::Some { value, generation } => value.as_ref().map(|stored| { let id = AssetId::Index { index: AssetIndex { generation: *generation, @@ -548,13 +565,13 @@ impl Assets { }, marker: PhantomData, }; - (id, v) + (id, stored) }), }) .chain( self.hash_map .iter() - .map(|(i, v)| (AssetId::Uuid { uuid: *i }, v)), + .map(|(i, stored)| (AssetId::Uuid { uuid: *i }, stored)), ) } @@ -628,20 +645,20 @@ impl Assets { /// /// Just as an example, this allows checking if a material property has changed /// before modifying it to avoid unnecessary material extraction down the pipeline. -pub struct AssetMut<'a, A: Asset> { - asset: &'a mut A, +pub struct AssetMut<'a, T, A: Asset> { + asset: &'a mut T, guard: AssetMutChangeNotifier<'a, A>, } -impl<'a, A: Asset> AssetMut<'a, A> { +impl<'a, T, A: Asset> AssetMut<'a, T, A> { /// Marks with inner asset as modified and returns reference to it. - pub fn into_inner(mut self) -> &'a mut A { + pub fn into_inner(mut self) -> &'a mut T { self.guard.changed = true; self.asset } /// Returns reference to the inner asset but doesn't mark it as modified. - pub fn into_inner_untracked(self) -> &'a mut A { + pub fn into_inner_untracked(self) -> &'a mut T { self.asset } @@ -652,20 +669,20 @@ impl<'a, A: Asset> AssetMut<'a, A> { /// This is a risky operation, that can have unexpected consequences on any system relying on this code. /// However, it can be an essential escape hatch when, for example, /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. - pub fn bypass_change_detection(&mut self) -> &mut A { + pub fn bypass_change_detection(&mut self) -> &mut T { self.asset } } -impl<'a, A: Asset> Deref for AssetMut<'a, A> { - type Target = A; +impl<'a, T, A: Asset> Deref for AssetMut<'a, T, A> { + type Target = T; fn deref(&self) -> &Self::Target { self.asset } } -impl<'a, A: Asset> DerefMut for AssetMut<'a, A> { +impl<'a, T, A: Asset> DerefMut for AssetMut<'a, T, A> { fn deref_mut(&mut self) -> &mut Self::Target { self.guard.changed = true; self.asset @@ -692,12 +709,12 @@ impl<'a, A: Asset> Drop for AssetMutChangeNotifier<'a, A> { /// A mutable iterator over [`Assets`]. pub struct AssetsMutIterator<'a, A: Asset> { queued_events: &'a mut Vec>, - dense_storage: Enumerate>>, - hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>, + dense_storage: Enumerate>>, + hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A::Storage>, } impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> { - type Item = (AssetId, &'a mut A); + type Item = (AssetId, &'a mut A::Storage); fn next(&mut self) -> Option { for (i, entry) in &mut self.dense_storage { diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 1dcecc8ff5d4a..2d0823bd22baa 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -794,7 +794,9 @@ mod tests { struct MyAsset { value: u32, } - impl Asset for MyAsset {} + impl Asset for MyAsset { + type Storage = Self; + } impl VisitAssetDependencies for MyAsset { fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {} } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index b0c56f5624432..a98439019e484 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -451,7 +451,12 @@ impl Plugin for AssetPlugin { label = "invalid `Asset`", note = "consider annotating `{Self}` with `#[derive(Asset)]`" )] -pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {} +pub trait Asset: + VisitAssetDependencies + TypePath + Send + Sync + 'static + Into +{ + /// The type stored in [`Assets`] internally. + type Storage: Send + Sync + 'static; +} /// A trait for components that can be used as asset identifiers, e.g. handle wrappers. pub trait AsAssetId: Component { @@ -585,7 +590,8 @@ pub trait AssetApp { /// This enables reflection code to access assets. For detailed information, see the docs on [`ReflectAsset`] and [`ReflectHandle`]. fn register_asset_reflect(&mut self) -> &mut Self where - A: Asset + Reflect + FromReflect + GetTypeRegistration; + A: Asset + Reflect + FromReflect + GetTypeRegistration, + A::Storage: FromReflect; /// Preregisters a loader for the given extensions, that will block asset loads until a real loader /// is registered. fn preregister_asset_loader(&mut self, extensions: &[&str]) -> &mut Self; @@ -675,6 +681,7 @@ impl AssetApp for App { fn register_asset_reflect(&mut self) -> &mut Self where A: Asset + Reflect + FromReflect + GetTypeRegistration, + A::Storage: FromReflect, { let type_registry = self.world().resource::(); { @@ -971,7 +978,7 @@ mod tests { const LARGE_ITERATION_COUNT: usize = 10000; - fn get(world: &World, id: AssetId) -> Option<&A> { + fn get(world: &World, id: AssetId) -> Option<&A::Storage> { world.resource::>().get(id) } diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs index 7498c570b9340..cf4b7eb8fc681 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -206,7 +206,9 @@ impl Process for () { } } -impl Asset for () {} +impl Asset for () { + type Storage = (); +} impl VisitAssetDependencies for () { fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy_asset::UntypedAssetId)) { diff --git a/crates/bevy_asset/src/reflect.rs b/crates/bevy_asset/src/reflect.rs index 3269dd55f8b11..112daaedd340a 100644 --- a/crates/bevy_asset/src/reflect.rs +++ b/crates/bevy_asset/src/reflect.rs @@ -156,7 +156,10 @@ impl ReflectAsset { } } -impl FromType for ReflectAsset { +impl FromType for ReflectAsset +where + A::Storage: FromReflect, +{ fn from_type() -> Self { ReflectAsset { handle_type_id: TypeId::of::>(), @@ -182,7 +185,7 @@ impl FromType for ReflectAsset { }, insert: |world, asset_id, value| { let mut assets = world.resource_mut::>(); - let value: A = FromReflect::from_reflect(value) + let value = FromReflect::from_reflect(value) .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`"); assets.insert(asset_id.typed_debug_checked(), value) }, diff --git a/crates/bevy_asset/src/render_asset.rs b/crates/bevy_asset/src/render_asset.rs index 90855e86e3389..24a9ed70bf770 100644 --- a/crates/bevy_asset/src/render_asset.rs +++ b/crates/bevy_asset/src/render_asset.rs @@ -1,6 +1,14 @@ +use bevy_ecs::{ + resource::Resource, + system::{Res, SystemParam}, +}; +use bevy_platform::collections::HashMap; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; +use derive_more::{Deref, DerefMut}; use serde::{Deserialize, Serialize}; +use crate::{Asset, AssetId, UntypedAssetId}; + bitflags::bitflags! { /// Defines where the asset will be used. /// @@ -50,3 +58,85 @@ impl Default for RenderAssetUsages { RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD } } + +/// Represents a value that can be extracted, like [`Option`]. +#[derive(Debug, Default, Clone, Copy, Reflect)] +pub enum Extractable { + #[default] + Extracted, + Data(A), +} + +impl Extractable { + pub fn as_option(self) -> Option { + match self { + Extractable::Extracted => None, + Extractable::Data(a) => Some(a), + } + } + + pub fn as_option_ref(&self) -> Option<&A> { + match self { + Extractable::Extracted => None, + Extractable::Data(a) => Some(a), + } + } + + pub fn as_option_mut(&mut self) -> Option<&mut A> { + match self { + Extractable::Extracted => None, + Extractable::Data(a) => Some(a), + } + } + + pub fn replace(&mut self, value: Self) -> Self { + core::mem::replace(self, value) + } + + pub fn take(&mut self) -> Self { + core::mem::take(self) + } +} + +impl From for Extractable { + fn from(value: A) -> Self { + Self::Data(value) + } +} + +/// Declares this type has an associated retained asset for use in the [`RetainedAssets`] system param. +pub trait GetRetainedAsset: Asset { + /// The type of retained asset. + type RetainedAsset: Send + Sync + 'static; +} + +/// A special retained asset that won't be stored in [`RetainedAssets`]. +pub struct EmptyRetainedAsset; + +/// Stores all `RenderAsset::RetainedAsset` if they exist and are not [`EmptyRetainedAsset`] during `RenderAsset`/`ErasedRenderAsset` extraction. +#[derive(Resource, Deref, DerefMut)] +pub struct ErasedRetainedAssets(HashMap); + +impl Default for ErasedRetainedAssets { + fn default() -> Self { + Self(Default::default()) + } +} + +/// A system parameter for getting the retained asset of an asset that implements [`GetRetainedAsset`] +#[derive(SystemParam)] +pub struct RetainedAssets<'w, A: GetRetainedAsset> { + erased_retained_assets: Res<'w, ErasedRetainedAssets<::RetainedAsset>>, +} + +impl<'w, A: GetRetainedAsset> RetainedAssets<'w, A> { + pub fn get(&self, id: impl Into>) -> Option<&A::RetainedAsset> { + self.erased_retained_assets.get(&id.into().untyped()) + } + + pub fn iter(&self) -> impl Iterator, &A::RetainedAsset)> { + self.erased_retained_assets + .iter() + .map(|(k, v)| (k.typed(), v)) + } +} diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 1b23ccee8a072..57f72b5cb4add 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -81,7 +81,7 @@ impl<'w, 's> EarPositions<'w, 's> { /// /// This system detects such entities, checks if their source asset /// data is available, and creates/inserts the sink. -pub(crate) fn play_queued_audio_system( +pub(crate) fn play_queued_audio_system + Decodable>( audio_output: Res, audio_sources: Res>, global_volume: Res, @@ -281,7 +281,7 @@ pub(crate) fn play_queued_audio_system( } } -pub(crate) fn cleanup_finished_audio( +pub(crate) fn cleanup_finished_audio>( mut commands: Commands, query_nonspatial_despawn: Query< (Entity, &AudioSink), diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index dddaa324a3b03..2aab58ddebd96 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -109,6 +109,6 @@ pub trait AddAudioSource { /// the [audio][super::AudioPlugin] and [asset][bevy_asset::AssetPlugin] plugins must be added first. fn add_audio_source(&mut self) -> &mut Self where - T: Decodable + Asset, + T: Decodable + Asset, f32: rodio::cpal::FromSample; } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 46c69eb22410c..d2183a3c62a35 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -107,7 +107,7 @@ impl Plugin for AudioPlugin { impl AddAudioSource for App { fn add_audio_source(&mut self) -> &mut Self where - T: Decodable + Asset, + T: Decodable + Asset, f32: rodio::cpal::FromSample, { self.init_asset::().add_systems( diff --git a/crates/bevy_camera/src/primitives.rs b/crates/bevy_camera/src/primitives.rs index 2964001f415e5..e3127ebaf6982 100644 --- a/crates/bevy_camera/src/primitives.rs +++ b/crates/bevy_camera/src/primitives.rs @@ -7,34 +7,8 @@ use bevy_math::{ primitives::{HalfSpace, ViewFrustum}, Affine3A, Mat3A, Vec3, Vec3A, }; -use bevy_mesh::{Mesh, VertexAttributeValues}; use bevy_reflect::prelude::*; -pub trait MeshAabb { - /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space - /// - /// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of - /// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices. - fn compute_aabb(&self) -> Option; -} - -impl MeshAabb for Mesh { - fn compute_aabb(&self) -> Option { - if let Some(aabb) = self.final_aabb { - // use precomputed extents - return Some(aabb.into()); - } - - let Ok(VertexAttributeValues::Float32x3(values)) = - self.try_attribute(Mesh::ATTRIBUTE_POSITION) - else { - return None; - }; - - Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))) - } -} - /// An axis-aligned bounding box, defined by: /// - a center, /// - the distances from the center to each faces along the axis, diff --git a/crates/bevy_camera/src/visibility/mod.rs b/crates/bevy_camera/src/visibility/mod.rs index 77a3a1b17f673..c8334ac574e78 100644 --- a/crates/bevy_camera/src/visibility/mod.rs +++ b/crates/bevy_camera/src/visibility/mod.rs @@ -44,7 +44,7 @@ pub use render_layers::*; use bevy_app::{Plugin, PostUpdate, ValidateParentHasComponentPlugin}; use bevy_asset::prelude::AssetChanged; -use bevy_asset::{AssetEventSystems, Assets}; +use bevy_asset::{AssetEventSystems, Assets, RetainedAssets}; use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystems}; @@ -53,7 +53,7 @@ use smallvec::SmallVec; use crate::{ camera::Camera, - primitives::{Aabb, Frustum, MeshAabb, Sphere}, + primitives::{Aabb, Frustum, Sphere}, Projection, }; use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, Mesh3d}; @@ -63,7 +63,7 @@ use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh, Mesh2d, /// /// It can be used for example: /// - disabling CPU culling completely for a [`Camera`], using only GPU culling. -/// - when overwriting a [`Mesh`]'s transform on the GPU side (e.g. overwriting `MeshInputUniform`'s +/// - when overwriting a `Mesh`'s transform on the GPU side (e.g. overwriting `MeshInputUniform`'s /// `world_from_local`), resulting in stale CPU-side positions. #[derive(Component, Default)] pub struct NoCpuCulling; @@ -310,9 +310,9 @@ impl<'a> SetViewVisibility for Mut<'a, ViewVisibility> { /// [`Frustum`]. /// /// It can be used for example: -/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations, -/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`] -/// to appear in the reflection of a [`Mesh`] within. +/// - when a `Mesh` is updated but its [`Aabb`] is not, which might happen with animations, +/// - when using some light effects, like wanting a `Mesh` out of the [`Frustum`] +/// to appear in the reflection of a `Mesh` within. #[derive(Debug, Component, Default, Reflect, Clone, PartialEq)] #[reflect(Component, Default, Debug)] pub struct NoFrustumCulling; @@ -556,7 +556,7 @@ pub struct NoAutoAabb; /// This system is used in system set [`VisibilitySystems::CalculateBounds`]. pub fn calculate_bounds( mut commands: Commands, - meshes: Res>, + meshes: RetainedAssets, new_aabb: Query< (Entity, &Mesh3d), ( @@ -576,17 +576,17 @@ pub fn calculate_bounds( ) { for (entity, mesh_handle) in &new_aabb { if let Some(mesh) = meshes.get(mesh_handle) - && let Some(aabb) = mesh.compute_aabb() + && let Some(aabb) = mesh.aabb { - commands.entity(entity).try_insert(aabb); + commands.entity(entity).try_insert(Aabb::from(aabb)); } } update_aabb .par_iter_mut() .for_each(|(mesh_handle, mut old_aabb)| { - if let Some(aabb) = meshes.get(mesh_handle).and_then(MeshAabb::compute_aabb) { - *old_aabb = aabb; + if let Some(aabb) = meshes.get(mesh_handle).and_then(|m| m.aabb) { + *old_aabb = aabb.into(); } }); } @@ -595,7 +595,7 @@ pub fn calculate_bounds( // component. fn update_skinned_mesh_bounds( inverse_bindposes_assets: Res>, - mesh_assets: Res>, + mesh_assets: RetainedAssets, mut mesh_entities: Query< (&mut Aabb, &Mesh3d, &SkinnedMesh, Option<&GlobalTransform>), With, @@ -945,6 +945,8 @@ pub fn add_visibility_class( mod test { use super::*; use bevy_app::prelude::*; + use bevy_asset::ErasedRetainedAssets; + use bevy_mesh::RetainedMesh; #[test] fn visibility_propagation() { @@ -1325,8 +1327,9 @@ mod test { struct ManualMark(bool); #[derive(Resource, Default)] struct ObservedChanged(bool); - app.init_resource::(); - app.init_resource::(); + app.init_resource::() + .init_resource::() + .init_resource::>(); app.add_systems( PostUpdate, diff --git a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs index 1c7a201516f70..be5ef0ca822ad 100644 --- a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs +++ b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs @@ -1,7 +1,9 @@ //! Module containing logic for the frame time graph use bevy_app::{Plugin, Update}; -use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle}; +use bevy_asset::{ + load_internal_asset, uuid_handle, Asset, Assets, Extractable, Handle, RetainedAssets, +}; use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy_ecs::{ schedule::IntoScheduleConfigs, @@ -99,6 +101,7 @@ impl UiMaterial for FrametimeGraphMaterial { fn update_frame_time_values( mut frame_time_graph_materials: ResMut>, mut buffers: ResMut>, + retained_buffers: RetainedAssets, diagnostics_store: Res, config: Option>, ) { @@ -115,7 +118,9 @@ fn update_frame_time_values( .collect::>(); for (_, material) in frame_time_graph_materials.iter_mut() { let mut buffer = buffers.get_mut(&material.values).unwrap(); - - buffer.set_data(frame_times.clone()); + let retained = retained_buffers.get(&material.values).unwrap(); + let mut new_buffer: ShaderBuffer = retained.clone().into(); + new_buffer.set_data(frame_times.clone()); + *buffer = Extractable::Data(new_buffer); } } diff --git a/crates/bevy_gizmos/src/skinned_mesh_bounds.rs b/crates/bevy_gizmos/src/skinned_mesh_bounds.rs index 2ec322a9976d5..be567bcd4fee3 100644 --- a/crates/bevy_gizmos/src/skinned_mesh_bounds.rs +++ b/crates/bevy_gizmos/src/skinned_mesh_bounds.rs @@ -1,7 +1,7 @@ //! A module adding debug visualization of [`DynamicSkinnedMeshBounds`]. use bevy_app::{Plugin, PostUpdate}; -use bevy_asset::Assets; +use bevy_asset::{Assets, RetainedAssets}; use bevy_camera::visibility::DynamicSkinnedMeshBounds; use bevy_color::Color; use bevy_ecs::{ @@ -85,14 +85,14 @@ pub struct ShowSkinnedMeshBoundsGizmo { fn draw( color: Color, mesh: &Mesh3d, - mesh_assets: &Res>, + mesh_assets: &RetainedAssets, skinned_mesh: &SkinnedMesh, joint_entities: &Query<&GlobalTransform>, inverse_bindposes_assets: &Res>, gizmos: &mut Gizmos, ) { if let Some(mesh_asset) = mesh_assets.get(mesh) - && let Some(bounds) = mesh_asset.skinned_mesh_bounds() + && let Some(bounds) = &mesh_asset.skinned_mesh_bounds && let Some(inverse_bindposes_asset) = inverse_bindposes_assets.get(&skinned_mesh.inverse_bindposes) { @@ -118,7 +118,7 @@ fn draw_skinned_mesh_bounds( With, >, joint_entities: Query<&GlobalTransform>, - mesh_assets: Res>, + mesh_assets: RetainedAssets, inverse_bindposes_assets: Res>, mut gizmos: Gizmos, ) { @@ -146,7 +146,7 @@ fn draw_all_skinned_mesh_bounds( ), >, joint_entities: Query<&GlobalTransform>, - mesh_assets: Res>, + mesh_assets: RetainedAssets, inverse_bindposes_assets: Res>, mut gizmos: Gizmos, ) { diff --git a/crates/bevy_gizmos_render/src/lib.rs b/crates/bevy_gizmos_render/src/lib.rs index 4b7d37324dc5e..1446cf05fdfc0 100755 --- a/crates/bevy_gizmos_render/src/lib.rs +++ b/crates/bevy_gizmos_render/src/lib.rs @@ -27,6 +27,7 @@ mod pipeline_2d; mod pipeline_3d; use bevy_app::{App, Plugin}; +use bevy_asset::EmptyRetainedAsset; use bevy_ecs::{ name::Name, resource::Resource, @@ -253,8 +254,13 @@ struct GpuLineGizmo { impl RenderAsset for GpuLineGizmo { type SourceAsset = GizmoAsset; + type RetainedAsset = EmptyRetainedAsset; type Param = SRes; + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( gizmo: Self::SourceAsset, _: AssetId, diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index d48745c6780a8..dd52c3b76fa5c 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -14,7 +14,9 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages}; +use bevy_asset::{ + uuid_handle, Asset, AssetApp, Assets, GetRetainedAsset, Handle, RenderAssetUsages, +}; use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza}; use bevy_ecs::resource::Resource; use bevy_math::{AspectRatio, UVec2, UVec3, Vec2}; @@ -610,6 +612,7 @@ impl ToExtents for UVec3 { )] #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] #[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))] +#[asset(extractable)] pub struct Image { /// Raw pixel data. /// If the image is being used as a storage texture which doesn't need to be initialized by the @@ -645,6 +648,110 @@ pub struct Image { pub copy_on_resize: bool, } +/// The representation of an [`Image`] that is retained in [`RetainedAssets`] on the main world after extracting. +/// +/// [`RetainedAssets`]: bevy_asset::RetainedAssets +#[derive(Debug, Clone, PartialEq)] +pub struct RetainedImage { + /// For texture data with layers and mips, this field controls how wgpu interprets the buffer layout. + /// + /// Use [`wgpu_types::TextureDataOrder::default()`] for all other cases. + pub data_order: TextureDataOrder, + // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors. + /// Describes the data layout of the GPU texture.\ + /// For example, whether a texture contains 1D/2D/3D data, and what the format of the texture data is. + /// + /// ## Field Usage Notes + /// - [`TextureDescriptor::label`] is used for caching purposes when not using `Asset`.\ + /// If you use assets, the label is purely a debugging aid. + /// - [`TextureDescriptor::view_formats`] is currently unused by Bevy. + pub texture_descriptor: TextureDescriptor, &'static [TextureFormat]>, + /// The [`ImageSampler`] to use during rendering. + pub sampler: ImageSampler, + /// Describes how the GPU texture should be interpreted.\ + /// For example, 2D image data could be read as plain 2D, an array texture of layers of 2D with the same dimensions (and the number of layers in that case), + /// a cube map, an array of cube maps, etc. + /// + /// ## Field Usage Notes + /// - [`TextureViewDescriptor::label`] is used for caching purposes when not using `Asset`.\ + /// If you use assets, the label is purely a debugging aid. + pub texture_view_descriptor: Option>>, + /// Where this image asset will be used. See [`RenderAssetUsages`] for more. + pub asset_usage: RenderAssetUsages, + /// Whether this image should be copied on the GPU when resized. + pub copy_on_resize: bool, +} + +impl GetRetainedAsset for Image { + type RetainedAsset = RetainedImage; +} + +impl From for Image { + fn from(value: RetainedImage) -> Self { + Image { + data: None, + data_order: value.data_order, + texture_descriptor: value.texture_descriptor, + sampler: value.sampler, + texture_view_descriptor: value.texture_view_descriptor, + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + +impl From for RetainedImage { + fn from(value: Image) -> Self { + RetainedImage { + data_order: value.data_order, + texture_descriptor: value.texture_descriptor, + sampler: value.sampler, + texture_view_descriptor: value.texture_view_descriptor, + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + +impl From<&Image> for RetainedImage { + fn from(value: &Image) -> Self { + RetainedImage { + data_order: value.data_order, + texture_descriptor: value.texture_descriptor.clone(), + sampler: value.sampler.clone(), + texture_view_descriptor: value.texture_view_descriptor.clone(), + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + +impl RetainedImage { + /// Returns the width of a 2D image. + #[inline] + pub fn width(&self) -> u32 { + self.texture_descriptor.size.width + } + + /// Returns the height of a 2D image. + #[inline] + pub fn height(&self) -> u32 { + self.texture_descriptor.size.height + } + + /// Returns the size of a 2D image as f32. + #[inline] + pub fn size_f32(&self) -> Vec2 { + Vec2::new(self.width() as f32, self.height() as f32) + } + + /// Returns the size of a 2D image. + #[inline] + pub fn size(&self) -> UVec2 { + UVec2::new(self.width(), self.height()) + } +} + #[cfg(feature = "serialize")] mod image_serde { use super::*; diff --git a/crates/bevy_image/src/saver.rs b/crates/bevy_image/src/saver.rs index e601326f0a3e3..55227c878a06c 100644 --- a/crates/bevy_image/src/saver.rs +++ b/crates/bevy_image/src/saver.rs @@ -286,11 +286,19 @@ mod tests { .get(&handle) .unwrap(); - assert_eq!(loaded_image.size(), UVec2::new(WIDTH, WIDTH)); + assert_eq!( + loaded_image.as_option_ref().unwrap().size(), + UVec2::new(WIDTH, WIDTH) + ); let compare_images = 'compare_images: { for y in 0..WIDTH { for x in 0..WIDTH { - if image.get_color_at(x, y).unwrap() != loaded_image.get_color_at(x, y).unwrap() + if image.get_color_at(x, y).unwrap() + != loaded_image + .as_option_ref() + .unwrap() + .get_color_at(x, y) + .unwrap() { break 'compare_images Err((x, y)); } @@ -321,7 +329,7 @@ mod tests { } panic!( "Mismatch in color at ({x}, {y})\nleft:\n{}\nright:\n{}", - image_to_string(loaded_image), + image_to_string(loaded_image.as_option_ref().unwrap()), image_to_string(&image) ); } diff --git a/crates/bevy_light/src/atmosphere.rs b/crates/bevy_light/src/atmosphere.rs index 83f7b9ab4295b..9dde582624a34 100644 --- a/crates/bevy_light/src/atmosphere.rs +++ b/crates/bevy_light/src/atmosphere.rs @@ -1,7 +1,7 @@ //! Provides types to specify atmosphere lighting, scattering terms, etc. use alloc::{borrow::Cow, sync::Arc}; -use bevy_asset::{Asset, AssetEvent, AssetId, Handle}; +use bevy_asset::{Asset, AssetEvent, AssetId, Extractable, Handle}; use bevy_color::{ColorToComponents, Gray, LinearRgba}; use bevy_ecs::{ component::Component, @@ -572,6 +572,9 @@ pub fn extract_chromatic_phase_textures( let Some(image) = images.get(*id) else { continue; }; + let Extractable::Data(image) = image else { + continue; + }; if image.texture_descriptor.format != TextureFormat::Rgba32Float { continue; } diff --git a/crates/bevy_mesh/src/index.rs b/crates/bevy_mesh/src/index.rs index 5c1618617d4f5..643b28098321c 100644 --- a/crates/bevy_mesh/src/index.rs +++ b/crates/bevy_mesh/src/index.rs @@ -6,8 +6,6 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu_types::IndexFormat; -use crate::MeshAccessError; - /// A disjunction of four iterators. This is necessary to have a well-formed type for the output /// of [`Mesh::triangles`](super::Mesh::triangles), which produces iterators of four different types depending on the /// branch taken. @@ -58,8 +56,6 @@ pub enum MeshWindingInvertError { /// * [`PrimitiveTopology::LineList`](super::PrimitiveTopology::LineList), but the indices are not in chunks of 2. #[error("Indices weren't in chunks according to topology")] AbruptIndicesEnd, - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } /// An error that occurred while trying to extract a collection of triangles from a [`Mesh`](super::Mesh). @@ -73,8 +69,12 @@ pub enum MeshTrianglesError { #[error("Face index data references vertices that do not exist")] BadIndices, - #[error("mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), + + #[error("Source mesh lacks position data")] + MissingPositions, + + #[error("Source mesh lacks face index data")] + MissingIndices, } /// An array of indices into the [`VertexAttributeValues`](super::VertexAttributeValues) for a mesh. diff --git a/crates/bevy_mesh/src/mesh.rs b/crates/bevy_mesh/src/mesh.rs index ec7dd0bcdd508..2d1c976c47577 100644 --- a/crates/bevy_mesh/src/mesh.rs +++ b/crates/bevy_mesh/src/mesh.rs @@ -13,9 +13,10 @@ use crate::morph::MorphAttributes; #[cfg(feature = "serialize")] use crate::SerializedMeshAttributeData; use alloc::collections::BTreeMap; -use bevy_asset::{Asset, RenderAssetUsages}; +use bevy_asset::{Asset, GetRetainedAsset, RenderAssetUsages}; use bevy_math::{bounding::Aabb3d, primitives::Triangle3d, *}; use bevy_platform::collections::{hash_map, HashMap}; +use bevy_platform::sync::Arc; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bytemuck::cast_slice; use core::hash::{Hash, Hasher}; @@ -29,110 +30,6 @@ use wgpu_types::{VertexAttribute, VertexFormat, VertexStepMode, WriteOnly}; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; -/// Error from accessing mesh vertex attributes or indices -#[derive(Error, Debug, Clone)] -pub enum MeshAccessError { - #[error("The mesh vertex/index data has been extracted to the RenderWorld (via `Mesh::asset_usage`)")] - ExtractedToRenderWorld, - #[error("The requested mesh data wasn't found in this mesh")] - NotFound, -} - -const MESH_EXTRACTED_ERROR: &str = "Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh `asset_usage` must include `MAIN_WORLD`"; - -// storage for extractable data with access methods which return errors if the -// contents have already been extracted -#[derive(Debug, Clone, PartialEq, Reflect, Default)] -enum MeshExtractableData { - Data(T), - #[default] - NoData, - ExtractedToRenderWorld, -} - -impl MeshExtractableData { - // get a reference to internal data. returns error if data has been extracted, or if no - // data exists - fn as_ref(&self) -> Result<&T, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(data), - MeshExtractableData::NoData => Err(MeshAccessError::NotFound), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get an optional reference to internal data. returns error if data has been extracted - fn as_ref_option(&self) -> Result, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(Some(data)), - MeshExtractableData::NoData => Ok(None), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get a mutable reference to internal data. returns error if data has been extracted, - // or if no data exists - fn as_mut(&mut self) -> Result<&mut T, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(data), - MeshExtractableData::NoData => Err(MeshAccessError::NotFound), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get an optional mutable reference to internal data. returns error if data has been extracted - fn as_mut_option(&mut self) -> Result, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(Some(data)), - MeshExtractableData::NoData => Ok(None), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // extract data and replace self with `ExtractedToRenderWorld`. returns error if - // data has been extracted - fn extract(&mut self) -> Result, MeshAccessError> { - match core::mem::replace(self, MeshExtractableData::ExtractedToRenderWorld) { - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - not_extracted => Ok(not_extracted), - } - } - - // replace internal data. returns the existing data, or an error if data has been extracted - fn replace( - &mut self, - data: impl Into>, - ) -> Result, MeshAccessError> { - match core::mem::replace(self, data.into()) { - MeshExtractableData::ExtractedToRenderWorld => { - *self = MeshExtractableData::ExtractedToRenderWorld; - Err(MeshAccessError::ExtractedToRenderWorld) - } - MeshExtractableData::Data(t) => Ok(Some(t)), - MeshExtractableData::NoData => Ok(None), - } - } -} - -impl From> for MeshExtractableData { - fn from(value: Option) -> Self { - match value { - Some(data) => MeshExtractableData::Data(data), - None => MeshExtractableData::NoData, - } - } -} - /// A 3D object made out of vertices representing triangles, lines, or points, /// with "attribute" values for each vertex. /// @@ -224,6 +121,7 @@ impl From> for MeshExtractableData { /// This type is only meant for short-term transmission between same versions and should not be stored anywhere. #[derive(Asset, Debug, Clone, Reflect, PartialEq)] #[reflect(Clone)] +#[asset(extractable)] pub struct Mesh { #[reflect(ignore, clone)] primitive_topology: PrimitiveTopology, @@ -232,12 +130,12 @@ pub struct Mesh { /// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order, /// which allows easy stable `VertexBuffers` (i.e. same buffer order) #[reflect(ignore, clone)] - attributes: MeshExtractableData>, - indices: MeshExtractableData, + attributes: BTreeMap, + indices: Option, #[cfg(feature = "morph")] - morph_targets: MeshExtractableData>, + morph_targets: Option>, #[cfg(feature = "morph")] - morph_target_names: MeshExtractableData>, + morph_target_names: Option>, pub asset_usage: RenderAssetUsages, /// Whether or not to build a BLAS for use with `bevy_solari` raytracing. /// @@ -254,10 +152,25 @@ pub struct Mesh { /// Does nothing if not used with `bevy_solari`, or if the mesh is not compatible /// with `bevy_solari` (see `bevy_solari`'s docs). pub enable_raytracing: bool, - /// Precomputed min and max extents of the mesh position data. Used mainly for constructing `Aabb`s for frustum culling. - /// This data will be set if/when a mesh is extracted to the GPU - pub final_aabb: Option, - skinned_mesh_bounds: Option, + pub skinned_mesh_bounds: Option>, +} + +#[derive(Debug, Clone, Reflect, PartialEq)] +#[reflect(Clone)] +pub struct RetainedMesh { + #[reflect(ignore, clone)] + pub primitive_topology: PrimitiveTopology, + pub has_indices: bool, + #[cfg(feature = "morph")] + pub has_morph_targets: bool, + pub asset_usage: RenderAssetUsages, + pub enable_raytracing: bool, + pub aabb: Option, + pub skinned_mesh_bounds: Option>, +} + +impl GetRetainedAsset for Mesh { + type RetainedAsset = RetainedMesh; } impl Mesh { @@ -341,15 +254,14 @@ impl Mesh { pub fn new(primitive_topology: PrimitiveTopology, asset_usage: RenderAssetUsages) -> Self { Mesh { primitive_topology, - attributes: MeshExtractableData::Data(Default::default()), - indices: MeshExtractableData::NoData, + attributes: Default::default(), + indices: None, #[cfg(feature = "morph")] - morph_targets: MeshExtractableData::NoData, + morph_targets: None, #[cfg(feature = "morph")] - morph_target_names: MeshExtractableData::NoData, + morph_target_names: None, asset_usage, enable_raytracing: true, - final_aabb: None, skinned_mesh_bounds: None, } } @@ -366,33 +278,12 @@ impl Mesh { /// /// # Panics /// Panics when the format of the values does not match the attribute's format. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_insert_attribute`] #[inline] pub fn insert_attribute( &mut self, attribute: MeshVertexAttribute, values: impl Into, ) { - self.try_insert_attribute(attribute, values) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the data for a vertex attribute (position, normal, etc.). The name will - /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - /// - /// # Panics - /// Panics when the format of the values does not match the attribute's format. - #[inline] - pub fn try_insert_attribute( - &mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) -> Result<(), MeshAccessError> { let values = values.into(); let values_format = VertexFormat::from(&values); if values_format != attribute.format { @@ -403,9 +294,7 @@ impl Mesh { } self.attributes - .as_mut()? .insert(attribute.id, MeshAttributeData { attribute, values }); - Ok(()) } /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). @@ -414,12 +303,6 @@ impl Mesh { /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the format of the values does not match the attribute's format. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_inserted_attribute`] - #[must_use] #[inline] pub fn with_inserted_attribute( mut self, @@ -430,272 +313,86 @@ impl Mesh { self } - /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). - /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_with_inserted_attribute( - mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) -> Result { - self.try_insert_attribute(attribute, values)?; - Ok(self) - } - /// Removes the data for a vertex attribute - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_remove_attribute`] + /// Returns None if the attribute does not exist. pub fn remove_attribute( &mut self, attribute: impl Into, ) -> Option { self.attributes - .as_mut() - .expect(MESH_EXTRACTED_ERROR) - .remove(&attribute.into()) - .map(|data| data.values) - } - - /// Removes the data for a vertex attribute - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - pub fn try_remove_attribute( - &mut self, - attribute: impl Into, - ) -> Result { - Ok(self - .attributes - .as_mut()? .remove(&attribute.into()) - .ok_or(MeshAccessError::NotFound)? - .values) + .map(|attr| attr.values) } /// Consumes the mesh and returns a mesh without the data for a vertex attribute /// /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place) /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_removed_attribute`] - #[must_use] + /// Returns an error if the attribute does not exist. pub fn with_removed_attribute(mut self, attribute: impl Into) -> Self { self.remove_attribute(attribute); self } - /// Consumes the mesh and returns a mesh without the data for a vertex attribute - /// - /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - pub fn try_with_removed_attribute( - mut self, - attribute: impl Into, - ) -> Result { - self.try_remove_attribute(attribute)?; - Ok(self) - } - /// Returns a bool indicating if the attribute is present in this mesh's vertex data. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_contains_attribute`] #[inline] pub fn contains_attribute(&self, id: impl Into) -> bool { - self.attributes - .as_ref() - .expect(MESH_EXTRACTED_ERROR) - .contains_key(&id.into()) - } - - /// Returns a bool indicating if the attribute is present in this mesh's vertex data. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_contains_attribute( - &self, - id: impl Into, - ) -> Result { - Ok(self.attributes.as_ref()?.contains_key(&id.into())) + self.attributes.contains_key(&id.into()) } /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attribute`] or [`Mesh::try_attribute_option`] #[inline] pub fn attribute( &self, id: impl Into, ) -> Option<&VertexAttributeValues> { - self.try_attribute_option(id).expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_attribute( - &self, - id: impl Into, - ) -> Result<&VertexAttributeValues, MeshAccessError> { - self.try_attribute_option(id)? - .ok_or(MeshAccessError::NotFound) - } - - /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_attribute_option( - &self, - id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self - .attributes - .as_ref()? - .get(&id.into()) - .map(|data| &data.values)) + self.attributes.get(&id.into()).map(|attr| &attr.values) } /// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. #[inline] - pub(crate) fn try_attribute_data( + pub(crate) fn attribute_data( &self, id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self.attributes.as_ref()?.get(&id.into())) + ) -> Option<&MeshAttributeData> { + self.attributes.get(&id.into()) } /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attribute_mut`] #[inline] pub fn attribute_mut( &mut self, id: impl Into, ) -> Option<&mut VertexAttributeValues> { - self.try_attribute_mut_option(id) - .expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_attribute_mut( - &mut self, - id: impl Into, - ) -> Result<&mut VertexAttributeValues, MeshAccessError> { - self.try_attribute_mut_option(id)? - .ok_or(MeshAccessError::NotFound) - } - - /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_attribute_mut_option( - &mut self, - id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self - .attributes - .as_mut()? + self.attributes .get_mut(&id.into()) - .map(|data| &mut data.values)) + .map(|attr| &mut attr.values) } /// Returns an iterator that yields references to the data of each vertex attribute. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attributes`] pub fn attributes( &self, ) -> impl Iterator { - self.try_attributes().expect(MESH_EXTRACTED_ERROR) - } - - /// Returns an iterator that yields references to the data of each vertex attribute. - /// Returns an error if data has been extracted to `RenderWorld` - pub fn try_attributes( - &self, - ) -> Result, MeshAccessError> - { - Ok(self - .attributes - .as_ref()? + self.attributes .values() - .map(|data| (&data.attribute, &data.values))) + .map(|data| (&data.attribute, &data.values)) } /// Returns an iterator that yields mutable references to the data of each vertex attribute. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attributes_mut`] pub fn attributes_mut( &mut self, ) -> impl Iterator { - self.try_attributes_mut().expect(MESH_EXTRACTED_ERROR) - } - - /// Returns an iterator that yields mutable references to the data of each vertex attribute. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_attributes_mut( - &mut self, - ) -> Result< - impl Iterator, - MeshAccessError, - > { - Ok(self - .attributes - .as_mut()? + self.attributes .values_mut() - .map(|data| (&data.attribute, &mut data.values))) + .map(|data| (&data.attribute, &mut data.values)) } /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants /// that use triangles. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_insert_indices`] #[inline] pub fn insert_indices(&mut self, indices: Indices) { - self.indices - .replace(Some(indices)) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the - /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants - /// that use triangles. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_insert_indices(&mut self, indices: Indices) -> Result<(), MeshAccessError> { - self.indices.replace(Some(indices))?; - Ok(()) + self.indices.replace(indices); } /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles @@ -703,10 +400,6 @@ impl Mesh { /// [`PrimitiveTopology`] variants that use triangles. /// /// (Alternatively, you can use [`Mesh::insert_indices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_inserted_indices`] #[must_use] #[inline] pub fn with_inserted_indices(mut self, indices: Indices) -> Self { @@ -714,126 +407,42 @@ impl Mesh { self } - /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles - /// are constructed out of the vertex attributes and are therefore only useful for the - /// [`PrimitiveTopology`] variants that use triangles. - /// - /// (Alternatively, you can use [`Mesh::try_insert_indices`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_with_inserted_indices(mut self, indices: Indices) -> Result { - self.try_insert_indices(indices)?; - Ok(self) - } - /// Retrieves the vertex `indices` of the mesh, returns None if not found. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_indices`] #[inline] pub fn indices(&self) -> Option<&Indices> { - self.indices.as_ref_option().expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the vertex `indices` of the mesh. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_indices(&self) -> Result<&Indices, MeshAccessError> { self.indices.as_ref() } - /// Retrieves the vertex `indices` of the mesh, returns None if not found. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_option(&self) -> Result, MeshAccessError> { - self.indices.as_ref_option() - } - /// Retrieves the vertex `indices` of the mesh mutably. #[inline] pub fn indices_mut(&mut self) -> Option<&mut Indices> { - self.try_indices_mut_option().expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the vertex `indices` of the mesh mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_mut(&mut self) -> Result<&mut Indices, MeshAccessError> { self.indices.as_mut() } - /// Retrieves the vertex `indices` of the mesh mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_mut_option(&mut self) -> Result, MeshAccessError> { - self.indices.as_mut_option() - } - /// Removes the vertex `indices` from the mesh and returns them. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_remove_indices`] #[inline] pub fn remove_indices(&mut self) -> Option { - self.try_remove_indices().expect(MESH_EXTRACTED_ERROR) - } - - /// Removes the vertex `indices` from the mesh and returns them. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_remove_indices(&mut self) -> Result, MeshAccessError> { - self.indices.replace(None) + self.indices.take() } /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. /// /// (Alternatively, you can use [`Mesh::remove_indices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_removed_indices`] #[must_use] pub fn with_removed_indices(mut self) -> Self { self.remove_indices(); self } - /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. - /// - /// (Alternatively, you can use [`Mesh::try_remove_indices`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_removed_indices(mut self) -> Result { - self.try_remove_indices()?; - Ok(self) - } - /// Returns the size of a vertex in bytes. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn get_vertex_size(&self) -> u64 { self.attributes - .as_ref() - .expect(MESH_EXTRACTED_ERROR) .values() .map(|data| data.attribute.format.size()) .sum() } /// Returns the size required for the vertex buffer in bytes. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn get_vertex_buffer_size(&self) -> usize { let vertex_size = self.get_vertex_size() as usize; let vertex_count = self.count_vertices(); @@ -842,11 +451,8 @@ impl Mesh { /// Computes and returns the index data of the mesh as bytes. /// This is used to transform the index data into a GPU friendly format. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { - let mesh_indices = self.indices.as_ref_option().expect(MESH_EXTRACTED_ERROR); + let mesh_indices = self.indices.as_ref(); mesh_indices.as_ref().map(|indices| match &indices { Indices::U16(indices) => cast_slice(&indices[..]), @@ -856,27 +462,19 @@ impl Mesh { /// If any morph displacements are present, returns them as a /// [`MorphAttributes`] array. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to the render - /// world. #[cfg(feature = "morph")] pub fn get_morph_targets(&self) -> Option<&[MorphAttributes]> { self.morph_targets - .as_ref_option() - .expect(MESH_EXTRACTED_ERROR) + .as_ref() .map(|morph_attributes| &morph_attributes[..]) } /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn get_mesh_vertex_buffer_layout( &self, mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, ) -> MeshVertexBufferLayoutRef { - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); + let mesh_attributes = &self.attributes; let mut attributes = Vec::with_capacity(mesh_attributes.len()); let mut attribute_ids = Vec::with_capacity(mesh_attributes.len()); @@ -905,12 +503,9 @@ impl Mesh { /// Counts all vertices of the mesh. /// /// If the attributes have different vertex counts, the smallest is returned. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn count_vertices(&self) -> usize { let mut vertex_count: Option = None; - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); + let mesh_attributes = &self.attributes; for (attribute_id, attribute_data) in mesh_attributes { let attribute_len = attribute_data.values.len(); @@ -942,9 +537,6 @@ impl Mesh { /// /// This is a convenience method which allocates a Vec. /// Prefer pre-allocating and using [`Mesh::write_packed_vertex_buffer_data`] when possible. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn create_packed_vertex_buffer_data(&self) -> Vec { let mut attributes_interleaved_buffer = vec![0; self.get_vertex_buffer_size()]; self.write_packed_vertex_buffer_data(WriteOnly::from_mut( @@ -959,11 +551,8 @@ impl Mesh { /// /// If the vertex attributes have different lengths, they are all truncated to /// the length of the smallest. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. pub fn write_packed_vertex_buffer_data(&self, mut slice: WriteOnly<'_, [u8]>) { - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); + let mesh_attributes = &self.attributes; let vertex_size = self.get_vertex_size() as usize; let vertex_count = self.count_vertices(); @@ -991,30 +580,16 @@ impl Mesh { /// /// This can dramatically increase the vertex count, so make sure this is what you want. /// Does nothing if no [Indices] are set. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_duplicate_vertices`] pub fn duplicate_vertices(&mut self) { - self.try_duplicate_vertices().expect(MESH_EXTRACTED_ERROR); - } - - /// Duplicates the vertex attributes so that no vertices are shared. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [Indices] are set. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_duplicate_vertices(&mut self) -> Result<(), MeshAccessError> { fn duplicate(values: &[T], indices: impl Iterator) -> Vec { indices.map(|i| values[i]).collect() } - let Some(indices) = self.indices.replace(None)? else { - return Ok(()); + let Some(indices) = self.indices.take() else { + return; }; - let mesh_attributes = self.attributes.as_mut()?; + let mesh_attributes = &mut self.attributes; for attributes in mesh_attributes.values_mut() { let indices = indices.iter(); @@ -1070,8 +645,6 @@ impl Mesh { VertexAttributeValues::Unorm8x4Bgra(vec) => *vec = duplicate(vec, indices), } } - - Ok(()) } /// Consumes the mesh and returns a mesh with no shared vertices. @@ -1080,42 +653,20 @@ impl Mesh { /// Does nothing if no [`Indices`] are set. /// /// (Alternatively, you can use [`Mesh::duplicate_vertices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_duplicated_vertices`] #[must_use] pub fn with_duplicated_vertices(mut self) -> Self { self.duplicate_vertices(); self } - /// Consumes the mesh and returns a mesh with no shared vertices. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [`Indices`] are set. - /// - /// (Alternatively, you can use [`Mesh::try_duplicate_vertices`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_duplicated_vertices(mut self) -> Result { - self.try_duplicate_vertices()?; - Ok(self) - } - /// Remove duplicate vertices and create the index pointing to the unique vertices. /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. /// Returns an error if the mesh already has [`Indices`] set, even if there /// are duplicate vertices. If deduplication is needed with indices already set, /// consider calling [`Mesh::duplicate_vertices`] and then this function. pub fn merge_duplicate_vertices(&mut self) -> Result<(), MeshMergeDuplicateVerticesError> { - match self.try_indices() { - Ok(_) => return Err(MeshMergeDuplicateVerticesError::IndicesAlreadySet), - Err(err) => match err { - MeshAccessError::ExtractedToRenderWorld => return Err(err.into()), - MeshAccessError::NotFound => (), - }, + if self.indices().is_some() { + return Err(MeshMergeDuplicateVerticesError::IndicesAlreadySet); } #[derive(Copy, Clone)] @@ -1153,11 +704,10 @@ impl Mesh { } } - let old_attributes = self.attributes.as_ref()?; + let old_attributes = &self.attributes; let mut new_attributes: BTreeMap = self .attributes - .as_ref()? .iter() .map(|(k, v)| { ( @@ -1197,8 +747,8 @@ impl Mesh { v.values.shrink_to_fit(); } - self.attributes = MeshExtractableData::Data(new_attributes); - self.indices = MeshExtractableData::Data(Indices::U32(indices)); + self.attributes = new_attributes; + self.indices = Some(Indices::U32(indices)); Ok(()) } @@ -1207,7 +757,6 @@ impl Mesh { /// /// (Alternatively, you can use [`Mesh::merge_duplicate_vertices`] to mutate an existing mesh in-place) /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. /// Returns an error if the mesh already has [`Indices`] set, even if there /// are duplicate vertices. If deduplication is needed with indices already set, /// consider calling [`Mesh::duplicate_vertices`] and then this function. @@ -1260,7 +809,7 @@ impl Mesh { } } - let mesh_indices = self.indices.as_mut_option()?; + let mesh_indices = self.indices.as_mut(); match mesh_indices { Some(Indices::U16(vec)) => invert(vec, self.primitive_topology), @@ -1284,28 +833,15 @@ impl Mesh { /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].= - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_normals`] pub fn compute_normals(&mut self) { - self.try_compute_normals().expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].= - pub fn try_compute_normals(&mut self) -> Result<(), MeshAccessError> { assert!( matches!(self.primitive_topology, PrimitiveTopology::TriangleList), "`compute_normals` can only work on `TriangleList`s" ); - if self.try_indices_option()?.is_none() { - self.try_compute_flat_normals() + if self.indices().is_none() { + self.compute_flat_normals(); } else { - self.try_compute_smooth_normals() + self.compute_smooth_normals(); } } @@ -1316,30 +852,13 @@ impl Mesh { /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal /// attributes. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_flat_normals`] /// /// FIXME: This should handle more cases since this is called as a part of gltf /// mesh loading where we can't really blame users for loading meshes that might /// not conform to the limitations here! pub fn compute_flat_normals(&mut self) { - self.try_compute_flat_normals().expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// - /// # Panics - /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal - /// attributes. - /// - /// FIXME: This should handle more cases since this is called as a part of gltf - /// mesh loading where we can't really blame users for loading meshes that might - /// not conform to the limitations here! - pub fn try_compute_flat_normals(&mut self) -> Result<(), MeshAccessError> { assert!( - self.try_indices_option()?.is_none(), + self.indices().is_none(), "`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`." ); assert!( @@ -1348,7 +867,8 @@ impl Mesh { ); let positions = self - .try_attribute(Mesh::ATTRIBUTE_POSITION)? + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() .as_float3() .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); @@ -1358,7 +878,7 @@ impl Mesh { .flat_map(|normal| [normal; 3]) .collect(); - self.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared @@ -1377,31 +897,8 @@ impl Mesh { /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_smooth_normals`] pub fn compute_smooth_normals(&mut self) { - self.try_compute_smooth_normals() - .expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the angles of the corners of connected triangles, thus - /// eliminating triangle area and count as factors in the final normal. This does make it - /// somewhat slower than [`Mesh::compute_area_weighted_normals`] which does not need to - /// greedily normalize each triangle's normal or calculate corner angles. - /// - /// If you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::compute_area_weighted_normals`] instead. If you need to weight them in some other - /// way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_compute_smooth_normals(&mut self) -> Result<(), MeshAccessError> { - self.try_compute_custom_smooth_normals(|[a, b, c], positions, normals| { + self.compute_custom_smooth_normals(|[a, b, c], positions, normals| { let pa = Vec3::from(positions[a]); let pb = Vec3::from(positions[b]); let pc = Vec3::from(positions[c]); @@ -1435,32 +932,7 @@ impl Mesh { normals[a] += normal * weight_a; normals[b] += normal * weight_b; normals[c] += normal * weight_c; - }) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. - /// - /// This method is actually somewhat faster than [`Mesh::compute_smooth_normals`] because an - /// intermediate result of triangle normal calculation is already scaled by the triangle's area. - /// - /// If you would rather have the computed normals be influenced only by the angles of connected - /// edges, see [`Mesh::compute_smooth_normals`] instead. If you need to weight them in some - /// other way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_area_weighted_normals`] - pub fn compute_area_weighted_normals(&mut self) { - self.try_compute_area_weighted_normals() - .expect(MESH_EXTRACTED_ERROR); + }); } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared @@ -1480,70 +952,18 @@ impl Mesh { /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_compute_area_weighted_normals(&mut self) -> Result<(), MeshAccessError> { - self.try_compute_custom_smooth_normals(|[a, b, c], positions, normals| { - let normal = Vec3::from(triangle_area_normal( - positions[a], - positions[b], - positions[c], - )); - [a, b, c].into_iter().for_each(|pos| { - normals[pos] += normal; - }); - }) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method allows you to customize how normals are weighted via the `per_triangle` parameter, - /// which must be a function or closure that accepts 3 parameters: - /// - The indices of the three vertices of the triangle as a `[usize; 3]`. - /// - A reference to the values of the [`Mesh::ATTRIBUTE_POSITION`] of the mesh (`&[[f32; 3]]`). - /// - A mutable reference to the sums of all normals so far. - /// - /// See also the standard methods included in Bevy for calculating smooth normals: - /// - [`Mesh::compute_smooth_normals`] - /// - [`Mesh::compute_area_weighted_normals`] - /// - /// An example that would weight each connected triangle's normal equally, thus skewing normals - /// towards the planes divided into the most triangles: - /// ``` - /// # use bevy_asset::RenderAssetUsages; - /// # use bevy_mesh::{Mesh, PrimitiveTopology, Meshable, MeshBuilder}; - /// # use bevy_math::{Vec3, primitives::Cuboid}; - /// # let mut mesh = Cuboid::default().mesh().build(); - /// mesh.compute_custom_smooth_normals(|[a, b, c], positions, normals| { - /// let normal = Vec3::from(bevy_mesh::triangle_normal(positions[a], positions[b], positions[c])); - /// for idx in [a, b, c] { - /// normals[idx] += normal; - /// } - /// }); - /// ``` - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_custom_smooth_normals`] - // - // FIXME: This should handle more cases since this is called as a part of gltf - // mesh loading where we can't really blame users for loading meshes that might - // not conform to the limitations here! - // - // When fixed, also update "Panics" sections of - // - [Mesh::compute_smooth_normals] - // - [Mesh::with_computed_smooth_normals] - // - [Mesh::compute_area_weighted_normals] - // - [Mesh::with_computed_area_weighted_normals] - pub fn compute_custom_smooth_normals( - &mut self, - per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]), - ) { - self.try_compute_custom_smooth_normals(per_triangle) - .expect(MESH_EXTRACTED_ERROR); + /// Panics if the mesh does not have indices defined. + pub fn compute_area_weighted_normals(&mut self) { + self.compute_custom_smooth_normals(|[a, b, c], positions, normals| { + let normal = Vec3::from(triangle_area_normal( + positions[a], + positions[b], + positions[c], + )); + [a, b, c].into_iter().for_each(|pos| { + normals[pos] += normal; + }); + }); } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared @@ -1588,27 +1008,29 @@ impl Mesh { // - [Mesh::with_computed_smooth_normals] // - [Mesh::compute_area_weighted_normals] // - [Mesh::with_computed_area_weighted_normals] - pub fn try_compute_custom_smooth_normals( + pub fn compute_custom_smooth_normals( &mut self, mut per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]), - ) -> Result<(), MeshAccessError> { + ) { assert!( matches!(self.primitive_topology, PrimitiveTopology::TriangleList), "smooth normals can only be computed on `TriangleList`s" ); assert!( - self.try_indices_option()?.is_some(), + self.indices().is_some(), "smooth normals can only be computed on indexed meshes" ); let positions = self - .try_attribute(Mesh::ATTRIBUTE_POSITION)? + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap() .as_float3() .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); let mut normals = vec![Vec3::ZERO; positions.len()]; - self.try_indices()? + self.indices() + .unwrap() .iter() .collect::>() .chunks_exact(3) @@ -1618,24 +1040,7 @@ impl Mesh { *normal = normal.try_normalize().unwrap_or(Vec3::ZERO); } - self.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_normals`] - #[must_use] - pub fn with_computed_normals(self) -> Self { - self.try_with_computed_normals() - .expect(MESH_EXTRACTED_ERROR) + self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. @@ -1647,9 +1052,9 @@ impl Mesh { /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - pub fn try_with_computed_normals(mut self) -> Result { - self.try_compute_normals()?; - Ok(self) + pub fn with_computed_normals(mut self) -> Self { + self.compute_normals(); + self } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. @@ -1660,26 +1065,11 @@ impl Mesh { /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh has indices defined - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_flat_normals`] pub fn with_computed_flat_normals(mut self) -> Self { self.compute_flat_normals(); self } - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh has indices defined - pub fn try_with_computed_flat_normals(mut self) -> Result { - self.try_compute_flat_normals()?; - Ok(self) - } - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place) @@ -1692,28 +1082,10 @@ impl Mesh { /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_smooth_normals`] pub fn with_computed_smooth_normals(mut self) -> Self { self.compute_smooth_normals(); self } - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the angles of triangle corners connected to each vertex. If - /// you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::with_computed_area_weighted_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_with_computed_smooth_normals(mut self) -> Result { - self.try_compute_smooth_normals()?; - Ok(self) - } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// @@ -1728,31 +1100,11 @@ impl Mesh { /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_area_weighted_normals`] pub fn with_computed_area_weighted_normals(mut self) -> Self { self.compute_area_weighted_normals(); self } - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_area_weighted_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. If you would rather have the computed normals be influenced - /// only by the angles of connected edges, see [`Mesh::with_computed_smooth_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_with_computed_area_weighted_normals(mut self) -> Result { - self.try_compute_area_weighted_normals()?; - Ok(self) - } - /// Generate tangents for the mesh using the `mikktspace` algorithm. /// /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. @@ -1760,7 +1112,7 @@ impl Mesh { #[cfg(feature = "bevy_mikktspace")] pub fn generate_tangents(&mut self) -> Result<(), super::GenerateTangentsError> { let tangents = super::generate_tangents_for_mesh(self)?; - self.try_insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents)?; + self.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); Ok(()) } @@ -1809,8 +1161,8 @@ impl Mesh { let index_offset = self.count_vertices(); // Extend attributes of `self` with attributes of `other`. - for (attribute, values) in self.try_attributes_mut()? { - if let Some(other_values) = other.try_attribute_option(attribute.id)? { + for (attribute, values) in self.attributes_mut() { + if let Some(other_values) = other.attribute(attribute.id) { #[expect( clippy::match_same_arms, reason = "Although the bindings on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever." @@ -1848,7 +1200,7 @@ impl Mesh { return Err(MeshMergeError::IncompatibleVertexAttributes { self_attribute: *attribute, other_attribute: other - .try_attribute_data(attribute.id)? + .attribute_data(attribute.id) .map(|data| data.attribute), }) } @@ -1857,9 +1209,7 @@ impl Mesh { } // Extend indices of `self` with indices of `other`. - if let (Some(indices), Some(other_indices)) = - (self.try_indices_mut_option()?, other.try_indices_option()?) - { + if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) { indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32)); } Ok(()) @@ -1868,39 +1218,15 @@ impl Mesh { /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_transformed_by`] pub fn transformed_by(mut self, transform: Transform) -> Self { self.transform_by(transform); self } - /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_transformed_by(mut self, transform: Transform) -> Result { - self.try_transform_by(transform)?; - Ok(self) - } - /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_transform_by`] pub fn transform_by(&mut self, transform: Transform) { - self.try_transform_by(transform) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_transform_by(&mut self, transform: Transform) -> Result<(), MeshAccessError> { // Needed when transforming normals and tangents let scale_recip = 1. / transform.scale; debug_assert!( @@ -1909,7 +1235,7 @@ impl Mesh { ); if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply scale, rotation, and translation to vertex positions positions @@ -1922,11 +1248,11 @@ impl Mesh { && transform.scale.x == transform.scale.y && transform.scale.y == transform.scale.z { - return Ok(()); + return; } if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { @@ -1937,7 +1263,7 @@ impl Mesh { } if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { @@ -1948,99 +1274,48 @@ impl Mesh { .to_array(); }); } - - Ok(()) } /// Translates the vertex positions of the mesh by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_translated_by`] pub fn translated_by(mut self, translation: Vec3) -> Self { self.translate_by(translation); self } - /// Translates the vertex positions of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_translated_by(mut self, translation: Vec3) -> Result { - self.try_translate_by(translation)?; - Ok(self) - } - /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_translate_by`] pub fn translate_by(&mut self, translation: Vec3) { - self.try_translate_by(translation) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_translate_by(&mut self, translation: Vec3) -> Result<(), MeshAccessError> { if translation == Vec3::ZERO { - return Ok(()); + return; } if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply translation to vertex positions positions .iter_mut() .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array()); } - - Ok(()) } /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_rotated_by`] pub fn rotated_by(mut self, rotation: Quat) -> Self { - self.try_rotate_by(rotation).expect(MESH_EXTRACTED_ERROR); + self.rotate_by(rotation); self } - /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_rotated_by(mut self, rotation: Quat) -> Result { - self.try_rotate_by(rotation)?; - Ok(self) - } - /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_rotate_by`] pub fn rotate_by(&mut self, rotation: Quat) { - self.try_rotate_by(rotation).expect(MESH_EXTRACTED_ERROR); - } - - /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_rotate_by(&mut self, rotation: Quat) -> Result<(), MeshAccessError> { if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply rotation to vertex positions positions @@ -2050,11 +1325,11 @@ impl Mesh { // No need to transform normals or tangents if rotation is near identity if rotation.is_near_identity() { - return Ok(()); + return; } if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals normals.iter_mut().for_each(|normal| { @@ -2063,7 +1338,7 @@ impl Mesh { } if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents tangents.iter_mut().for_each(|tangent| { @@ -2073,45 +1348,20 @@ impl Mesh { .to_array(); }); } - - Ok(()) } /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_scaled_by`] pub fn scaled_by(mut self, scale: Vec3) -> Self { self.scale_by(scale); self } - /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_scaled_by(mut self, scale: Vec3) -> Result { - self.try_scale_by(scale)?; - Ok(self) - } - /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_scale_by`] pub fn scale_by(&mut self, scale: Vec3) { - self.try_scale_by(scale).expect(MESH_EXTRACTED_ERROR); - } - - /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_scale_by(&mut self, scale: Vec3) -> Result<(), MeshAccessError> { // Needed when transforming normals and tangents let scale_recip = 1. / scale; debug_assert!( @@ -2120,7 +1370,7 @@ impl Mesh { ); if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply scale to vertex positions positions @@ -2130,11 +1380,11 @@ impl Mesh { // No need to transform normals or tangents if scale is uniform if scale.x == scale.y && scale.y == scale.z { - return Ok(()); + return; } if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals, taking into account non-uniform scaling normals.iter_mut().for_each(|normal| { @@ -2143,7 +1393,7 @@ impl Mesh { } if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents, taking into account non-uniform scaling tangents.iter_mut().for_each(|tangent| { @@ -2155,24 +1405,12 @@ impl Mesh { .to_array(); }); } - - Ok(()) } /// Normalize joint weights so they sum to 1. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_normalize_joint_weights`] pub fn normalize_joint_weights(&mut self) { - self.try_normalize_joint_weights() - .expect(MESH_EXTRACTED_ERROR); - } - - /// Normalize joint weights so they sum to 1. - pub fn try_normalize_joint_weights(&mut self) -> Result<(), MeshAccessError> { if let Some(VertexAttributeValues::Float32x4(joints)) = - self.try_attribute_mut_option(Self::ATTRIBUTE_JOINT_WEIGHT)? + self.attribute_mut(Self::ATTRIBUTE_JOINT_WEIGHT) { for weights in joints.iter_mut() { // force negative weights to zero @@ -2190,8 +1428,6 @@ impl Mesh { } } } - - Ok(()) } /// Get a list of this Mesh's [triangles] as an iterator if possible. @@ -2204,13 +1440,16 @@ impl Mesh { /// [primitive topology]: PrimitiveTopology /// [triangles]: Triangle3d pub fn triangles(&self) -> Result + '_, MeshTrianglesError> { - let position_data = self.try_attribute(Mesh::ATTRIBUTE_POSITION)?; - + let Some(position_data) = self.attribute(Mesh::ATTRIBUTE_POSITION) else { + return Err(MeshTrianglesError::MissingPositions); + }; let Some(vertices) = position_data.as_float3() else { return Err(MeshTrianglesError::PositionsFormat); }; - let indices = self.try_indices()?; + let Some(indices) = self.indices() else { + return Err(MeshTrianglesError::MissingIndices); + }; match self.primitive_topology { PrimitiveTopology::TriangleList => { @@ -2288,57 +1527,14 @@ impl Mesh { } } - /// Extracts the mesh vertex, index and morph target data for GPU upload. - /// This function is called internally in render world extraction, it is - /// unlikely to be useful outside of that context. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn take_gpu_data(&mut self) -> Result { - let attributes = self.attributes.extract()?; - let indices = self.indices.extract()?; - #[cfg(feature = "morph")] - let morph_targets = self.morph_targets.extract()?; - #[cfg(feature = "morph")] - let morph_target_names = self.morph_target_names.extract()?; - - // store the aabb extents as they cannot be computed after extraction - if let Some(MeshAttributeData { - values: VertexAttributeValues::Float32x3(position_values), - .. - }) = attributes - .as_ref_option()? - .and_then(|attrs| attrs.get(&Self::ATTRIBUTE_POSITION.id)) - && !position_values.is_empty() - { - let mut iter = position_values.iter().map(|p| Vec3::from_slice(p)); - let mut min = iter.next().unwrap(); - let mut max = min; - for v in iter { - min = Vec3::min(min, v); - max = Vec3::max(max, v); - } - self.final_aabb = Some(Aabb3d::from_min_max(min, max)); - } - - Ok(Self { - attributes, - indices, - #[cfg(feature = "morph")] - morph_targets, - #[cfg(feature = "morph")] - morph_target_names, - ..self.clone() - }) - } - /// Get this mesh's [`SkinnedMeshBounds`]. pub fn skinned_mesh_bounds(&self) -> Option<&SkinnedMeshBounds> { - self.skinned_mesh_bounds.as_ref() + self.skinned_mesh_bounds.as_deref() } /// Set this mesh's [`SkinnedMeshBounds`]. pub fn set_skinned_mesh_bounds(&mut self, skinned_mesh_bounds: Option) { - self.skinned_mesh_bounds = skinned_mesh_bounds; + self.skinned_mesh_bounds = skinned_mesh_bounds.map(Arc::new); } /// Consumes the mesh and returns a mesh with the given [`SkinnedMeshBounds`]. @@ -2352,7 +1548,7 @@ impl Mesh { /// Generate [`SkinnedMeshBounds`] for this mesh. pub fn generate_skinned_mesh_bounds(&mut self) -> Result<(), SkinnedMeshBoundsError> { - self.skinned_mesh_bounds = Some(SkinnedMeshBounds::from_mesh(self)?); + self.skinned_mesh_bounds = Some(Arc::new(SkinnedMeshBounds::from_mesh(self)?)); Ok(()) } @@ -2361,69 +1557,49 @@ impl Mesh { self.generate_skinned_mesh_bounds()?; Ok(self) } + + /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space + /// + /// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of + /// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices. + pub fn compute_aabb(&self) -> Option { + let mut aabb = None; + if let Some(VertexAttributeValues::Float32x3(position_values)) = + self.attribute(Mesh::ATTRIBUTE_POSITION) + && !position_values.is_empty() + { + let mut iter = position_values.iter().map(|p| Vec3::from_slice(p)); + let mut min = iter.next().unwrap(); + let mut max = min; + for v in iter { + min = Vec3::min(min, v); + max = Vec3::max(max, v); + } + aabb = Some(Aabb3d::from_min_max(min, max)); + } + aabb + } } #[cfg(feature = "morph")] impl Mesh { /// Whether this mesh has morph targets. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_has_morph_targets`] pub fn has_morph_targets(&self) -> bool { - self.try_has_morph_targets().expect(MESH_EXTRACTED_ERROR) - } - - /// Whether this mesh has morph targets. - pub fn try_has_morph_targets(&self) -> Result { - Ok(self.morph_targets.as_ref_option()?.is_some()) - } - - /// Set the [morph target] displacements for this mesh. - /// - /// [morph target]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_targets`] - #[cfg(feature = "morph")] - pub fn set_morph_targets(&mut self, morph_targets: Vec) { - self.try_set_morph_targets(morph_targets) - .expect(MESH_EXTRACTED_ERROR); + self.morph_targets.is_some() } /// Set the [morph target] displacements for this mesh. /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation #[cfg(feature = "morph")] - pub fn try_set_morph_targets( - &mut self, - morph_targets: Vec, - ) -> Result<(), MeshAccessError> { - self.morph_targets.replace(Some(morph_targets))?; - Ok(()) + pub fn set_morph_targets(&mut self, morph_targets: Vec) { + self.morph_targets.replace(morph_targets); } /// Retrieve the morph target displacements for this mesh, or None if there /// are no morph targets. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_morph_targets`] #[cfg(feature = "morph")] pub fn morph_targets(&self) -> Option<&Vec> { - self.morph_targets - .as_ref_option() - .expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieve the morph displacements for this mesh, or None if there are no - /// morph targets. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the morph targets do not exist. - #[cfg(feature = "morph")] - pub fn try_morph_targets(&self) -> Result<&Vec, MeshAccessError> { self.morph_targets.as_ref() } @@ -2433,10 +1609,6 @@ impl Mesh { /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place) /// /// [morph target]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_morph_targets`] #[must_use] #[cfg(feature = "morph")] pub fn with_morph_targets(mut self, morph_targets: Vec) -> Self { @@ -2444,89 +1616,23 @@ impl Mesh { self } - /// Consumes the mesh and returns a mesh with the given [morph targets]. - /// - /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place) - /// - /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[cfg(feature = "morph")] - pub fn try_with_morph_targets( - mut self, - morph_targets: Vec, - ) -> Result { - self.try_set_morph_targets(morph_targets)?; - Ok(self) - } - /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_target_names`] pub fn set_morph_target_names(&mut self, names: Vec) { - self.try_set_morph_target_names(names) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_set_morph_target_names( - &mut self, - names: Vec, - ) -> Result<(), MeshAccessError> { - self.morph_target_names.replace(Some(names))?; - Ok(()) - } - - /// Consumes the mesh and returns a mesh with morph target names. - /// Names should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_target_names`] - #[must_use] - pub fn with_morph_target_names(self, names: Vec) -> Self { - self.try_with_morph_target_names(names) - .expect(MESH_EXTRACTED_ERROR) + self.morph_target_names.replace(names); } /// Consumes the mesh and returns a mesh with morph target names. /// Names should correspond to the order of the morph targets in `set_morph_targets`. /// /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_morph_target_names( - mut self, - names: Vec, - ) -> Result { - self.try_set_morph_target_names(names)?; - Ok(self) + pub fn with_morph_target_names(mut self, names: Vec) -> Self { + self.set_morph_target_names(names); + self } /// Gets a list of all morph target names, if they exist. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_morph_target_names`] pub fn morph_target_names(&self) -> Option<&[String]> { - self.try_morph_target_names().expect(MESH_EXTRACTED_ERROR) - } - - /// Gets a list of all morph target names, if they exist. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the morph targets do not exist. - pub fn try_morph_target_names(&self) -> Result, MeshAccessError> { - Ok(self - .morph_target_names - .as_ref_option()? - .map(core::ops::Deref::deref)) + self.morph_target_names.as_deref() } } @@ -2597,9 +1703,6 @@ impl SerializedMesh { primitive_topology: mesh.primitive_topology, attributes: mesh .attributes - .replace(None) - .expect(MESH_EXTRACTED_ERROR) - .unwrap() .into_iter() .map(|(id, data)| { ( @@ -2608,7 +1711,7 @@ impl SerializedMesh { ) }) .collect(), - indices: mesh.indices.replace(None).expect(MESH_EXTRACTED_ERROR), + indices: mesh.indices.take(), } } @@ -2672,7 +1775,7 @@ impl MeshDeserializer { /// See the documentation for [`SerializedMesh`] for caveats. pub fn deserialize(&self, serialized_mesh: SerializedMesh) -> Mesh { Mesh { - attributes: MeshExtractableData::Data( + attributes: serialized_mesh .attributes .into_iter() @@ -2689,8 +1792,8 @@ impl MeshDeserializer { }; Some((id, data)) }) - .collect()), - indices: serialized_mesh.indices.into(), + .collect(), + indices: serialized_mesh.indices, ..Mesh::new(serialized_mesh.primitive_topology, RenderAssetUsages::default()) } } @@ -2701,8 +1804,6 @@ impl MeshDeserializer { pub enum MeshMergeDuplicateVerticesError { #[error("Index attribute already set.")] IndicesAlreadySet, - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } /// Error that can occur when calling [`Mesh::merge`]. @@ -2722,8 +1823,6 @@ pub enum MeshMergeError { self_primitive_topology: PrimitiveTopology, other_primitive_topology: PrimitiveTopology, }, - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } #[cfg(test)] @@ -2734,7 +1833,6 @@ mod tests { use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues}; use crate::PrimitiveTopology; use bevy_asset::RenderAssetUsages; - use bevy_math::bounding::Aabb3d; use bevy_math::primitives::Triangle3d; use bevy_math::Vec3; use bevy_transform::components::Transform; @@ -3090,29 +2188,6 @@ mod tests { ); } - #[test] - fn take_gpu_data_calculates_aabb() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [-0.5, 0., 0.], - [-1., 0., 0.], - [-1., -1., 0.], - [-0.5, -1., 0.], - ], - ); - mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0])); - mesh = mesh.take_gpu_data().unwrap(); - assert_eq!( - mesh.final_aabb, - Some(Aabb3d::from_min_max([-1., -1., 0.], [-0.5, 0., 0.])) - ); - } - #[cfg(feature = "serialize")] #[test] fn serialize_deserialize_mesh() { diff --git a/crates/bevy_mesh/src/mikktspace.rs b/crates/bevy_mesh/src/mikktspace.rs index 36b24353c7c13..6c7b2a1cb653a 100644 --- a/crates/bevy_mesh/src/mikktspace.rs +++ b/crates/bevy_mesh/src/mikktspace.rs @@ -1,5 +1,3 @@ -use crate::MeshAccessError; - use super::{Indices, Mesh, VertexAttributeValues}; use thiserror::Error; use wgpu_types::{PrimitiveTopology, VertexFormat}; @@ -72,8 +70,6 @@ pub enum GenerateTangentsError { InvalidVertexAttributeFormat(&'static str, VertexFormat), #[error("mesh not suitable for tangent generation")] MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError), - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } pub(crate) fn generate_tangents_for_mesh( @@ -84,7 +80,7 @@ pub(crate) fn generate_tangents_for_mesh( other => return Err(GenerateTangentsError::UnsupportedTopology(other)), }; - let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or( + let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), )?; let VertexAttributeValues::Float32x3(positions) = positions else { @@ -93,7 +89,7 @@ pub(crate) fn generate_tangents_for_mesh( VertexFormat::Float32x3, )); }; - let normals = mesh.try_attribute_option(Mesh::ATTRIBUTE_NORMAL)?.ok_or( + let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name), )?; let VertexAttributeValues::Float32x3(normals) = normals else { @@ -102,7 +98,7 @@ pub(crate) fn generate_tangents_for_mesh( VertexFormat::Float32x3, )); }; - let uvs = mesh.try_attribute_option(Mesh::ATTRIBUTE_UV_0)?.ok_or( + let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name), )?; let VertexAttributeValues::Float32x2(uvs) = uvs else { @@ -115,7 +111,7 @@ pub(crate) fn generate_tangents_for_mesh( let len = positions.len(); let tangents = vec![[0., 0., 0., 0.]; len]; let mut mikktspace_mesh = MikktspaceGeometryHelper { - indices: mesh.try_indices_option()?, + indices: mesh.indices(), positions, normals, uvs, diff --git a/crates/bevy_mesh/src/skinning.rs b/crates/bevy_mesh/src/skinning.rs index 45325021d1440..37de09ad65950 100644 --- a/crates/bevy_mesh/src/skinning.rs +++ b/crates/bevy_mesh/src/skinning.rs @@ -1,4 +1,4 @@ -use crate::{Mesh, MeshVertexAttribute, VertexAttributeValues, VertexFormat}; +use crate::{Mesh, MeshVertexAttribute, RetainedMesh, VertexAttributeValues, VertexFormat}; use bevy_asset::{AsAssetId, Asset, AssetId, Handle}; use bevy_ecs::{ component::Component, entity::Entity, prelude::ReflectComponent, system::Query, @@ -190,12 +190,12 @@ pub enum EntityAabbFromSkinnedMeshBoundsError { /// encloses the skinned vertices of the mesh. pub fn entity_aabb_from_skinned_mesh_bounds( joint_entities: &Query<&GlobalTransform>, - mesh: &Mesh, + mesh: &RetainedMesh, skinned_mesh: &SkinnedMesh, skinned_mesh_inverse_bindposes: &SkinnedMeshInverseBindposes, world_from_entity: Option<&GlobalTransform>, ) -> Result { - let Some(skinned_mesh_bounds) = mesh.skinned_mesh_bounds() else { + let Some(skinned_mesh_bounds) = &mesh.skinned_mesh_bounds else { return Err(EntityAabbFromSkinnedMeshBoundsError::MissingSkinnedMeshBounds); }; diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index dc99a9688a97e..895f9abbfc497 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -12,7 +12,9 @@ //! For prefiltered environment maps, see [`bevy_light::EnvironmentMapLight`]. //! These components are intended to be added to a camera. use bevy_app::{App, Plugin, Update}; -use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Assets, RenderAssetUsages}; +use bevy_asset::{ + embedded_asset, load_embedded_asset, AssetServer, Assets, RenderAssetUsages, RetainedAssets, +}; use bevy_core_pipeline::mip_generation::{self, DownsampleShaders, DownsamplingConstants}; use bevy_ecs::{ component::Component, @@ -1018,11 +1020,12 @@ pub fn filtering_system( pub fn generate_environment_map_light( mut commands: Commands, mut images: ResMut>, + retained_images: RetainedAssets, query: Query<(Entity, &GeneratedEnvironmentMapLight), Without>, ) { for (entity, filtered_env_map) in &query { // Validate and fetch the source cubemap so we can size our targets correctly - let Some(src_image) = images.get(&filtered_env_map.environment_map) else { + let Some(src_image) = retained_images.get(&filtered_env_map.environment_map) else { // Texture not ready yet – try again next frame continue; }; diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 78d98f46ebf7d..c774e3d688b3b 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -4,7 +4,9 @@ use crate::material_bind_groups::{ use crate::*; use alloc::sync::Arc; use bevy_asset::prelude::AssetChanged; -use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId}; +use bevy_asset::{ + Asset, AssetEventSystems, AssetId, AssetServer, EmptyRetainedAsset, UntypedAssetId, +}; use bevy_camera::visibility::ViewVisibility; use bevy_core_pipeline::core_3d::TransparentSortingInfo3d; use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; @@ -1512,6 +1514,7 @@ where M::Data: PartialEq + Eq + Hash + Clone, { type SourceAsset = M; + type RetainedAsset = EmptyRetainedAsset; type ErasedAsset = PreparedMaterial; type Param = ( @@ -1533,6 +1536,10 @@ where M::Param, ); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( material: Self::SourceAsset, material_id: AssetId, diff --git a/crates/bevy_pbr/src/medium.rs b/crates/bevy_pbr/src/medium.rs index e0025a009b4da..723fc247e24b2 100644 --- a/crates/bevy_pbr/src/medium.rs +++ b/crates/bevy_pbr/src/medium.rs @@ -2,7 +2,7 @@ use bevy_color::ColorToComponents; use bevy_light::atmosphere::{ScatteringMedium, ScatteringTerm}; use bevy_app::{App, Plugin}; -use bevy_asset::AssetId; +use bevy_asset::{AssetId, EmptyRetainedAsset}; use bevy_ecs::{ resource::Resource, system::{Commands, Res, SystemParamItem}, @@ -63,9 +63,13 @@ pub struct GpuScatteringMedium { impl RenderAsset for GpuScatteringMedium { type SourceAsset = ScatteringMedium; - + type RetainedAsset = EmptyRetainedAsset; type Param = (Res<'static, RenderDevice>, Res<'static, RenderQueue>); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( source_asset: Self::SourceAsset, _asset_id: AssetId, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6b90cffa7149c..acc3327337d5a 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -2766,7 +2766,6 @@ pub fn build_dummy_white_gpu_image( sampler, texture_descriptor: image.texture_descriptor, texture_view_descriptor: image.texture_view_descriptor, - had_data: true, } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 3b8581abfbcf6..897179ddf60e6 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -8,7 +8,7 @@ use crate::{ use bevy_app::{App, Plugin, PostUpdate, Startup}; use bevy_asset::{ embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp, - AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId, + AssetEventSystems, AssetId, AssetServer, Assets, EmptyRetainedAsset, Handle, UntypedAssetId, }; use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d}; use bevy_color::{Color, ColorToComponents}; @@ -947,8 +947,13 @@ impl AsAssetId for Mesh3dWireframe { impl RenderAsset for RenderWireframeMaterial { type SourceAsset = WireframeMaterial; + type RetainedAsset = EmptyRetainedAsset; type Param = (); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( source_asset: Self::SourceAsset, _asset_id: AssetId, diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index 4d5aa6d81fd8a..3c6bc01853cfa 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -46,26 +46,21 @@ pub(super) fn ray_intersection_over_mesh( return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list } // Vertex positions are required - let positions = mesh - .try_attribute(Mesh::ATTRIBUTE_POSITION) - .ok()? - .as_float3()?; + let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?.as_float3()?; // Normals are optional let normals = mesh - .try_attribute(Mesh::ATTRIBUTE_NORMAL) - .ok() + .attribute(Mesh::ATTRIBUTE_NORMAL) .and_then(|normal_values| normal_values.as_float3()); let uvs = mesh - .try_attribute(Mesh::ATTRIBUTE_UV_0) - .ok() + .attribute(Mesh::ATTRIBUTE_UV_0) .and_then(|uvs| match uvs { VertexAttributeValues::Float32x2(uvs) => Some(uvs.as_slice()), _ => None, }); - match mesh.try_indices().ok() { + match mesh.indices() { Some(Indices::U16(indices)) => { ray_mesh_intersection(ray, transform, positions, normals, Some(indices), uvs, cull) } diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index 057537ea57a39..55953274d1a45 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -17,7 +17,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use intersections::*; pub use intersections::{ray_aabb_intersection_3d, ray_mesh_intersection, RayMeshHit}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, Extractable, Handle}; use bevy_ecs::{prelude::*, system::lifetimeless::Read, system::SystemParam}; use bevy_math::FloatOrd; use bevy_transform::components::GlobalTransform; @@ -281,6 +281,9 @@ impl<'w, 's> MeshRayCast<'w, 's> { let Some(mesh) = self.meshes.get(mesh_handle) else { return; }; + let Extractable::Data(mesh) = mesh else { + return; + }; // Backfaces of 2d meshes are never culled, unlike 3d meshes. let backfaces = match (has_backfaces, mesh2d.is_some()) { diff --git a/crates/bevy_post_process/src/auto_exposure/compensation_curve.rs b/crates/bevy_post_process/src/auto_exposure/compensation_curve.rs index 8e60bbb2b0ce8..203124736b216 100644 --- a/crates/bevy_post_process/src/auto_exposure/compensation_curve.rs +++ b/crates/bevy_post_process/src/auto_exposure/compensation_curve.rs @@ -1,9 +1,9 @@ -use bevy_asset::{prelude::*, RenderAssetUsages}; +use bevy_asset::{prelude::*, EmptyRetainedAsset}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_math::{cubic_splines::CubicGenerator, FloatExt, Vec2}; use bevy_reflect::prelude::*; use bevy_render::{ - render_asset::{AssetExtractionError, RenderAsset}, + render_asset::RenderAsset, render_resource::{ Extent3d, ShaderType, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, UniformBuffer, @@ -186,17 +186,11 @@ pub(super) struct AutoExposureCompensationCurveUniform { impl RenderAsset for GpuAutoExposureCompensationCurve { type SourceAsset = AutoExposureCompensationCurve; + type RetainedAsset = EmptyRetainedAsset; type Param = (SRes, SRes); - fn asset_usage(_: &Self::SourceAsset) -> RenderAssetUsages { - RenderAssetUsages::RENDER_WORLD - } - - fn take_gpu_data( - source: &mut Self::SourceAsset, - _previous_gpu_asset: Option<&Self>, - ) -> Result { - Ok(source.clone()) + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset } fn prepare_asset( diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index ec0b832b5c9fb..10e5d00707626 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -284,6 +284,7 @@ impl NormalizedRenderTargetExt for NormalizedRenderTarget { }), NormalizedRenderTarget::Image(image_target) => images .get(&image_target.handle) + .and_then(|maybe_extracted| maybe_extracted.as_option_ref()) .map(|image| RenderTargetInfo { physical_size: image.size(), scale_factor: image_target.scale_factor, diff --git a/crates/bevy_render/src/erased_render_asset.rs b/crates/bevy_render/src/erased_render_asset.rs index eeba62d5ba519..54d708289969e 100644 --- a/crates/bevy_render/src/erased_render_asset.rs +++ b/crates/bevy_render/src/erased_render_asset.rs @@ -3,8 +3,10 @@ use crate::{ RenderStartup, RenderSystems, Res, }; use bevy_app::{App, Plugin, SubApp}; -use bevy_asset::RenderAssetUsages; -use bevy_asset::{Asset, AssetEvent, AssetId, Assets, UntypedAssetId}; +use bevy_asset::{ + Asset, AssetEvent, AssetId, Assets, ErasedRetainedAssets, Extractable, UntypedAssetId, +}; +use bevy_asset::{EmptyRetainedAsset, RenderAssetUsages}; use bevy_ecs::{ prelude::{Commands, IntoScheduleConfigs, Local, MessageReader, ResMut, Resource}, schedule::{ScheduleConfigs, SystemSet}, @@ -14,6 +16,7 @@ use bevy_ecs::{ use bevy_log::{debug, error}; use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::render_asset::RenderAssetBytesPerFrameLimiter; +use core::any::{Any, TypeId}; use core::marker::PhantomData; use thiserror::Error; @@ -39,6 +42,7 @@ pub struct AssetExtractionSystems; pub trait ErasedRenderAsset: Send + Sync + 'static { /// The representation of the asset in the "main world". type SourceAsset: Asset + Clone; + type RetainedAsset: Send + Sync + 'static; /// The target representation of the asset in the "render world". type ErasedAsset: Send + Sync + 'static + Sized; @@ -64,6 +68,18 @@ pub trait ErasedRenderAsset: Send + Sync + 'static { None } + /// Make a [`Self::RetainedAsset`] to be added to the [`ErasedRetainedAssets`]. + /// + /// During render asset extraction, assets that don't contain [`RenderAssetUsages::MAIN_WORLD`] will be extracted + /// and its data will be discarded. + /// + /// The retained asset is guaranteed to exist in the [`ErasedRetainedAssets`] for any [`RenderAssetUsages`], + /// unless the retained asset is [`EmptyRetainedAsset`], in which case the [`ErasedRetainedAssets`] of this asset is always empty. + /// To access the retained assets, use the [`bevy_asset::RetainedAssets`] system parameter. + /// + /// This is useful for retaining asset's metadata after extracted to render world. + fn retain_main_world_asset(source: &Self::SourceAsset) -> Self::RetainedAsset; + /// Prepares the [`ErasedRenderAsset::SourceAsset`] for the GPU by transforming it into a [`ErasedRenderAsset`]. /// /// ECS data may be accessed via `param`. @@ -117,7 +133,8 @@ impl Plugin for ErasedRenderAssetPlugin { fn build(&self, app: &mut App) { - app.init_resource::>(); + app.init_resource::>() + .init_resource::>(); } fn finish(&self, app: &mut App) { @@ -233,6 +250,7 @@ struct CachedExtractErasedRenderAssetSystemState { state: SystemState<( MessageReader<'static, 'static, AssetEvent>, ResMut<'static, Assets>, + ResMut<'static, ErasedRetainedAssets>, )>, } @@ -257,7 +275,7 @@ fn collect_erased_render_assets_to_reextract( mut render_assets: ResMut>, mut prepare_next_frame: ResMut>, ) { - let source_type_id = core::any::TypeId::of::(); + let source_type_id = TypeId::of::(); // ErasedRenderAssets is shared across all material types that produce // the same ErasedAsset type. Drain only the entries matching our SourceAsset. let mut ids = Vec::new(); @@ -294,62 +312,87 @@ pub(crate) fn extract_erased_render_asset( .map(|r| core::mem::take(&mut r.ids)) .filter(|ids| !ids.is_empty()); - main_world.resource_scope( - |world, mut cached_state: Mut>| { - let (mut events, mut assets) = cached_state.state.get_mut(world).unwrap(); + main_world.resource_scope(|world, mut cached_state: Mut>| { + let (mut events, mut assets, mut retained_assets) = cached_state.state.get_mut(world).unwrap(); + + if let Some(reextract_ids) = reextract_ids { + needs_extracting.extend(reextract_ids); + } - if let Some(reextract_ids) = reextract_ids { - needs_extracting.extend(reextract_ids); + for event in events.read() { + #[expect( + clippy::match_same_arms, + reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." + )] + match event { + AssetEvent::Added { id } => { + needs_extracting.insert(*id); + } + AssetEvent::Modified { id } => { + needs_extracting.insert(*id); + extracted_assets.modified.insert(*id); + } + AssetEvent::Removed { .. } => { + // We don't care that the asset was removed from Assets in the main world. + // An asset is only removed from ErasedRenderAssets when its last handle is dropped (AssetEvent::Unused). + } + AssetEvent::Unused { id } => { + needs_extracting.remove(id); + extracted_assets.modified.remove(id); + extracted_assets.removed.insert(*id); + retained_assets.remove(&id.untyped()); + } + AssetEvent::LoadedWithDependencies { .. } => { + // TODO: handle this + } } + } - for event in events.read() { - #[expect( - clippy::match_same_arms, - reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." - )] - match event { - AssetEvent::Added { id } => { - needs_extracting.insert(*id); - } - AssetEvent::Modified { id } => { - needs_extracting.insert(*id); - extracted_assets.modified.insert(*id); - } - AssetEvent::Removed { .. } => { - // We don't care that the asset was removed from Assets in the main world. - // An asset is only removed from ErasedRenderAssets when its last handle is dropped (AssetEvent::Unused). - } - AssetEvent::Unused { id } => { - needs_extracting.remove(id); - extracted_assets.modified.remove(id); - extracted_assets.removed.insert(*id); + for id in needs_extracting.drain() { + let Some(asset) = assets.get_mut_untracked(id) else { + continue; + }; + + if let Some(extractable_asset) = (asset as &mut dyn Any).downcast_mut::>() { + let Extractable::Data(asset_data) = &*extractable_asset else { + panic!("Asset is already extracted: {}", id); + }; + let retained_asset = A::retain_main_world_asset(asset_data); + if retained_asset.type_id() != TypeId::of::() { + retained_assets.insert(id.untyped(), retained_asset); + } + match A::asset_usage(asset_data) { + u if u == RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD => { + extracted_assets.extracted.push((id, asset_data.clone())); + extracted_assets.added.insert(id); } - AssetEvent::LoadedWithDependencies { .. } => { - // TODO: handle this + u if u == RenderAssetUsages::RENDER_WORLD => { + let asset_data = extractable_asset.take().as_option().unwrap(); + extracted_assets.extracted.push((id, asset_data)); + extracted_assets.added.insert(id); } + u if u == RenderAssetUsages::MAIN_WORLD => {} + _ => unreachable!(), } - } - - for id in needs_extracting.drain() { - if let Some(asset) = assets.get(id) { - let asset_usage = A::asset_usage(asset); - if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { - if asset_usage == RenderAssetUsages::RENDER_WORLD { - if let Some(asset) = assets.remove(id) { - extracted_assets.extracted.push((id, asset)); - extracted_assets.added.insert(id); - } - } else { - extracted_assets.extracted.push((id, asset.clone())); - extracted_assets.added.insert(id); - } + } else { + let asset_data = (asset as &dyn Any).downcast_ref::().unwrap(); + let retained_asset = A::retain_main_world_asset(asset_data); + if retained_asset.type_id() != TypeId::of::() { + retained_assets.insert(id.untyped(), retained_asset); + } + match A::asset_usage(asset_data) { + u if u.contains(RenderAssetUsages::RENDER_WORLD) => { + extracted_assets.extracted.push((id, asset_data.clone())); + extracted_assets.added.insert(id); } + u if u == RenderAssetUsages::MAIN_WORLD => {} + _ => unreachable!(), } } + } - cached_state.state.apply(world); - }, - ); + cached_state.state.apply(world); + }); } // TODO: consider storing inside system? diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index e60e10a41658a..3690ee8cbe700 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -5,15 +5,14 @@ pub mod morph; #[cfg(feature = "morph")] use crate::GpuResourceAppExt; use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, renderer::{RenderDevice, RenderQueue}, texture::GpuImage, RenderApp, }; use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin}; -use bevy_asset::{AssetId, RenderAssetUsages}; -use bevy_camera::primitives::MeshAabb; +use bevy_asset::{AssetId, GetRetainedAsset, RenderAssetUsages}; use bevy_ecs::{ prelude::*, system::{ @@ -21,6 +20,7 @@ use bevy_ecs::{ SystemParamItem, }, }; +use bevy_math::bounding::BoundingVolume; pub use bevy_mesh::*; use glam::Vec3; use wgpu::IndexFormat; @@ -119,7 +119,7 @@ pub enum RenderMeshBufferInfo { impl RenderAsset for RenderMesh { type SourceAsset = Mesh; - + type RetainedAsset = ::RetainedAsset; #[cfg(not(feature = "morph"))] type Param = ( SRes, @@ -140,15 +140,6 @@ impl RenderAsset for RenderMesh { mesh.asset_usage } - fn take_gpu_data( - source: &mut Self::SourceAsset, - _previous_gpu_asset: Option<&Self>, - ) -> Result { - source - .take_gpu_data() - .map_err(|_| AssetExtractionError::AlreadyExtracted) - } - fn byte_len(mesh: &Self::SourceAsset) -> Option { let mut vertex_size = 0; for attribute_data in mesh.attributes() { @@ -161,6 +152,19 @@ impl RenderAsset for RenderMesh { Some(vertex_size * vertex_count + index_bytes) } + fn retain_main_world_asset(source: &Self::SourceAsset) -> Self::RetainedAsset { + RetainedMesh { + primitive_topology: source.primitive_topology(), + has_indices: source.indices().is_some(), + #[cfg(feature = "morph")] + has_morph_targets: source.has_morph_targets(), + asset_usage: source.asset_usage, + enable_raytracing: source.enable_raytracing, + aabb: source.compute_aabb(), + skinned_mesh_bounds: source.skinned_mesh_bounds.clone(), + } + } + /// Converts the extracted mesh into a [`RenderMesh`]. fn prepare_asset( mesh: Self::SourceAsset, @@ -213,7 +217,7 @@ impl RenderAsset for RenderMesh { Ok(RenderMesh { vertex_count: mesh.count_vertices() as u32, aabb_center: match mesh.compute_aabb() { - Some(aabb) => aabb.center.into(), + Some(aabb) => aabb.center().into(), None => Vec3::ZERO, }, buffer_info, diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index a3ea3613d1c76..5b01fc3d3dbe2 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -3,7 +3,10 @@ use crate::{ RenderStartup, RenderSystems, Res, }; use bevy_app::{App, Plugin, SubApp}; -use bevy_asset::{Asset, AssetEvent, AssetId, Assets, RenderAssetUsages}; +use bevy_asset::{ + Asset, AssetEvent, AssetId, Assets, EmptyRetainedAsset, ErasedRetainedAssets, Extractable, + RenderAssetUsages, +}; use bevy_ecs::{ prelude::{Commands, IntoScheduleConfigs, Local, MessageReader, ResMut, Resource}, schedule::{ScheduleConfigs, SystemSet}, @@ -12,6 +15,7 @@ use bevy_ecs::{ }; use bevy_log::{debug, error}; use bevy_platform::collections::{HashMap, HashSet}; +use core::any::{Any, TypeId}; use core::marker::PhantomData; use core::sync::atomic::{AtomicUsize, Ordering}; use thiserror::Error; @@ -47,7 +51,7 @@ pub enum AssetExtractionError { pub trait RenderAsset: Send + Sync + 'static + Sized { /// The representation of the asset in the "main world". type SourceAsset: Asset + Clone; - + type RetainedAsset: Send + Sync + 'static; /// Specifies all ECS data required by [`RenderAsset::prepare_asset`]. /// /// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) [`SystemParam`]. @@ -70,6 +74,18 @@ pub trait RenderAsset: Send + Sync + 'static + Sized { None } + /// Make a [`Self::RetainedAsset`] to be added to the [`ErasedRetainedAssets`]. + /// + /// During render asset extraction, assets that don't contain [`RenderAssetUsages::MAIN_WORLD`] will be extracted + /// and its data will be discarded. + /// + /// The retained asset is guaranteed to exist in the [`ErasedRetainedAssets`] for any [`RenderAssetUsages`], + /// unless the retained asset is [`EmptyRetainedAsset`], in which case the [`ErasedRetainedAssets`] of this asset is always empty. + /// To access the retained assets, use the [`bevy_asset::RetainedAssets`] system parameter. + /// + /// This is useful for retaining asset's metadata after extracted to render world. + fn retain_main_world_asset(source: &Self::SourceAsset) -> Self::RetainedAsset; + /// Prepares the [`RenderAsset::SourceAsset`] for the GPU by transforming it into a [`RenderAsset`]. /// /// ECS data may be accessed via `param`. @@ -91,18 +107,6 @@ pub trait RenderAsset: Send + Sync + 'static + Sized { _param: &mut SystemParamItem, ) { } - - /// Make a copy of the asset to be moved to the `RenderWorld` / gpu. Heavy internal data (pixels, vertex attributes) - /// should be moved into the copy, leaving this asset with only metadata. - /// An error may be returned to indicate that the asset has already been extracted, and should not - /// have been modified on the CPU side (as it cannot be transferred to GPU again). - /// The previous GPU asset is also provided, which can be used to check if the modification is valid. - fn take_gpu_data( - _source: &mut Self::SourceAsset, - _previous_gpu_asset: Option<&Self>, - ) -> Result { - Err(AssetExtractionError::NoExtractionImplementation) - } } /// This plugin extracts the changed assets from the "app world" into the "render world" @@ -133,7 +137,9 @@ impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { - app.init_resource::>(); + app.init_resource::>() + .init_resource::>(); + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() @@ -243,7 +249,7 @@ struct CachedExtractRenderAssetSystemState { state: SystemState<( MessageReader<'static, 'static, AssetEvent>, ResMut<'static, Assets>, - Option>>, + ResMut<'static, ErasedRetainedAssets>, )>, } @@ -294,70 +300,89 @@ pub(crate) fn extract_render_asset( .map(|r| core::mem::take(&mut r.ids)) .filter(|ids| !ids.is_empty()); - main_world.resource_scope( - |world, mut cached_state: Mut>| { - let (mut events, mut assets, maybe_render_assets) = cached_state.state.get_mut(world).unwrap(); + main_world.resource_scope(|world, mut cached_state: Mut>| { + let (mut events, mut assets, mut retained_assets) = cached_state.state.get_mut(world).unwrap(); + + if let Some(reextract_ids) = reextract_ids { + needs_extracting.extend(reextract_ids); + } - if let Some(reextract_ids) = reextract_ids { - needs_extracting.extend(reextract_ids); + for event in events.read() { + #[expect( + clippy::match_same_arms, + reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." + )] + match event { + AssetEvent::Added { id } => { + needs_extracting.insert(*id); + } + AssetEvent::Modified { id } => { + needs_extracting.insert(*id); + extracted_assets.modified.insert(*id); + } + AssetEvent::Removed { .. } => { + // We don't care that the asset was removed from Assets in the main world. + // An asset is only removed from RenderAssets when its last handle is dropped (AssetEvent::Unused). + } + AssetEvent::Unused { id } => { + needs_extracting.remove(id); + extracted_assets.modified.remove(id); + extracted_assets.removed.insert(*id); + retained_assets.remove(&id.untyped()); + } + AssetEvent::LoadedWithDependencies { .. } => { + // TODO: handle this + } } + } - for event in events.read() { - #[expect( - clippy::match_same_arms, - reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." - )] - match event { - AssetEvent::Added { id } => { - needs_extracting.insert(*id); - } - AssetEvent::Modified { id } => { - needs_extracting.insert(*id); - extracted_assets.modified.insert(*id); - } - AssetEvent::Removed { .. } => { - // We don't care that the asset was removed from Assets in the main world. - // An asset is only removed from RenderAssets when its last handle is dropped (AssetEvent::Unused). - } - AssetEvent::Unused { id } => { - needs_extracting.remove(id); - extracted_assets.modified.remove(id); - extracted_assets.removed.insert(*id); + for id in needs_extracting.drain() { + let Some(asset) = assets.get_mut_untracked(id) else { + continue; + }; + + if let Some(extractable_asset) = (asset as &mut dyn Any).downcast_mut::>() { + let Extractable::Data(asset_data) = &*extractable_asset else { + panic!("Asset {} is already extracted", id); + }; + let retained_asset = A::retain_main_world_asset(asset_data); + if retained_asset.type_id() != TypeId::of::() { + retained_assets.insert(id.untyped(), retained_asset); + } + match A::asset_usage(asset_data) { + u if u == RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD => { + extracted_assets.extracted.push((id, asset_data.clone())); + extracted_assets.added.insert(id); } - AssetEvent::LoadedWithDependencies { .. } => { - // TODO: handle this + u if u == RenderAssetUsages::RENDER_WORLD => { + let asset_data = extractable_asset.take().as_option().unwrap(); + extracted_assets.extracted.push((id, asset_data)); + extracted_assets.added.insert(id); } + u if u == RenderAssetUsages::MAIN_WORLD => {} + _ => unreachable!(), } - } - - for id in needs_extracting.drain() { - if let Some(asset) = assets.get(id) { - let asset_usage = A::asset_usage(asset); - if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { - if asset_usage == RenderAssetUsages::RENDER_WORLD { - if let Some(asset) = assets.get_mut_untracked(id) { - let previous_asset = maybe_render_assets.as_ref().and_then(|render_assets| render_assets.get(id)); - match A::take_gpu_data(asset, previous_asset) { - Ok(gpu_data_asset) => { - extracted_assets.extracted.push((id, gpu_data_asset)); - extracted_assets.added.insert(id); - } - Err(e) => { - error!("{} with RenderAssetUsages == RENDER_WORLD cannot be extracted: {e}", core::any::type_name::()); - } - }; - } - } else { - extracted_assets.extracted.push((id, asset.clone())); - extracted_assets.added.insert(id); - } + } else { + let Some(asset_data) = (asset as &dyn Any).downcast_ref::() else { + panic!("RenderAsset::SourceAsset::Storage must be Extractable or SourceAsset, {}", id); + }; + let retained_asset = A::retain_main_world_asset(asset_data); + if retained_asset.type_id() != TypeId::of::() { + retained_assets.insert(id.untyped(), retained_asset); + } + match A::asset_usage(asset_data) { + u if u.contains(RenderAssetUsages::RENDER_WORLD) => { + extracted_assets.extracted.push((id, asset_data.clone())); + extracted_assets.added.insert(id); } + u if u == RenderAssetUsages::MAIN_WORLD => {} + _ => unreachable!(), } } + } - cached_state.state.apply(world); - }, - ); + cached_state.state.apply(world); + }); } // TODO: consider storing inside system? diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index eddfa555edd5a..45d209ab2d9e0 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -1,10 +1,10 @@ use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, render_resource::{Buffer, BufferUsages}, renderer::{RenderDevice, RenderQueue}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, RenderAssetUsages}; +use bevy_asset::{Asset, AssetApp, AssetId, GetRetainedAsset, RenderAssetUsages}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_utils::default; @@ -27,6 +27,7 @@ impl Plugin for StoragePlugin { #[derive(Asset, Reflect, Debug, Clone)] #[reflect(opaque)] #[reflect(Default, Debug, Clone)] +#[asset(extractable)] pub struct ShaderBuffer { /// Optional data used to initialize the buffer. pub data: Option>, @@ -38,6 +39,56 @@ pub struct ShaderBuffer { pub copy_on_resize: bool, } +/// The representation of a storage buffer that is retained in [`RetainedAssets`] on the main world after extracting. +/// +/// [`RetainedAssets`]: bevy_asset::RetainedAssets +#[derive(Reflect, Debug, Clone)] +#[reflect(opaque)] +#[reflect(Debug, Clone)] +pub struct RetainedShaderBuffer { + /// The buffer description used to create the buffer. + pub buffer_description: wgpu::BufferDescriptor<'static>, + /// The asset usage of the storage buffer. + pub asset_usage: RenderAssetUsages, + /// Whether this buffer should be copied on the GPU when resized. + pub copy_on_resize: bool, +} + +impl GetRetainedAsset for ShaderBuffer { + type RetainedAsset = RetainedShaderBuffer; +} + +impl From for ShaderBuffer { + fn from(value: RetainedShaderBuffer) -> Self { + ShaderBuffer { + data: None, + buffer_description: value.buffer_description, + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + +impl From for RetainedShaderBuffer { + fn from(value: ShaderBuffer) -> Self { + RetainedShaderBuffer { + buffer_description: value.buffer_description, + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + +impl From<&ShaderBuffer> for RetainedShaderBuffer { + fn from(value: &ShaderBuffer) -> Self { + RetainedShaderBuffer { + buffer_description: value.buffer_description.clone(), + asset_usage: value.asset_usage, + copy_on_resize: value.copy_on_resize, + } + } +} + impl Default for ShaderBuffer { fn default() -> Self { Self { @@ -134,26 +185,15 @@ pub struct GpuShaderBuffer { impl RenderAsset for GpuShaderBuffer { type SourceAsset = ShaderBuffer; + type RetainedAsset = ::RetainedAsset; type Param = (SRes, SRes); fn asset_usage(source_asset: &Self::SourceAsset) -> RenderAssetUsages { source_asset.asset_usage } - fn take_gpu_data( - source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) + fn retain_main_world_asset(source: &Self::SourceAsset) -> Self::RetainedAsset { + source.into() } fn prepare_asset( diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index a95bd22550278..2d793fc538c45 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -137,7 +137,6 @@ fn fallback_image_new( sampler, texture_descriptor: image.texture_descriptor, texture_view_descriptor: image.texture_view_descriptor, - had_data: true, } } diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index 73524c10a9ec5..c2c9877c37b9b 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -1,9 +1,9 @@ use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset}, + render_asset::{PrepareAssetError, RenderAsset}, render_resource::{DefaultImageSampler, Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, }; -use bevy_asset::{AssetId, RenderAssetUsages}; +use bevy_asset::{AssetId, GetRetainedAsset, RenderAssetUsages}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_image::{Image, ImageSampler}; use bevy_log::warn; @@ -20,11 +20,11 @@ pub struct GpuImage { pub sampler: Sampler, pub texture_descriptor: TextureDescriptor, &'static [TextureFormat]>, pub texture_view_descriptor: Option>>, - pub had_data: bool, } impl RenderAsset for GpuImage { type SourceAsset = Image; + type RetainedAsset = ::RetainedAsset; type Param = ( SRes, SRes, @@ -36,29 +36,15 @@ impl RenderAsset for GpuImage { image.asset_usage } - fn take_gpu_data( - source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - // check if this image originally had data and no longer does, that implies it - // has already been extracted - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) - } - #[inline] fn byte_len(image: &Self::SourceAsset) -> Option { image.data.as_ref().map(Vec::len) } + fn retain_main_world_asset(source: &Self::SourceAsset) -> Self::RetainedAsset { + source.into() + } + /// Converts the extracted image into a [`GpuImage`]. fn prepare_asset( image: Self::SourceAsset, @@ -175,7 +161,6 @@ impl RenderAsset for GpuImage { sampler, texture_descriptor: image.texture_descriptor, texture_view_descriptor: image.texture_view_descriptor, - had_data, }) } } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2c0f0ac0ced57..e97532e63ea04 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -39,12 +39,8 @@ pub mod prelude { }; } -use bevy_asset::Assets; -use bevy_camera::{ - primitives::{Aabb, MeshAabb}, - visibility::NoFrustumCulling, - visibility::VisibilitySystems, -}; +use bevy_asset::{Assets, RetainedAssets}; +use bevy_camera::{primitives::Aabb, visibility::NoFrustumCulling, visibility::VisibilitySystems}; use bevy_mesh::{Mesh, Mesh2d}; #[cfg(feature = "bevy_text")] use bevy_text::detect_text_needs_rerender; @@ -60,7 +56,7 @@ use bevy_app::prelude::*; use bevy_asset::prelude::AssetChanged; use bevy_camera::visibility::NoAutoAabb; use bevy_ecs::prelude::*; -use bevy_image::{Image, TextureAtlasLayout, TextureAtlasPlugin}; +use bevy_image::{Image, RetainedImage, TextureAtlasLayout, TextureAtlasPlugin}; use bevy_math::Vec2; /// Adds support for 2D sprites. @@ -117,8 +113,8 @@ impl Plugin for SpritePlugin { /// Used in system set [`VisibilitySystems::CalculateBounds`]. pub fn calculate_bounds_2d( mut commands: Commands, - meshes: Res>, - images: Res>, + meshes: RetainedAssets, + images: RetainedAssets, atlases: Res>, new_mesh_aabb: Query< (Entity, &Mesh2d), @@ -160,9 +156,9 @@ pub fn calculate_bounds_2d( // New meshes require inserting a component for (entity, mesh_handle) in &new_mesh_aabb { if let Some(mesh) = meshes.get(mesh_handle) - && let Some(aabb) = mesh.compute_aabb() + && let Some(aabb) = mesh.aabb { - commands.entity(entity).try_insert(aabb); + commands.entity(entity).try_insert(Aabb::from(aabb)); } } @@ -170,8 +166,8 @@ pub fn calculate_bounds_2d( update_mesh_aabb .par_iter_mut() .for_each(|(mesh_handle, mut aabb)| { - if let Some(new_aabb) = meshes.get(mesh_handle).and_then(MeshAabb::compute_aabb) { - aabb.set_if_neq(new_aabb); + if let Some(new_aabb) = meshes.get(mesh_handle).and_then(|m| m.aabb) { + aabb.set_if_neq(new_aabb.into()); } }); @@ -182,7 +178,7 @@ pub fn calculate_bounds_2d( .or_else(|| sprite.rect.map(|rect| rect.size())) .or_else(|| match &sprite.texture_atlas { // We default to the texture size for regular sprites - None => images.get(&sprite.image).map(Image::size_f32), + None => images.get(&sprite.image).map(RetainedImage::size_f32), // We default to the drawn rect for atlas sprites Some(atlas) => atlas .texture_rect(&atlases) @@ -222,7 +218,7 @@ pub fn calculate_bounds_2d( // inside the vertex shader which isn't recognized by calculate_aabb(). fn calculate_bounds_2d_sprite_mesh( mut commands: Commands, - images: Res>, + images: RetainedAssets, atlases: Res>, new_sprite_aabb: Query< (Entity, &SpriteMesh, &Anchor), @@ -248,7 +244,7 @@ fn calculate_bounds_2d_sprite_mesh( .or_else(|| sprite.rect.map(|rect| rect.size())) .or_else(|| match &sprite.texture_atlas { // We default to the texture size for regular sprites - None => images.get(&sprite.image).map(Image::size_f32), + None => images.get(&sprite.image).map(RetainedImage::size_f32), // We default to the drawn rect for atlas sprites Some(atlas) => atlas .texture_rect(&atlases) @@ -284,7 +280,10 @@ fn calculate_bounds_2d_sprite_mesh( #[cfg(test)] mod test { use super::*; + use bevy_asset::ErasedRetainedAssets; + use bevy_image::Image; use bevy_math::{Rect, Vec2, Vec3A}; + use bevy_mesh::{Mesh, RetainedMesh}; #[test] fn calculate_bounds_2d_create_aabb_for_image_sprite_entity() { @@ -293,10 +292,18 @@ mod test { // Add resources and get handle to image let mut image_assets = Assets::::default(); + let mut retained_image_assets = ErasedRetainedAssets::::default(); let image_handle = image_assets.add(Image::default()); + retained_image_assets.insert( + image_handle.id().untyped(), + RetainedImage::from(Image::default()), + ); app.insert_resource(image_assets); + app.insert_resource(retained_image_assets); let mesh_assets = Assets::::default(); + let retained_mesh_assets = ErasedRetainedAssets::::default(); app.insert_resource(mesh_assets); + app.insert_resource(retained_mesh_assets); let texture_atlas_assets = Assets::::default(); app.insert_resource(texture_atlas_assets); @@ -329,12 +336,21 @@ mod test { // Setup app let mut app = App::new(); + // Add resources and get handle to image // Add resources and get handle to image let mut image_assets = Assets::::default(); + let mut retained_image_assets = ErasedRetainedAssets::::default(); let image_handle = image_assets.add(Image::default()); + retained_image_assets.insert( + image_handle.id().untyped(), + RetainedImage::from(Image::default()), + ); app.insert_resource(image_assets); + app.insert_resource(retained_image_assets); let mesh_assets = Assets::::default(); + let retained_mesh_assets = ErasedRetainedAssets::::default(); app.insert_resource(mesh_assets); + app.insert_resource(retained_mesh_assets); let texture_atlas_assets = Assets::::default(); app.insert_resource(texture_atlas_assets); @@ -394,10 +410,18 @@ mod test { // Add resources and get handle to image let mut image_assets = Assets::::default(); + let mut retained_image_assets = ErasedRetainedAssets::::default(); let image_handle = image_assets.add(Image::default()); + retained_image_assets.insert( + image_handle.id().untyped(), + RetainedImage::from(Image::default()), + ); app.insert_resource(image_assets); + app.insert_resource(retained_image_assets); let mesh_assets = Assets::::default(); + let retained_mesh_assets = ErasedRetainedAssets::::default(); app.insert_resource(mesh_assets); + app.insert_resource(retained_mesh_assets); let texture_atlas_assets = Assets::::default(); app.insert_resource(texture_atlas_assets); diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 4a0a9d354e084..7d5b20b64a0f2 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -12,7 +12,7 @@ use crate::{Anchor, Sprite}; use bevy_app::prelude::*; -use bevy_asset::prelude::*; +use bevy_asset::{prelude::*, Extractable}; use bevy_camera::{ visibility::{RenderLayers, ViewVisibility}, Camera, Projection, RenderTarget, @@ -243,6 +243,9 @@ fn sprite_picking( // This handle doesn't return a valid image, so returning false here would make picking "color sprites" impossible break 'valid_pixel true; }; + let Extractable::Data(image) = image else { + break 'valid_pixel false; + }; // grab pixel and check alpha let color = match image.get_color_at( cursor_pixel_space.x as u32, diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 534c99e2f0423..64689149d0570 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -89,6 +89,7 @@ impl Sprite { ) -> Result { let image_size = images .get(&self.image) + .and_then(|maybe_extracted| maybe_extracted.as_option_ref()) .map(Image::size) .unwrap_or(UVec2::ONE); diff --git a/crates/bevy_sprite/src/sprite_mesh.rs b/crates/bevy_sprite/src/sprite_mesh.rs index 5462f3e7d5a12..40a3e1c7520d8 100644 --- a/crates/bevy_sprite/src/sprite_mesh.rs +++ b/crates/bevy_sprite/src/sprite_mesh.rs @@ -1,8 +1,8 @@ -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, Handle, RetainedAssets}; use bevy_camera::visibility::{Visibility, VisibilityClass}; use bevy_color::Color; use bevy_ecs::{component::Component, reflect::ReflectComponent, template::FromTemplate}; -use bevy_image::{Image, TextureAtlas, TextureAtlasLayout}; +use bevy_image::{Image, RetainedImage, TextureAtlas, TextureAtlasLayout}; use bevy_math::{Rect, UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, PartialReflect, Reflect}; use bevy_transform::components::Transform; @@ -103,12 +103,12 @@ impl SpriteMesh { &self, point_relative_to_sprite: Vec2, anchor: Anchor, - images: &Assets, + images: &RetainedAssets, texture_atlases: &Assets, ) -> Result { let image_size = images .get(&self.image) - .map(Image::size) + .map(RetainedImage::size) .unwrap_or(UVec2::ONE); let atlas_rect = self diff --git a/crates/bevy_sprite/src/text2d.rs b/crates/bevy_sprite/src/text2d.rs index bbce33f2b5662..78d56b84d9929 100644 --- a/crates/bevy_sprite/src/text2d.rs +++ b/crates/bevy_sprite/src/text2d.rs @@ -297,6 +297,7 @@ pub fn update_text2d_layout( e @ (TextError::FailedToAddGlyph(_) | TextError::MissingAtlasLayout | TextError::MissingAtlasTexture + | TextError::ExtractedAtlasTexture | TextError::InconsistentAtlasState), ) => { panic!("Fatal error when processing text: {e}."); @@ -326,6 +327,7 @@ pub fn update_text2d_layout( | TextError::FailedToGetGlyphImage(_) | TextError::MissingAtlasLayout | TextError::MissingAtlasTexture + | TextError::ExtractedAtlasTexture | TextError::InconsistentAtlasState | TextError::DegenerateScaleFactor), ) => { diff --git a/crates/bevy_sprite_render/src/mesh2d/material.rs b/crates/bevy_sprite_render/src/mesh2d/material.rs index 6a1b066720184..b015eff00a740 100644 --- a/crates/bevy_sprite_render/src/mesh2d/material.rs +++ b/crates/bevy_sprite_render/src/mesh2d/material.rs @@ -5,7 +5,8 @@ use crate::{ use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::prelude::AssetChanged; use bevy_asset::{ - AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, Handle, UntypedAssetId, + AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, EmptyRetainedAsset, + Handle, UntypedAssetId, }; use bevy_camera::visibility::ViewVisibility; use bevy_core_pipeline::{ @@ -1099,7 +1100,7 @@ impl PreparedMaterial2d { impl RenderAsset for PreparedMaterial2d { type SourceAsset = M; - + type RetainedAsset = EmptyRetainedAsset; type Param = ( SRes, SRes, @@ -1111,6 +1112,10 @@ impl RenderAsset for PreparedMaterial2d { M::Param, ); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( material: Self::SourceAsset, material_id: AssetId, diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index ec844c40e4ae5..44a7e665aacd5 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -5,7 +5,7 @@ use crate::{ use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; use bevy_asset::{ embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp, - AssetEventSystems, AssetId, AssetServer, Assets, Handle, UntypedAssetId, + AssetEventSystems, AssetId, AssetServer, Assets, EmptyRetainedAsset, Handle, UntypedAssetId, }; use bevy_camera::{visibility::ViewVisibility, Camera, Camera2d}; use bevy_color::{Color, ColorToComponents}; @@ -457,8 +457,13 @@ impl AsAssetId for Mesh2dWireframe { impl RenderAsset for RenderWireframeMaterial { type SourceAsset = Wireframe2dMaterial; + type RetainedAsset = EmptyRetainedAsset; type Param = (); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( source_asset: Self::SourceAsset, _asset_id: AssetId, diff --git a/crates/bevy_sprite_render/src/texture_slice/computed_slices.rs b/crates/bevy_sprite_render/src/texture_slice/computed_slices.rs index 55324faa658b0..0a851eb03786b 100644 --- a/crates/bevy_sprite_render/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite_render/src/texture_slice/computed_slices.rs @@ -1,5 +1,5 @@ use crate::{ExtractedSlice, TextureAtlasLayout}; -use bevy_asset::{AssetEvent, Assets}; +use bevy_asset::{AssetEvent, Assets, RetainedAssets}; use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_math::{Rect, Vec2}; @@ -56,7 +56,7 @@ impl ComputedTextureSlices { #[must_use] fn compute_sprite_slices( sprite: &Sprite, - images: &Assets, + images: &RetainedAssets, atlas_layouts: &Assets, ) -> Option { let (image_size, texture_rect) = match &sprite.texture_atlas { @@ -109,7 +109,7 @@ fn compute_sprite_slices( pub(crate) fn compute_slices_on_asset_event( mut commands: Commands, mut events: MessageReader>, - images: Res>, + images: RetainedAssets, atlas_layouts: Res>, sprites: Query<(Entity, &Sprite)>, ) { @@ -141,7 +141,7 @@ pub(crate) fn compute_slices_on_asset_event( /// System reacting to changes on the [`Sprite`] component to compute the sprite slices pub(crate) fn compute_slices_on_sprite_change( mut commands: Commands, - images: Res>, + images: RetainedAssets, atlas_layouts: Res>, changed_sprites: Query<(Entity, &Sprite), Changed>, ) { diff --git a/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs b/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs index f416ffd2dffc0..78bb68d4bb40a 100644 --- a/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs +++ b/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs @@ -1,6 +1,6 @@ use crate::{AlphaMode2d, MeshMaterial2d}; use bevy_app::{App, Plugin, Update}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, Extractable, Handle}; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -234,6 +234,13 @@ pub fn update_tilemap_chunk_indices( ); continue; }; + let Extractable::Data(tile_data_image) = &mut *tile_data_image else { + warn!( + "TilemapChunkMaterial tile data image is extracted to render world {}", + chunk_entity + ); + continue; + }; let Some(data) = tile_data_image.data.as_mut() else { warn!( "TilemapChunkMaterial tile data image data not found for tilemap chunk {}", diff --git a/crates/bevy_text/src/error.rs b/crates/bevy_text/src/error.rs index e52bd76c470af..886d5c5ee6ca4 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -23,6 +23,9 @@ pub enum TextError { /// Missing texture for the font atlas #[error("missing texture for the font atlas")] MissingAtlasTexture, + /// Extracted texture for the font atlas + #[error("the texture for the font atlas is extracted to render world")] + ExtractedAtlasTexture, /// Failed to find glyph in atlas after it was added #[error("failed to find glyph in atlas after it was added")] InconsistentAtlasState, diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index c696adc318ac5..23249099d9249 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -95,11 +95,14 @@ impl FontAtlas { let mut atlas_texture = textures .get_mut(&self.texture) .ok_or(TextError::MissingAtlasTexture)?; + let atlas_texture = atlas_texture + .as_option_mut() + .ok_or(TextError::ExtractedAtlasTexture)?; if let Ok(glyph_index) = self.dynamic_texture_atlas_builder.add_texture( &mut self.texture_atlas, texture, - &mut atlas_texture, + atlas_texture, ) { self.glyph_to_atlas_index.insert( key, diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 41c457d87a74f..10f7800f3e236 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -42,6 +42,7 @@ impl FontAtlasSet { .map(|font_atlas| { images .get(&font_atlas.texture) + .and_then(|maybe_extracted| maybe_extracted.as_option_ref()) .and_then(|image| image.data.as_ref()) .map_or(0, |data| data.len() as u64) }) diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index fa3fe0a12f3a4..16f4b5d58f630 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,8 +1,8 @@ use crate::{ComputedUiRenderTargetInfo, ContentSize, Measure, MeasureArgs, Node, NodeMeasure}; -use bevy_asset::{AsAssetId, AssetId, Assets, Handle}; +use bevy_asset::{AsAssetId, AssetId, Assets, Handle, RetainedAssets}; use bevy_color::Color; use bevy_ecs::prelude::*; -use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE}; +use bevy_image::{prelude::*, RetainedImage, TRANSPARENT_IMAGE_HANDLE}; use bevy_math::{Rect, UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_sprite::TextureSlicer; @@ -243,7 +243,7 @@ type UpdateImageFilter = (With, Without); /// Updates content size of the node based on the image provided pub fn update_image_content_size_system( - textures: Res>, + textures: RetainedAssets, atlases: Res>, mut query: Query< ( @@ -272,7 +272,7 @@ pub fn update_image_content_size_system( .map(|rect| rect.size().as_uvec2()) .or_else(|| match &image.texture_atlas { Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()), - None => textures.get(&image.image).map(Image::size), + None => textures.get(&image.image).map(RetainedImage::size), }) { // Update only if size or scale factor has changed to avoid needless layout calculations diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 4e1d08e536320..17f4206a70926 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -321,6 +321,7 @@ pub fn measure_text_system( | TextError::FailedToGetGlyphImage(_) | TextError::MissingAtlasLayout | TextError::MissingAtlasTexture + | TextError::ExtractedAtlasTexture | TextError::InconsistentAtlasState), ) => { panic!("Fatal error when processing text: {e}."); @@ -395,6 +396,7 @@ pub fn text_system( e @ (TextError::FailedToAddGlyph(_) | TextError::MissingAtlasLayout | TextError::MissingAtlasTexture + | TextError::ExtractedAtlasTexture | TextError::InconsistentAtlasState), ) => { panic!("Fatal error when processing text: {e}."); diff --git a/crates/bevy_ui/src/widget/viewport.rs b/crates/bevy_ui/src/widget/viewport.rs index 5668f94ddda60..78298d5ae9d44 100644 --- a/crates/bevy_ui/src/widget/viewport.rs +++ b/crates/bevy_ui/src/widget/viewport.rs @@ -185,6 +185,11 @@ pub fn update_viewport_render_target_size( continue; }; let size = size.as_uvec2().max(UVec2::ONE).to_extents(); - images.get_mut(image_handle).unwrap().resize(size); + images + .get_mut(image_handle) + .unwrap() + .as_option_mut() + .expect("Image is extracted to render world") + .resize(size); } } diff --git a/crates/bevy_ui_render/src/ui_material_pipeline.rs b/crates/bevy_ui_render/src/ui_material_pipeline.rs index e0f17d64fb262..7b3442d4ae25b 100644 --- a/crates/bevy_ui_render/src/ui_material_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_material_pipeline.rs @@ -555,7 +555,7 @@ pub struct PreparedUiMaterial { impl RenderAsset for PreparedUiMaterial { type SourceAsset = M; - + type RetainedAsset = EmptyRetainedAsset; type Param = ( SRes, SRes, @@ -563,6 +563,10 @@ impl RenderAsset for PreparedUiMaterial { M::Param, ); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( material: Self::SourceAsset, _: AssetId, diff --git a/crates/bevy_winit/src/cursor/mod.rs b/crates/bevy_winit/src/cursor/mod.rs index 85c56f6ff1114..826ddac064966 100644 --- a/crates/bevy_winit/src/cursor/mod.rs +++ b/crates/bevy_winit/src/cursor/mod.rs @@ -143,6 +143,8 @@ fn update_cursors( if cursor_cache.0.contains_key(&cache_key) { CursorSource::CustomCached(cache_key) } else { + use bevy_asset::Extractable; + let Some(image) = images.get(handle) else { tracing::warn!( "Cursor image {handle:?} is not loaded yet and couldn't be used. Trying again next frame." @@ -151,6 +153,14 @@ fn update_cursors( continue; }; + let Extractable::Data(image) = image else { + tracing::warn!( + "Cursor image {handle:?} is extracted to render world. \ + Please add `RenderAssetUsages::MAIN_WORLD` usage to it." + ); + continue; + }; + let (rect, needs_sub_image) = calculate_effective_rect(&texture_atlases, image, texture_atlas, rect); diff --git a/examples/2d/cpu_draw.rs b/examples/2d/cpu_draw.rs index d786bd3cdf514..6846cdf5d0fb2 100644 --- a/examples/2d/cpu_draw.rs +++ b/examples/2d/cpu_draw.rs @@ -121,7 +121,7 @@ fn draw( let (x, y) = (xy.x as u32, xy.y as u32); // Get the old color of that pixel. - let old_color = image.get_color_at(x, y).unwrap(); + let old_color = image.as_option_ref().unwrap().get_color_at(x, y).unwrap(); // If the old color is our current color, change our drawing color. let tolerance = 1.0 / 255.0; @@ -135,6 +135,8 @@ fn draw( // Set the new color, but keep old alpha value from image. image + .as_option_mut() + .unwrap() .set_color_at(x, y, draw_color.with_alpha(old_color.alpha())) .unwrap(); diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 2a68a86762d9a..aaab7295b10fd 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -7,6 +7,7 @@ //! Only one padded and one unpadded texture atlas are rendered to the screen. //! An upscaled sprite from each of the four atlases are rendered to the screen. +use bevy::asset::Extractable; use bevy::{asset::LoadedFolder, image::ImageSampler, prelude::*}; fn main() { @@ -232,6 +233,13 @@ fn create_texture_atlas( ); continue; }; + let Extractable::Data(texture) = texture else { + warn!( + "`Image` {} is extracted to render world.", + handle.path().unwrap() + ); + continue; + }; texture_atlas_builder.add_texture(Some(id), texture); } @@ -242,6 +250,7 @@ fn create_texture_atlas( // Update the sampling settings of the texture atlas let mut image = textures.get_mut(&texture).unwrap(); + let image = image.as_option_mut().unwrap(); image.sampler = sampling.unwrap_or_default(); (texture_atlas_layout, texture_atlas_sources, texture) diff --git a/examples/3d/generate_custom_mesh.rs b/examples/3d/generate_custom_mesh.rs index bdc3d250a07b4..463790db9d990 100644 --- a/examples/3d/generate_custom_mesh.rs +++ b/examples/3d/generate_custom_mesh.rs @@ -2,6 +2,7 @@ //! assign a custom UV mapping for a custom texture, //! and how to change the UV mapping at run-time. +use bevy::asset::Extractable; use bevy::{ asset::RenderAssetUsages, mesh::{Indices, VertexAttributeValues}, @@ -77,8 +78,11 @@ fn input_handler( ) { if keyboard_input.just_pressed(KeyCode::Space) { let mesh_handle = mesh_query.single().expect("Query not successful"); - let mesh = meshes.get_mut(mesh_handle).unwrap(); - toggle_texture(mesh.into_inner()); + let mut mesh = meshes.get_mut(mesh_handle).unwrap(); + let Extractable::Data(mesh) = &mut *mesh else { + panic!("Mesh is extracted to render world") + }; + toggle_texture(mesh); } if keyboard_input.pressed(KeyCode::KeyX) { for mut transform in &mut query { diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index ac1c837ddc237..724d0d6712eb6 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -13,6 +13,7 @@ //! //! * Clicking anywhere moves the object. +use bevy::asset::RetainedAssets; use bevy::{ color::palettes::css::*, light::Skybox, @@ -526,7 +527,7 @@ fn play_animations( } fn create_cubes( - image_assets: Res>, + image_assets: RetainedAssets, mut commands: Commands, irradiance_volumes: Query<(&IrradianceVolume, &GlobalTransform)>, voxel_cube_parents: Query>, diff --git a/examples/3d/reflection_probes.rs b/examples/3d/reflection_probes.rs index e216af22d5a17..57f6414e1a945 100644 --- a/examples/3d/reflection_probes.rs +++ b/examples/3d/reflection_probes.rs @@ -10,6 +10,7 @@ //! //! Reflection probes don't work on WebGL 2 or WebGPU. +use bevy::asset::Extractable; use bevy::{ camera::{Exposure, Hdr}, core_pipeline::tonemapping::Tonemapping, @@ -374,6 +375,7 @@ impl FromWorld for Cubemaps { fn setup_environment_map_usage(cubemaps: Res, mut images: ResMut>) { if let Some(mut image) = images.get_mut(&cubemaps.specular_environment_map) + && let Extractable::Data(image) = &mut *image && !image .texture_descriptor .usage diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 94ac4b98e001c..912908969201b 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -154,6 +154,7 @@ fn asset_loaded( if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle).is_loaded() { info!("Swapping to {}...", CUBEMAPS[cubemap.index].0); let mut image = images.get_mut(&cubemap.image_handle).unwrap(); + let image = image.as_option_mut().unwrap(); // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, // so they appear as one texture. The following code reconfigures the texture as necessary. if image.texture_descriptor.array_layer_count() == 1 { diff --git a/examples/3d/solari.rs b/examples/3d/solari.rs index c976807bd7c4b..fdbc7246e02a2 100644 --- a/examples/3d/solari.rs +++ b/examples/3d/solari.rs @@ -395,6 +395,7 @@ fn add_raytracing_meshes_on_scene_load( // Ensure meshes are Solari compatible let mut mesh = meshes.get_mut(mesh_handle).unwrap(); + let mesh = mesh.as_option_mut().unwrap(); if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) { let vertex_count = mesh.count_vertices(); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]); diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index a32f364beecb6..bb2850f2c0d4c 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -1,5 +1,6 @@ //! This examples compares Tonemapping options +use bevy::asset::RetainedAssets; use bevy::{ asset::UnapprovedPathMode, camera::Hdr, @@ -228,7 +229,7 @@ fn resize_image( image_mesh: Query<(&MeshMaterial3d, &Mesh3d), With>, materials: Res>, mut meshes: ResMut>, - images: Res>, + images: RetainedAssets, mut image_event_reader: MessageReader>, ) { for event in image_event_reader.read() { diff --git a/examples/animation/morph_targets.rs b/examples/animation/morph_targets.rs index 7db62b51f5ba2..d6b128c71a8c0 100644 --- a/examples/animation/morph_targets.rs +++ b/examples/animation/morph_targets.rs @@ -2,6 +2,7 @@ //! //! Also illustrates how to read morph target names in `name_morphs`. +use bevy::asset::Extractable; use bevy::{prelude::*, world_serialization::WorldInstanceReady}; use std::f32::consts::PI; @@ -86,6 +87,7 @@ fn name_morphs( if let AssetEvent::::Added { id } = event && let Some(path) = asset_server.get_path(*id) && let Some(mesh) = meshes.get(*id) + && let Extractable::Data(mesh) = &mesh && let Some(names) = mesh.morph_target_names() { info!("Morph target names for {path:?}:"); diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index f147251d20372..5e9f325ef3f2f 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -12,6 +12,7 @@ //! without gaps, it is simpler to use [`bevy::render::view::window::screenshot::Screenshot`] //! than this approach. +use bevy::asset::Extractable; use bevy::{ app::{AppExit, ScheduleRunnerPlugin}, camera::RenderTarget, @@ -471,6 +472,9 @@ fn update( for image in images_to_save.iter() { // Fill correct data from channel to image let mut img_bytes = images.get_mut(image.id()).unwrap(); + let Extractable::Data(img_bytes) = &mut *img_bytes else { + panic!("Image is extracted to render world") + }; // We need to ensure that this works regardless of the image dimensions // If the image became wider when copying from the texture to the buffer, diff --git a/examples/asset/alter_mesh.rs b/examples/asset/alter_mesh.rs index 400944997a588..1dea93c67c6f1 100644 --- a/examples/asset/alter_mesh.rs +++ b/examples/asset/alter_mesh.rs @@ -1,5 +1,6 @@ //! Shows how to modify mesh assets after spawning. +use bevy::asset::Extractable; use bevy::{ asset::RenderAssetUsages, gltf::GltfLoaderSettings, input::common_conditions::input_just_pressed, mesh::VertexAttributeValues, prelude::*, @@ -181,6 +182,9 @@ fn alter_mesh( let Some(mut mesh) = meshes.get_mut(*left_shape) else { return; }; + let Extractable::Data(mesh) = &mut *mesh else { + return; + }; // Now we can directly manipulate vertices on the mesh. Here, we're just scaling in and out // for demonstration purposes. This will affect all entities currently using the asset. diff --git a/examples/asset/alter_sprite.rs b/examples/asset/alter_sprite.rs index e0a5922285c12..5ea8573134f48 100644 --- a/examples/asset/alter_sprite.rs +++ b/examples/asset/alter_sprite.rs @@ -1,5 +1,6 @@ //! Shows how to modify texture assets after spawning. +use bevy::asset::Extractable; use bevy::{ asset::RenderAssetUsages, image::ImageLoaderSettings, input::common_conditions::input_just_pressed, prelude::*, @@ -128,6 +129,9 @@ fn alter_asset(mut images: ResMut>, left_bird: Single<&Sprite, Wit let Some(mut image) = images.get_mut(&left_bird.image) else { return; }; + let Extractable::Data(image) = &mut *image else { + return; + }; for pixel in image.data.as_mut().unwrap() { // Directly modify the asset data, which will affect all users of this asset. By diff --git a/examples/asset/asset_loading.rs b/examples/asset/asset_loading.rs index 49d2dca279025..c20c69adc9bf1 100644 --- a/examples/asset/asset_loading.rs +++ b/examples/asset/asset_loading.rs @@ -40,7 +40,7 @@ fn setup( // You might notice that this doesn't run! This is because assets load in parallel without // blocking. When an asset has loaded, it will appear in relevant Assets // collection. - info!("{:?}", sphere.primitive_topology()); + info!("{:?}", sphere.as_option_ref().map(Mesh::primitive_topology)); } else { info!("sphere hasn't loaded yet"); } diff --git a/examples/asset/asset_saving.rs b/examples/asset/asset_saving.rs index 6f06556f899c0..468426a1afb7b 100644 --- a/examples/asset/asset_saving.rs +++ b/examples/asset/asset_saving.rs @@ -41,6 +41,7 @@ fn perform_save( asset_server: Res, ) { let image = images.get(&image_to_save.0).unwrap(); + let image = image.as_option_ref().unwrap(); let image = image.clone(); let asset_server = asset_server.clone(); @@ -235,6 +236,7 @@ fn try_plot( }; let pixel_coordinates = pixel_space.floor().as_uvec2(); let mut image = images.get_mut(&sprite.image).unwrap(); + let image = image.as_option_mut().unwrap(); // For an actual drawing app, you'd at least draw a line from the last point, but this is // simpler. image diff --git a/examples/gltf/query_gltf_primitives.rs b/examples/gltf/query_gltf_primitives.rs index 3f1a4e57b8dbe..add8f3a96ba76 100644 --- a/examples/gltf/query_gltf_primitives.rs +++ b/examples/gltf/query_gltf_primitives.rs @@ -3,6 +3,7 @@ use std::f32::consts::PI; +use bevy::asset::Extractable; use bevy::{gltf::GltfMaterialName, mesh::VertexAttributeValues, prelude::*}; fn main() { @@ -35,6 +36,7 @@ fn find_top_material_and_mesh( } if let Some(mut mesh) = meshes.get_mut(mesh_handle) + && let Extractable::Data(mesh) = &mut *mesh && let Some(VertexAttributeValues::Float32x3(positions)) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) { diff --git a/examples/large_scenes/bevy_city/src/assets.rs b/examples/large_scenes/bevy_city/src/assets.rs index 370ae891757e3..3a454a9ac157b 100644 --- a/examples/large_scenes/bevy_city/src/assets.rs +++ b/examples/large_scenes/bevy_city/src/assets.rs @@ -286,6 +286,7 @@ fn merge_world_asset( let Some(mesh) = entity_ref .get::() .and_then(|mesh3d| meshes.get(mesh3d)) + .and_then(|mesh3d| mesh3d.as_option_ref()) else { continue; }; diff --git a/examples/large_scenes/mipmap_generator/src/lib.rs b/examples/large_scenes/mipmap_generator/src/lib.rs index 959b54560070f..0101fc2e11c6b 100644 --- a/examples/large_scenes/mipmap_generator/src/lib.rs +++ b/examples/large_scenes/mipmap_generator/src/lib.rs @@ -11,7 +11,7 @@ use fast_image_resize::{ResizeAlg, ResizeOptions, Resizer}; use tracing::warn; use bevy::{ - asset::RenderAssetUsages, + asset::{Extractable, RenderAssetUsages}, image::{ImageSampler, ImageSamplerDescriptor}, pbr::{ExtendedMaterial, MaterialExtension}, platform::collections::HashMap, @@ -232,7 +232,7 @@ pub struct MipmapTasks( pub struct MaterialHandle(pub Handle); #[allow(clippy::too_many_arguments)] -pub fn generate_mipmaps( +pub fn generate_mipmaps + GetImages>( mut commands: Commands, mut material_events: MessageReader>, mut materials: ResMut>, @@ -272,6 +272,9 @@ pub fn generate_mipmaps( continue; //There is already a task for this image } if let Some(mut image) = images.get_mut(image_h) { + let Extractable::Data(image) = &mut *image else { + panic!("Image is extracted to render world") + }; let mut descriptor = match image.sampler.clone() { ImageSampler::Default => default_sampler.0.clone(), ImageSampler::Descriptor(descriptor) => descriptor, @@ -279,7 +282,7 @@ pub fn generate_mipmaps( descriptor.anisotropy_clamp = settings.anisotropic_filtering; image.sampler = ImageSampler::Descriptor(descriptor); if image.texture_descriptor.mip_level_count == 1 - && check_image_compatible(&image).is_ok() + && check_image_compatible(image).is_ok() { let mut image = image.clone(); let settings = settings.clone(); @@ -314,7 +317,7 @@ pub fn generate_mipmaps( match future::block_on(future::poll_once(task)) { Some(task_data) => { if let Some(mut image) = images.get_mut(image_h) { - *image = task_data.image; + *image = Extractable::Data(task_data.image); progress.processed += 1; let prev_cached_data_gb = bytes_to_gb(progress.cached_data_size_bytes); progress.cached_data_size_bytes += task_data.added_cache_size; diff --git a/examples/shader/storage_buffer.rs b/examples/shader/storage_buffer.rs index d4e99f6a90ed6..085572f87b2c3 100644 --- a/examples/shader/storage_buffer.rs +++ b/examples/shader/storage_buffer.rs @@ -1,4 +1,5 @@ //! This example demonstrates how to use a storage buffer with `AsBindGroup` in a custom material. +use bevy::asset::{Extractable, RetainedAssets}; use bevy::{ mesh::MeshTag, prelude::*, @@ -70,11 +71,14 @@ fn update( material_handles: Res, mut materials: ResMut>, mut buffers: ResMut>, + retained_buffers: RetainedAssets, ) { let material = materials.get_mut(&material_handles.0).unwrap(); let mut buffer = buffers.get_mut(&material.colors).unwrap(); - buffer.set_data( + let retained_buffer = retained_buffers.get(&material.colors).unwrap(); + let mut new_buffer: ShaderBuffer = retained_buffer.clone().into(); + new_buffer.set_data( (0..5) .map(|i| { let t = time.elapsed_secs() * 5.0; @@ -87,6 +91,7 @@ fn update( }) .collect::>(), ); + *buffer = Extractable::Data(new_buffer); } // Holds handles to the custom materials diff --git a/examples/shader_advanced/manual_material.rs b/examples/shader_advanced/manual_material.rs index a4261944e161e..a047a241c218d 100644 --- a/examples/shader_advanced/manual_material.rs +++ b/examples/shader_advanced/manual_material.rs @@ -1,5 +1,6 @@ //! A simple 3D scene with light shining over a cube sitting on a plane. +use bevy::asset::EmptyRetainedAsset; use bevy::{ asset::{AsAssetId, AssetEventSystems}, core_pipeline::core_3d::Opaque3d, @@ -131,6 +132,7 @@ struct ImageMaterial { impl ErasedRenderAsset for ImageMaterial { type SourceAsset = ImageMaterial; + type RetainedAsset = EmptyRetainedAsset; type ErasedAsset = PreparedMaterial; type Param = ( SRes>, @@ -142,6 +144,10 @@ impl ErasedRenderAsset for ImageMaterial { SRes, ); + fn retain_main_world_asset(_source: &Self::SourceAsset) -> Self::RetainedAsset { + EmptyRetainedAsset + } + fn prepare_asset( source_asset: Self::SourceAsset, asset_id: AssetId, diff --git a/examples/tools/scene_viewer/morph_viewer_plugin.rs b/examples/tools/scene_viewer/morph_viewer_plugin.rs index e6e535e7c0a3d..326fc1eecdc5c 100644 --- a/examples/tools/scene_viewer/morph_viewer_plugin.rs +++ b/examples/tools/scene_viewer/morph_viewer_plugin.rs @@ -260,6 +260,7 @@ fn detect_morphs( let target_names = weights .first_mesh() .and_then(|h| meshes.get(h)) + .and_then(|m| m.as_option_ref()) .and_then(|m| m.morph_target_names()); let targets = Target::new(name, weights.weights(), target_names, entity); detected.extend(targets); diff --git a/examples/ui/text/font_atlas_debug.rs b/examples/ui/text/font_atlas_debug.rs index 4baea4b8a651d..38a9816baf842 100644 --- a/examples/ui/text/font_atlas_debug.rs +++ b/examples/ui/text/font_atlas_debug.rs @@ -3,6 +3,7 @@ use bevy::{color::palettes::basic::YELLOW, prelude::*, text::FontAtlasSet}; +use bevy::asset::RetainedAssets; use chacha20::ChaCha8Rng; use rand::{RngExt, SeedableRng}; @@ -40,7 +41,7 @@ fn atlas_render_system( mut commands: Commands, mut state: ResMut, font_atlas_set: Res, - images: Res>, + images: RetainedAssets, ) { if let Some(font_atlases) = font_atlas_set.values().next() { let x_offset = state.atlas_count as f32;