(&mut self, extension: &str) -> &mut Self {
- if let Some(asset_processor) = self.world.get_resource::() {
- asset_processor.set_default_processor::(extension);
- }
- self
- }
-
- fn register_asset_source(
- &mut self,
- id: impl Into>,
- source: AssetSourceBuilder,
- ) -> &mut Self {
- let id = id.into();
- if self.world.get_resource::().is_some() {
- error!("{} must be registered before `AssetPlugin` (typically added as part of `DefaultPlugins`)", id);
- }
-
- {
- let mut sources = self
- .world
- .get_resource_or_insert_with(AssetSourceBuilders::default);
- sources.insert(id, source);
- }
-
- self
- }
}
/// A system set that holds all "track asset" operations.
@@ -431,11 +433,12 @@ mod tests {
io::{
gated::{GateOpener, GatedReader},
memory::{Dir, MemoryAssetReader},
- AssetSource, AssetSourceId, Reader,
+ AssetReader, AssetReaderError, AssetSource, AssetSourceId, Reader,
},
loader::{AssetLoader, LoadContext},
- Asset, AssetApp, AssetEvent, AssetId, AssetPath, AssetPlugin, AssetServer, Assets,
- DependencyLoadState, LoadState, RecursiveDependencyLoadState,
+ Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
+ AssetPlugin, AssetServer, Assets, DependencyLoadState, LoadState,
+ RecursiveDependencyLoadState,
};
use bevy_app::{App, Update};
use bevy_core::TaskPoolPlugin;
@@ -446,20 +449,23 @@ mod tests {
};
use bevy_log::LogPlugin;
use bevy_reflect::TypePath;
- use bevy_utils::BoxedFuture;
+ use bevy_utils::{BoxedFuture, Duration, HashMap};
use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize};
- use std::path::Path;
+ use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+ };
use thiserror::Error;
#[derive(Asset, TypePath, Debug)]
pub struct CoolText {
- text: String,
- embedded: String,
+ pub text: String,
+ pub embedded: String,
#[dependency]
- dependencies: Vec>,
+ pub dependencies: Vec>,
#[dependency]
- sub_texts: Vec>,
+ pub sub_texts: Vec>,
}
#[derive(Asset, TypePath, Debug)]
@@ -476,10 +482,10 @@ mod tests {
}
#[derive(Default)]
- struct CoolTextLoader;
+ pub struct CoolTextLoader;
#[derive(Error, Debug)]
- enum CoolTextLoaderError {
+ pub enum CoolTextLoaderError {
#[error("Could not load dependency: {dependency}")]
CannotLoadDependency { dependency: AssetPath<'static> },
#[error("A RON error occurred during loading")]
@@ -537,6 +543,83 @@ mod tests {
}
}
+ /// A dummy [`CoolText`] asset reader that only succeeds after `failure_count` times it's read from for each asset.
+ #[derive(Default, Clone)]
+ pub struct UnstableMemoryAssetReader {
+ pub attempt_counters: Arc>>,
+ pub load_delay: Duration,
+ memory_reader: MemoryAssetReader,
+ failure_count: usize,
+ }
+
+ impl UnstableMemoryAssetReader {
+ pub fn new(root: Dir, failure_count: usize) -> Self {
+ Self {
+ load_delay: Duration::from_millis(10),
+ memory_reader: MemoryAssetReader { root },
+ attempt_counters: Default::default(),
+ failure_count,
+ }
+ }
+ }
+
+ impl AssetReader for UnstableMemoryAssetReader {
+ fn is_directory<'a>(
+ &'a self,
+ path: &'a Path,
+ ) -> BoxedFuture<'a, Result> {
+ self.memory_reader.is_directory(path)
+ }
+ fn read_directory<'a>(
+ &'a self,
+ path: &'a Path,
+ ) -> BoxedFuture<'a, Result, AssetReaderError>> {
+ self.memory_reader.read_directory(path)
+ }
+ fn read_meta<'a>(
+ &'a self,
+ path: &'a Path,
+ ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
+ self.memory_reader.read_meta(path)
+ }
+ fn read<'a>(
+ &'a self,
+ path: &'a Path,
+ ) -> BoxedFuture<
+ 'a,
+ Result>, bevy_asset::io::AssetReaderError>,
+ > {
+ let attempt_number = {
+ let key = PathBuf::from(path);
+ let mut attempt_counters = self.attempt_counters.lock().unwrap();
+ if let Some(existing) = attempt_counters.get_mut(&key) {
+ *existing += 1;
+ *existing
+ } else {
+ attempt_counters.insert(key, 1);
+ 1
+ }
+ };
+
+ if attempt_number <= self.failure_count {
+ let io_error = std::io::Error::new(
+ std::io::ErrorKind::ConnectionRefused,
+ format!(
+ "Simulated failure {attempt_number} of {}",
+ self.failure_count
+ ),
+ );
+ let wait = self.load_delay;
+ return Box::pin(async move {
+ std::thread::sleep(wait);
+ Err(AssetReaderError::Io(io_error.into()))
+ });
+ }
+
+ self.memory_reader.read(path)
+ }
+ }
+
fn test_app(dir: Dir) -> (App, GateOpener) {
let mut app = App::new();
let (gated_memory_reader, gate_opener) = GatedReader::new(MemoryAssetReader { root: dir });
@@ -552,7 +635,7 @@ mod tests {
(app, gate_opener)
}
- fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
+ pub fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
for _ in 0..LARGE_ITERATION_COUNT {
app.update();
if predicate(&mut app.world).is_some() {
@@ -581,6 +664,10 @@ mod tests {
#[test]
fn load_dependencies() {
+ // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
+ #[cfg(not(feature = "multi-threaded"))]
+ panic!("This test requires the \"multi-threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi-threaded");
+
let dir = Dir::default();
let a_path = "a.cool.ron";
@@ -872,13 +959,23 @@ mod tests {
id: id_results.d_id,
},
AssetEvent::Modified { id: a_id },
+ AssetEvent::Unused { id: a_id },
AssetEvent::Removed { id: a_id },
+ AssetEvent::Unused {
+ id: id_results.b_id,
+ },
AssetEvent::Removed {
id: id_results.b_id,
},
+ AssetEvent::Unused {
+ id: id_results.c_id,
+ },
AssetEvent::Removed {
id: id_results.c_id,
},
+ AssetEvent::Unused {
+ id: id_results.d_id,
+ },
AssetEvent::Removed {
id: id_results.d_id,
},
@@ -888,6 +985,10 @@ mod tests {
#[test]
fn failure_load_states() {
+ // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
+ #[cfg(not(feature = "multi-threaded"))]
+ panic!("This test requires the \"multi-threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi-threaded");
+
let dir = Dir::default();
let a_path = "a.cool.ron";
@@ -1007,6 +1108,10 @@ mod tests {
#[test]
fn manual_asset_management() {
+ // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
+ #[cfg(not(feature = "multi-threaded"))]
+ panic!("This test requires the \"multi-threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi-threaded");
+
let dir = Dir::default();
let dep_path = "dep.cool.ron";
@@ -1062,7 +1167,11 @@ mod tests {
// remove event is emitted
app.update();
let events = std::mem::take(&mut app.world.resource_mut::().0);
- let expected_events = vec![AssetEvent::Added { id }, AssetEvent::Removed { id }];
+ let expected_events = vec![
+ AssetEvent::Added { id },
+ AssetEvent::Unused { id },
+ AssetEvent::Removed { id },
+ ];
assert_eq!(events, expected_events);
let dep_handle = app.world.resource::().load(dep_path);
@@ -1108,6 +1217,10 @@ mod tests {
#[test]
fn load_folder() {
+ // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
+ #[cfg(not(feature = "multi-threaded"))]
+ panic!("This test requires the \"multi-threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi-threaded");
+
let dir = Dir::default();
let a_path = "text/a.cool.ron";
@@ -1196,6 +1309,133 @@ mod tests {
});
}
+ /// Tests that `AssetLoadFailedEvent` events are emitted and can be used to retry failed assets.
+ #[test]
+ fn load_error_events() {
+ #[derive(Resource, Default)]
+ struct ErrorTracker {
+ tick: u64,
+ failures: usize,
+ queued_retries: Vec<(AssetPath<'static>, AssetId, u64)>,
+ finished_asset: Option>,
+ }
+
+ fn asset_event_handler(
+ mut events: EventReader>,
+ mut tracker: ResMut,
+ ) {
+ for event in events.read() {
+ if let AssetEvent::LoadedWithDependencies { id } = event {
+ tracker.finished_asset = Some(*id);
+ }
+ }
+ }
+
+ fn asset_load_error_event_handler(
+ server: Res,
+ mut errors: EventReader>,
+ mut tracker: ResMut,
+ ) {
+ // In the real world, this would refer to time (not ticks)
+ tracker.tick += 1;
+
+ // Retry loading past failed items
+ let now = tracker.tick;
+ tracker
+ .queued_retries
+ .retain(|(path, old_id, retry_after)| {
+ if now > *retry_after {
+ let new_handle = server.load::(path);
+ assert_eq!(&new_handle.id(), old_id);
+ false
+ } else {
+ true
+ }
+ });
+
+ // Check what just failed
+ for error in errors.read() {
+ let (load_state, _, _) = server.get_load_states(error.id).unwrap();
+ assert_eq!(load_state, LoadState::Failed);
+ assert_eq!(*error.path.source(), AssetSourceId::Name("unstable".into()));
+ match &error.error {
+ AssetLoadError::AssetReaderError(read_error) => match read_error {
+ AssetReaderError::Io(_) => {
+ tracker.failures += 1;
+ if tracker.failures <= 2 {
+ // Retry in 10 ticks
+ tracker.queued_retries.push((
+ error.path.clone(),
+ error.id,
+ now + 10,
+ ));
+ } else {
+ panic!(
+ "Unexpected failure #{} (expected only 2)",
+ tracker.failures
+ );
+ }
+ }
+ _ => panic!("Unexpected error type {:?}", read_error),
+ },
+ _ => panic!("Unexpected error type {:?}", error.error),
+ }
+ }
+ }
+
+ let a_path = "text/a.cool.ron";
+ let a_ron = r#"
+(
+ text: "a",
+ dependencies: [],
+ embedded_dependencies: [],
+ sub_texts: [],
+)"#;
+
+ let dir = Dir::default();
+ dir.insert_asset_text(Path::new(a_path), a_ron);
+ let unstable_reader = UnstableMemoryAssetReader::new(dir, 2);
+
+ let mut app = App::new();
+ app.register_asset_source(
+ "unstable",
+ AssetSource::build().with_reader(move || Box::new(unstable_reader.clone())),
+ )
+ .add_plugins((
+ TaskPoolPlugin::default(),
+ LogPlugin::default(),
+ AssetPlugin::default(),
+ ))
+ .init_asset::()
+ .register_asset_loader(CoolTextLoader)
+ .init_resource::()
+ .add_systems(
+ Update,
+ (asset_event_handler, asset_load_error_event_handler).chain(),
+ );
+
+ let asset_server = app.world.resource::().clone();
+ let a_path = format!("unstable://{a_path}");
+ let a_handle: Handle = asset_server.load(a_path);
+ let a_id = a_handle.id();
+
+ app.world.spawn(a_handle);
+
+ run_app_until(&mut app, |world| {
+ let tracker = world.resource::();
+ match tracker.finished_asset {
+ Some(asset_id) => {
+ assert_eq!(asset_id, a_id);
+ let assets = world.resource::>();
+ let result = assets.get(asset_id).unwrap();
+ assert_eq!(result.text, "a");
+ Some(())
+ }
+ None => None,
+ }
+ });
+ }
+
#[test]
fn ignore_system_ambiguities_on_assets() {
let mut app = App::new();
diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs
index 26f7ebc6a0f8f..da3468201e7a0 100644
--- a/crates/bevy_asset/src/loader.rs
+++ b/crates/bevy_asset/src/loader.rs
@@ -97,6 +97,10 @@ where
})
}
+ fn extensions(&self) -> &[&str] {
+ ::extensions(self)
+ }
+
fn deserialize_meta(&self, meta: &[u8]) -> Result, DeserializeMetaError> {
let meta = AssetMeta::::deserialize(meta)?;
Ok(Box::new(meta))
@@ -109,10 +113,6 @@ where
}))
}
- fn extensions(&self) -> &[&str] {
- ::extensions(self)
- }
-
fn type_name(&self) -> &'static str {
std::any::type_name::()
}
@@ -121,13 +121,13 @@ where
TypeId::of::()
}
- fn asset_type_id(&self) -> TypeId {
- TypeId::of::()
- }
-
fn asset_type_name(&self) -> &'static str {
std::any::type_name::()
}
+
+ fn asset_type_id(&self) -> TypeId {
+ TypeId::of::()
+ }
}
pub(crate) struct LabeledAsset {
@@ -254,7 +254,7 @@ pub struct LoadDirectError {
}
/// An error that occurs while deserializing [`AssetMeta`].
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
pub enum DeserializeMetaError {
#[error("Failed to deserialize asset meta: {0:?}")]
DeserializeSettings(#[from] SpannedError),
diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs
index dbcd7d7feb57d..e8758999f7d7e 100644
--- a/crates/bevy_asset/src/meta.rs
+++ b/crates/bevy_asset/src/meta.rs
@@ -128,11 +128,6 @@ pub trait AssetMetaDyn: Downcast + Send + Sync {
}
impl AssetMetaDyn for AssetMeta {
- fn serialize(&self) -> Vec {
- ron::ser::to_string_pretty(&self, PrettyConfig::default())
- .expect("type is convertible to ron")
- .into_bytes()
- }
fn loader_settings(&self) -> Option<&dyn Settings> {
if let AssetAction::Load { settings, .. } = &self.asset {
Some(settings)
@@ -147,6 +142,11 @@ impl AssetMetaDyn for AssetMeta {
None
}
}
+ fn serialize(&self) -> Vec {
+ ron::ser::to_string_pretty(&self, PrettyConfig::default())
+ .expect("type is convertible to ron")
+ .into_bytes()
+ }
fn processed_info(&self) -> &Option {
&self.processed_info
}
diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs
index f321fc2154a8d..68991316913db 100644
--- a/crates/bevy_asset/src/path.rs
+++ b/crates/bevy_asset/src/path.rs
@@ -302,7 +302,7 @@ impl<'a> AssetPath<'a> {
/// Resolves a relative asset path via concatenation. The result will be an `AssetPath` which
/// is resolved relative to this "base" path.
///
- /// ```rust
+ /// ```
/// # use bevy_asset::AssetPath;
/// assert_eq!(AssetPath::parse("a/b").resolve("c"), Ok(AssetPath::parse("a/b/c")));
/// assert_eq!(AssetPath::parse("a/b").resolve("./c"), Ok(AssetPath::parse("a/b/c")));
@@ -352,7 +352,7 @@ impl<'a> AssetPath<'a> {
/// primary use case for this method is resolving relative paths embedded within asset files,
/// which are relative to the asset in which they are contained.
///
- /// ```rust
+ /// ```
/// # use bevy_asset::AssetPath;
/// assert_eq!(AssetPath::parse("a/b").resolve_embed("c"), Ok(AssetPath::parse("a/c")));
/// assert_eq!(AssetPath::parse("a/b").resolve_embed("./c"), Ok(AssetPath::parse("a/c")));
@@ -626,10 +626,6 @@ impl Reflect for AssetPath<'static> {
self
}
#[inline]
- fn clone_value(&self) -> Box {
- Box::new(self.clone())
- }
- #[inline]
fn apply(&mut self, value: &dyn Reflect) {
let value = Reflect::as_any(value);
if let Some(value) = value.downcast_ref::() {
@@ -655,6 +651,10 @@ impl Reflect for AssetPath<'static> {
fn reflect_owned(self: Box) -> ReflectOwned {
ReflectOwned::Value(self)
}
+ #[inline]
+ fn clone_value(&self) -> Box {
+ Box::new(self.clone())
+ }
fn reflect_hash(&self) -> Option {
let mut hasher = bevy_reflect::utility::reflect_hasher();
Hash::hash(&::core::any::Any::type_id(self), &mut hasher);
diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs
index 9033a3b08abfa..b5ef275103ac3 100644
--- a/crates/bevy_asset/src/processor/mod.rs
+++ b/crates/bevy_asset/src/processor/mod.rs
@@ -293,6 +293,13 @@ impl AssetProcessor {
AssetPath::from_path(&path).with_source(source.id())
);
}
+ AssetReaderError::HttpError(status) => {
+ error!(
+ "Path '{}' was removed, but the destination reader could not determine if it \
+ was a folder or a file due to receiving an unexpected HTTP Status {status}",
+ AssetPath::from_path(&path).with_source(source.id())
+ );
+ }
}
}
}
@@ -344,6 +351,13 @@ impl AssetProcessor {
AssetReaderError::NotFound(_err) => {
// The processed folder does not exist. No need to update anything
}
+ AssetReaderError::HttpError(status) => {
+ self.log_unrecoverable().await;
+ error!(
+ "Unrecoverable Error: Failed to read the processed assets at {path:?} in order to remove assets that no longer exist \
+ in the source directory. Restart the asset processor to fully reprocess assets. HTTP Status Code {status}"
+ );
+ }
AssetReaderError::Io(err) => {
self.log_unrecoverable().await;
error!(
@@ -752,7 +766,7 @@ impl AssetProcessor {
.await
.map_err(|e| ProcessError::AssetReaderError {
path: asset_path.clone(),
- err: AssetReaderError::Io(e),
+ err: AssetReaderError::Io(e.into()),
})?;
// PERF: in theory these hashes could be streamed if we want to avoid allocating the whole asset.
@@ -878,11 +892,11 @@ impl AssetProcessor {
state_is_valid = false;
};
let Ok(source) = self.get_source(path.source()) else {
- (unrecoverable_err)(&"AssetSource does not exist");
+ unrecoverable_err(&"AssetSource does not exist");
continue;
};
let Ok(processed_writer) = source.processed_writer() else {
- (unrecoverable_err)(&"AssetSource does not have a processed AssetWriter registered");
+ unrecoverable_err(&"AssetSource does not have a processed AssetWriter registered");
continue;
};
@@ -891,7 +905,7 @@ impl AssetProcessor {
AssetWriterError::Io(err) => {
// any error but NotFound means we could be in a bad state
if err.kind() != ErrorKind::NotFound {
- (unrecoverable_err)(&err);
+ unrecoverable_err(&err);
}
}
}
@@ -901,7 +915,7 @@ impl AssetProcessor {
AssetWriterError::Io(err) => {
// any error but NotFound means we could be in a bad state
if err.kind() != ErrorKind::NotFound {
- (unrecoverable_err)(&err);
+ unrecoverable_err(&err);
}
}
}
diff --git a/crates/bevy_asset/src/reflect.rs b/crates/bevy_asset/src/reflect.rs
index 352007cb22362..4ba8e3eed34bd 100644
--- a/crates/bevy_asset/src/reflect.rs
+++ b/crates/bevy_asset/src/reflect.rs
@@ -61,7 +61,7 @@ impl ReflectAsset {
/// Furthermore, this does *not* allow you to have look up two distinct handles,
/// you can only have at most one alive at the same time.
/// This means that this is *not allowed*:
- /// ```rust,no_run
+ /// ```no_run
/// # use bevy_asset::{ReflectAsset, UntypedHandle};
/// # use bevy_ecs::prelude::World;
/// # let reflect_asset: ReflectAsset = unimplemented!();
@@ -176,7 +176,7 @@ impl FromType for ReflectAsset {
/// the [`ReflectAsset`] type data on the corresponding `T` asset type:
///
///
-/// ```rust,no_run
+/// ```no_run
/// # use bevy_reflect::{TypeRegistry, prelude::*};
/// # use bevy_ecs::prelude::*;
/// use bevy_asset::{ReflectHandle, ReflectAsset};
diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs
index 41bbdf27bf3ec..2165c57b9cb13 100644
--- a/crates/bevy_asset/src/server/info.rs
+++ b/crates/bevy_asset/src/server/info.rs
@@ -1,8 +1,8 @@
use crate::{
meta::{AssetHash, MetaTransform},
- Asset, AssetHandleProvider, AssetPath, DependencyLoadState, ErasedLoadedAsset, Handle,
- InternalAssetEvent, LoadState, RecursiveDependencyLoadState, StrongHandle, UntypedAssetId,
- UntypedHandle,
+ Asset, AssetHandleProvider, AssetLoadError, AssetPath, DependencyLoadState, ErasedLoadedAsset,
+ Handle, InternalAssetEvent, LoadState, RecursiveDependencyLoadState, StrongHandle,
+ UntypedAssetId, UntypedHandle,
};
use bevy_ecs::world::World;
use bevy_log::warn;
@@ -74,6 +74,8 @@ pub(crate) struct AssetInfos {
pub(crate) living_labeled_assets: HashMap, HashSet>,
pub(crate) handle_providers: HashMap,
pub(crate) dependency_loaded_event_sender: HashMap,
+ pub(crate) dependency_failed_event_sender:
+ HashMap, AssetLoadError)>,
}
impl std::fmt::Debug for AssetInfos {
@@ -197,7 +199,8 @@ impl AssetInfos {
let mut should_load = false;
if loading_mode == HandleLoadingMode::Force
|| (loading_mode == HandleLoadingMode::Request
- && info.load_state == LoadState::NotLoaded)
+ && (info.load_state == LoadState::NotLoaded
+ || info.load_state == LoadState::Failed))
{
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
@@ -268,8 +271,12 @@ impl AssetInfos {
self.infos.get_mut(&id)
}
- pub(crate) fn get_path_handle(&self, path: AssetPath) -> Option {
- let id = *self.path_to_id.get(&path)?;
+ pub(crate) fn get_path_id(&self, path: &AssetPath) -> Option {
+ self.path_to_id.get(path).copied()
+ }
+
+ pub(crate) fn get_path_handle(&self, path: &AssetPath) -> Option {
+ let id = *self.path_to_id.get(path)?;
self.get_id_handle(id)
}
diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs
index 51d5cc2dc2604..51cb399d10182 100644
--- a/crates/bevy_asset/src/server/mod.rs
+++ b/crates/bevy_asset/src/server/mod.rs
@@ -12,8 +12,9 @@ use crate::{
MetaTransform, Settings,
},
path::AssetPath,
- Asset, AssetEvent, AssetHandleProvider, AssetId, AssetMetaCheck, Assets, DeserializeMetaError,
- ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, UntypedHandle,
+ Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
+ DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId,
+ UntypedAssetLoadFailedEvent, UntypedHandle,
};
use bevy_ecs::prelude::*;
use bevy_log::{error, info, warn};
@@ -119,7 +120,7 @@ impl AssetServer {
}
}
- /// Retrieves the [`AssetReader`] for the given `source`.
+ /// Retrieves the [`AssetSource`] for the given `source`.
pub fn get_source<'a>(
&'a self,
source: impl Into>,
@@ -173,11 +174,30 @@ impl AssetServer {
.resource_mut::>>()
.send(AssetEvent::LoadedWithDependencies { id: id.typed() });
}
- self.data
- .infos
- .write()
+ fn failed_sender(
+ world: &mut World,
+ id: UntypedAssetId,
+ path: AssetPath<'static>,
+ error: AssetLoadError,
+ ) {
+ world
+ .resource_mut::>>()
+ .send(AssetLoadFailedEvent {
+ id: id.typed(),
+ path,
+ error,
+ });
+ }
+
+ let mut infos = self.data.infos.write();
+
+ infos
.dependency_loaded_event_sender
.insert(TypeId::of::(), sender::);
+
+ infos
+ .dependency_failed_event_sender
+ .insert(TypeId::of::(), failed_sender::);
}
pub(crate) fn register_handle_provider(&self, handle_provider: AssetHandleProvider) {
@@ -366,6 +386,7 @@ impl AssetServer {
let server = self.clone();
IoTaskPool::get()
.spawn(async move {
+ let path_clone = path.clone();
match server.load_untyped_async(path).await {
Ok(handle) => server.send_asset_event(InternalAssetEvent::Loaded {
id,
@@ -377,7 +398,11 @@ impl AssetServer {
}),
Err(err) => {
error!("{err}");
- server.send_asset_event(InternalAssetEvent::Failed { id });
+ server.send_asset_event(InternalAssetEvent::Failed {
+ id,
+ path: path_clone,
+ error: err,
+ });
}
}
})
@@ -406,7 +431,11 @@ impl AssetServer {
// if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
// we cannot find the meta and loader
if let Some(handle) = &input_handle {
- self.send_asset_event(InternalAssetEvent::Failed { id: handle.id() });
+ self.send_asset_event(InternalAssetEvent::Failed {
+ id: handle.id(),
+ path: path.clone_owned(),
+ error: e.clone(),
+ });
}
e
})?;
@@ -487,9 +516,16 @@ impl AssetServer {
match loaded_asset.labeled_assets.get(&label) {
Some(labeled_asset) => labeled_asset.handle.clone(),
None => {
+ let mut all_labels: Vec = loaded_asset
+ .labeled_assets
+ .keys()
+ .map(|s| (**s).to_owned())
+ .collect();
+ all_labels.sort_unstable();
return Err(AssetLoadError::MissingLabel {
base_path,
label: label.to_string(),
+ all_labels,
});
}
}
@@ -504,6 +540,8 @@ impl AssetServer {
Err(err) => {
self.send_asset_event(InternalAssetEvent::Failed {
id: base_handle.id(),
+ error: err.clone(),
+ path: path.into_owned(),
});
Err(err)
}
@@ -527,7 +565,6 @@ impl AssetServer {
IoTaskPool::get()
.spawn(async move {
if server.data.infos.read().should_reload(&path) {
- info!("Reloading {path} because it has changed");
if let Err(err) = server.load_internal(None, path, true, None).await {
error!("{}", err);
}
@@ -683,7 +720,7 @@ impl AssetServer {
}),
Err(err) => {
error!("Failed to load folder. {err}");
- server.send_asset_event(InternalAssetEvent::Failed { id });
+ server.send_asset_event(InternalAssetEvent::Failed { id, error: err, path });
},
}
})
@@ -768,12 +805,20 @@ impl AssetServer {
self.data.infos.read().contains_key(id.into())
}
+ /// Returns an active untyped asset id for the given path, if the asset at the given path has already started loading,
+ /// or is still "alive".
+ pub fn get_path_id<'a>(&self, path: impl Into>) -> Option {
+ let infos = self.data.infos.read();
+ let path = path.into();
+ infos.get_path_id(&path)
+ }
+
/// Returns an active untyped handle for the given path, if the asset at the given path has already started loading,
/// or is still "alive".
pub fn get_handle_untyped<'a>(&self, path: impl Into>) -> Option {
let infos = self.data.infos.read();
let path = path.into();
- infos.get_path_handle(path)
+ infos.get_path_handle(&path)
}
/// Returns the path for the given `id`, if it has one.
@@ -867,7 +912,7 @@ impl AssetServer {
ron::de::from_bytes(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
- error: Box::new(DeserializeMetaError::DeserializeMinimal(e)),
+ error: DeserializeMetaError::DeserializeMinimal(e).into(),
}
})?;
let loader_name = match minimal.asset {
@@ -887,7 +932,7 @@ impl AssetServer {
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
- error: Box::new(e),
+ error: e.into(),
}
})?;
@@ -924,7 +969,7 @@ impl AssetServer {
AssetLoadError::AssetLoaderError {
path: asset_path.clone_owned(),
loader_name: loader.type_name(),
- error: e,
+ error: e.into(),
}
})
}
@@ -934,6 +979,7 @@ impl AssetServer {
pub fn handle_internal_asset_events(world: &mut World) {
world.resource_scope(|world, server: Mut| {
let mut infos = server.data.infos.write();
+ let mut untyped_failures = vec![];
for event in server.data.asset_event_receiver.try_iter() {
match event {
InternalAssetEvent::Loaded { id, loaded_asset } => {
@@ -951,10 +997,30 @@ pub fn handle_internal_asset_events(world: &mut World) {
.expect("Asset event sender should exist");
sender(world, id);
}
- InternalAssetEvent::Failed { id } => infos.process_asset_fail(id),
+ InternalAssetEvent::Failed { id, path, error } => {
+ infos.process_asset_fail(id);
+
+ // Send untyped failure event
+ untyped_failures.push(UntypedAssetLoadFailedEvent {
+ id,
+ path: path.clone(),
+ error: error.clone(),
+ });
+
+ // Send typed failure event
+ let sender = infos
+ .dependency_failed_event_sender
+ .get(&id.type_id())
+ .expect("Asset failed event sender should exist");
+ sender(world, id, path, error);
+ }
}
}
+ if !untyped_failures.is_empty() {
+ world.send_event_batch(untyped_failures);
+ }
+
fn queue_ancestors(
asset_path: &AssetPath,
infos: &AssetInfos,
@@ -974,7 +1040,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
current_folder = parent.to_path_buf();
let parent_asset_path =
AssetPath::from(current_folder.clone()).with_source(source.clone());
- if let Some(folder_handle) = infos.get_path_handle(parent_asset_path.clone()) {
+ if let Some(folder_handle) = infos.get_path_handle(&parent_asset_path) {
info!("Reloading folder {parent_asset_path} because the content has changed");
server.load_folder_internal(folder_handle.id(), parent_asset_path);
}
@@ -1025,6 +1091,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
}
for path in paths_to_reload {
+ info!("Reloading {path} because it has changed");
server.reload(path);
}
});
@@ -1059,6 +1126,8 @@ pub(crate) enum InternalAssetEvent {
},
Failed {
id: UntypedAssetId,
+ path: AssetPath<'static>,
+ error: AssetLoadError,
},
}
@@ -1102,7 +1171,7 @@ pub enum RecursiveDependencyLoadState {
}
/// An error that occurs during an [`Asset`] load.
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
pub enum AssetLoadError {
#[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
RequestedHandleTypeMismatch {
@@ -1136,24 +1205,29 @@ pub enum AssetLoadError {
AssetLoaderError {
path: AssetPath<'static>,
loader_name: &'static str,
- error: Box,
+ error: Arc,
},
- #[error("The file at '{base_path}' does not contain the labeled asset '{label}'.")]
+ #[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
+ base_path,
+ label,
+ all_labels.len(),
+ all_labels.iter().map(|l| format!("'{}'", l)).collect::>().join(", "))]
MissingLabel {
base_path: AssetPath<'static>,
label: String,
+ all_labels: Vec,
},
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]
pub struct MissingAssetLoaderForExtensionError {
extensions: Vec,
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given [`std::any::type_name`].
-#[derive(Error, Debug)]
+#[derive(Error, Debug, Clone)]
#[error("no `AssetLoader` found with the name '{type_name}'")]
pub struct MissingAssetLoaderForTypeNameError {
type_name: String,
diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs
index 56a53ee02b4b5..709d595d4e758 100644
--- a/crates/bevy_audio/src/audio.rs
+++ b/crates/bevy_audio/src/audio.rs
@@ -1,51 +1,25 @@
use crate::{AudioSource, Decodable};
use bevy_asset::{Asset, Handle};
-use bevy_derive::{Deref, DerefMut};
+use bevy_derive::Deref;
use bevy_ecs::prelude::*;
use bevy_math::Vec3;
use bevy_reflect::prelude::*;
-/// Defines the volume to play an audio source at.
-#[derive(Clone, Copy, Debug, Reflect)]
-pub enum Volume {
- /// A volume level relative to the global volume.
- Relative(VolumeLevel),
- /// A volume level that ignores the global volume.
- Absolute(VolumeLevel),
-}
-
-impl Default for Volume {
- fn default() -> Self {
- Self::Relative(VolumeLevel::default())
- }
-}
-
-impl Volume {
- /// Create a new volume level relative to the global volume.
- pub fn new_relative(volume: f32) -> Self {
- Self::Relative(VolumeLevel::new(volume))
- }
- /// Create a new volume level that ignores the global volume.
- pub fn new_absolute(volume: f32) -> Self {
- Self::Absolute(VolumeLevel::new(volume))
- }
-}
-
/// A volume level equivalent to a non-negative float.
-#[derive(Clone, Copy, Deref, DerefMut, Debug, Reflect)]
-pub struct VolumeLevel(pub(crate) f32);
+#[derive(Clone, Copy, Deref, Debug, Reflect)]
+pub struct Volume(pub(crate) f32);
-impl Default for VolumeLevel {
+impl Default for Volume {
fn default() -> Self {
Self(1.0)
}
}
-impl VolumeLevel {
+impl Volume {
/// Create a new volume level.
pub fn new(volume: f32) -> Self {
debug_assert!(volume >= 0.0);
- Self(volume)
+ Self(f32::max(volume, 0.))
}
/// Get the value of the volume level.
pub fn get(&self) -> f32 {
@@ -53,7 +27,7 @@ impl VolumeLevel {
}
/// Zero (silent) volume level
- pub const ZERO: Self = VolumeLevel(0.0);
+ pub const ZERO: Self = Volume(0.0);
}
/// The way Bevy manages the sound playback.
@@ -106,7 +80,7 @@ impl PlaybackSettings {
/// Will play the associated audio source once.
pub const ONCE: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Once,
- volume: Volume::Relative(VolumeLevel(1.0)),
+ volume: Volume(1.0),
speed: 1.0,
paused: false,
spatial: false,
@@ -115,28 +89,19 @@ impl PlaybackSettings {
/// Will play the associated audio source in a loop.
pub const LOOP: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Loop,
- volume: Volume::Relative(VolumeLevel(1.0)),
- speed: 1.0,
- paused: false,
- spatial: false,
+ ..PlaybackSettings::ONCE
};
/// Will play the associated audio source once and despawn the entity afterwards.
pub const DESPAWN: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Despawn,
- volume: Volume::Relative(VolumeLevel(1.0)),
- speed: 1.0,
- paused: false,
- spatial: false,
+ ..PlaybackSettings::ONCE
};
/// Will play the associated audio source once and remove the audio components afterwards.
pub const REMOVE: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Remove,
- volume: Volume::Relative(VolumeLevel(1.0)),
- speed: 1.0,
- paused: false,
- spatial: false,
+ ..PlaybackSettings::ONCE
};
/// Helper to start in a paused state.
@@ -196,21 +161,21 @@ impl SpatialListener {
}
}
-/// Use this [`Resource`] to control the global volume of all audio with a [`Volume::Relative`] volume.
+/// Use this [`Resource`] to control the global volume of all audio.
///
/// Note: changing this value will not affect already playing audio.
#[derive(Resource, Default, Clone, Copy, Reflect)]
#[reflect(Resource)]
pub struct GlobalVolume {
/// The global volume of all audio.
- pub volume: VolumeLevel,
+ pub volume: Volume,
}
impl GlobalVolume {
/// Create a new [`GlobalVolume`] with the given volume.
pub fn new(volume: f32) -> Self {
Self {
- volume: VolumeLevel::new(volume),
+ volume: Volume::new(volume),
}
}
}
diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs
index ecbf805bc1c34..2acf0c6ebba18 100644
--- a/crates/bevy_audio/src/audio_output.rs
+++ b/crates/bevy_audio/src/audio_output.rs
@@ -1,6 +1,6 @@
use crate::{
AudioSourceBundle, Decodable, GlobalVolume, PlaybackMode, PlaybackSettings, SpatialAudioSink,
- SpatialListener, SpatialScale, Volume,
+ SpatialListener, SpatialScale,
};
use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::{prelude::*, system::SystemParam};
@@ -77,8 +77,8 @@ impl<'w, 's> EarPositions<'w, 's> {
.unwrap_or_else(|| {
let settings = SpatialListener::default();
(
- (settings.left_ear_offset * self.scale.0),
- (settings.right_ear_offset * self.scale.0),
+ settings.left_ear_offset * self.scale.0,
+ settings.right_ear_offset * self.scale.0,
)
});
@@ -159,10 +159,7 @@ pub(crate) fn play_queued_audio_system(
};
sink.set_speed(settings.speed);
- match settings.volume {
- Volume::Relative(vol) => sink.set_volume(vol.0 * global_volume.volume.0),
- Volume::Absolute(vol) => sink.set_volume(vol.0),
- }
+ sink.set_volume(settings.volume.0 * global_volume.volume.0);
if settings.paused {
sink.pause();
@@ -202,10 +199,7 @@ pub(crate) fn play_queued_audio_system(
};
sink.set_speed(settings.speed);
- match settings.volume {
- Volume::Relative(vol) => sink.set_volume(vol.0 * global_volume.volume.0),
- Volume::Absolute(vol) => sink.set_volume(vol.0),
- }
+ sink.set_volume(settings.volume.0 * global_volume.volume.0);
if settings.paused {
sink.pause();
@@ -291,7 +285,7 @@ pub(crate) fn audio_output_available(audio_output: Res) -> bool {
/// Updates spatial audio sinks when emitter positions change.
pub(crate) fn update_emitter_positions(
- mut emitters: Query<(&mut GlobalTransform, &SpatialAudioSink), Changed>,
+ mut emitters: Query<(&GlobalTransform, &SpatialAudioSink), Changed>,
spatial_scale: Res,
) {
for (transform, sink) in emitters.iter_mut() {
diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs
index 30f9c8cf8da9e..8b0c7090eac14 100644
--- a/crates/bevy_audio/src/audio_source.rs
+++ b/crates/bevy_audio/src/audio_source.rs
@@ -97,8 +97,8 @@ pub trait Decodable: Send + Sync + 'static {
}
impl Decodable for AudioSource {
- type Decoder = rodio::Decoder>;
type DecoderItem = > as Iterator>::Item;
+ type Decoder = rodio::Decoder>;
fn decoder(&self) -> Self::Decoder {
rodio::Decoder::new(Cursor::new(self.clone())).unwrap()
diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs
index 2bc1c0ce5a469..1d63ac7d461f8 100644
--- a/crates/bevy_audio/src/lib.rs
+++ b/crates/bevy_audio/src/lib.rs
@@ -63,7 +63,7 @@ struct AudioPlaySet;
/// Insert an [`AudioBundle`] onto your entities to play audio.
#[derive(Default)]
pub struct AudioPlugin {
- /// The global volume for all audio entities with a [`Volume::Relative`] volume.
+ /// The global volume for all audio entities.
pub global_volume: GlobalVolume,
/// The scale factor applied to the positions of audio sources and listeners for
/// spatial audio.
@@ -72,12 +72,11 @@ pub struct AudioPlugin {
impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) {
- app.register_type::()
+ app.register_type::()
.register_type::()
.register_type::()
.register_type::()
.register_type::()
- .register_type::()
.register_type::()
.insert_resource(self.global_volume)
.insert_resource(self.spatial_scale)
diff --git a/crates/bevy_core/src/name.rs b/crates/bevy_core/src/name.rs
index 7cae9c796dabd..dfa4a11c118bd 100644
--- a/crates/bevy_core/src/name.rs
+++ b/crates/bevy_core/src/name.rs
@@ -10,6 +10,9 @@ use std::{
ops::Deref,
};
+#[cfg(feature = "serialize")]
+use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
+
/// Component used to identify an entity. Stores a hash for faster comparisons.
///
/// The hash is eagerly re-computed upon each update to the name.
@@ -19,8 +22,9 @@ use std::{
/// used instead as the default unique identifier.
#[derive(Reflect, Component, Clone)]
#[reflect(Component, Default, Debug)]
+#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
pub struct Name {
- hash: u64, // TODO: Shouldn't be serialized
+ hash: u64, // Won't be serialized (see: `bevy_core::serde` module)
name: Cow<'static, str>,
}
@@ -88,7 +92,7 @@ impl std::fmt::Debug for Name {
/// Convenient query for giving a human friendly name to an entity.
///
-/// ```rust
+/// ```
/// # use bevy_core::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)] pub struct Score(f32);
diff --git a/crates/bevy_core_pipeline/src/blit/mod.rs b/crates/bevy_core_pipeline/src/blit/mod.rs
index 9a9777a43f212..91258c034de59 100644
--- a/crates/bevy_core_pipeline/src/blit/mod.rs
+++ b/crates/bevy_core_pipeline/src/blit/mod.rs
@@ -20,6 +20,10 @@ pub struct BlitPlugin;
impl Plugin for BlitPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, BLIT_SHADER_HANDLE, "blit.wgsl", Shader::from_wgsl);
+
+ if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
+ render_app.allow_ambiguous_resource::>();
+ }
}
fn finish(&self, app: &mut App) {
diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs
index 09f668495d26a..85f3e93ff1be1 100644
--- a/crates/bevy_core_pipeline/src/bloom/mod.rs
+++ b/crates/bevy_core_pipeline/src/bloom/mod.rs
@@ -265,12 +265,7 @@ impl ViewNode for BloomNode {
let mut upsampling_final_pass =
render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("bloom_upsampling_final_pass"),
- color_attachments: &[Some(view_target.get_unsampled_color_attachment(
- Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- },
- ))],
+ color_attachments: &[Some(view_target.get_unsampled_color_attachment())],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs
index 8d2d3bdd4d275..69c789933c686 100644
--- a/crates/bevy_core_pipeline/src/bloom/settings.rs
+++ b/crates/bevy_core_pipeline/src/bloom/settings.rs
@@ -175,19 +175,19 @@ pub struct BloomPrefilterSettings {
pub threshold_softness: f32,
}
-#[derive(Clone, Reflect, PartialEq, Eq, Hash, Copy)]
+#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)]
pub enum BloomCompositeMode {
EnergyConserving,
Additive,
}
impl ExtractComponent for BloomSettings {
- type Data = (&'static Self, &'static Camera);
+ type QueryData = (&'static Self, &'static Camera);
- type Filter = ();
+ type QueryFilter = ();
type Out = (Self, BloomUniforms);
- fn extract_component((settings, camera): QueryItem<'_, Self::Data>) -> Option {
+ fn extract_component((settings, camera): QueryItem<'_, Self::QueryData>) -> Option {
match (
camera.physical_viewport_rect(),
camera.physical_viewport_size(),
diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs
index af51d695eafa2..6e512b873572a 100644
--- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs
+++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs
@@ -77,11 +77,11 @@ pub struct CASUniform {
}
impl ExtractComponent for ContrastAdaptiveSharpeningSettings {
- type Data = &'static Self;
- type Filter = With;
+ type QueryData = &'static Self;
+ type QueryFilter = With;
type Out = (DenoiseCAS, CASUniform);
- fn extract_component(item: QueryItem) -> Option {
+ fn extract_component(item: QueryItem) -> Option {
if !item.enabled || item.sharpening_strength == 0.0 {
return None;
}
diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
index fcd794029b04f..d76c0c315c0b6 100644
--- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
+++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
@@ -1,11 +1,11 @@
-use crate::{
- clear_color::ClearColorConfig,
- tonemapping::{DebandDither, Tonemapping},
-};
+use crate::tonemapping::{DebandDither, Tonemapping};
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use bevy_render::{
- camera::{Camera, CameraProjection, CameraRenderGraph, OrthographicProjection},
+ camera::{
+ Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
+ OrthographicProjection,
+ },
extract_component::ExtractComponent,
primitives::Frustum,
view::VisibleEntities,
@@ -15,9 +15,7 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
#[derive(Component, Default, Reflect, Clone, ExtractComponent)]
#[extract_component_filter(With)]
#[reflect(Component)]
-pub struct Camera2d {
- pub clear_color: ClearColorConfig,
-}
+pub struct Camera2d;
#[derive(Bundle)]
pub struct Camera2dBundle {
@@ -31,6 +29,7 @@ pub struct Camera2dBundle {
pub camera_2d: Camera2d,
pub tonemapping: Tonemapping,
pub deband_dither: DebandDither,
+ pub main_texture_usages: CameraMainTextureUsages,
}
impl Default for Camera2dBundle {
@@ -57,9 +56,10 @@ impl Default for Camera2dBundle {
transform,
global_transform: Default::default(),
camera: Camera::default(),
- camera_2d: Camera2d::default(),
+ camera_2d: Camera2d,
tonemapping: Tonemapping::None,
deband_dither: DebandDither::Disabled,
+ main_texture_usages: Default::default(),
}
}
}
@@ -95,9 +95,10 @@ impl Camera2dBundle {
transform,
global_transform: Default::default(),
camera: Camera::default(),
- camera_2d: Camera2d::default(),
+ camera_2d: Camera2d,
tonemapping: Tonemapping::None,
deband_dither: DebandDither::Disabled,
+ main_texture_usages: Default::default(),
}
}
}
diff --git a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs
index c3a19e93df26b..656db89100819 100644
--- a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs
+++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs
@@ -1,13 +1,10 @@
-use crate::{
- clear_color::{ClearColor, ClearColorConfig},
- core_2d::{camera_2d::Camera2d, Transparent2d},
-};
+use crate::core_2d::Transparent2d;
use bevy_ecs::prelude::*;
use bevy_render::{
camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
- render_resource::{LoadOp, Operations, RenderPassDescriptor, StoreOp},
+ render_resource::RenderPassDescriptor,
renderer::RenderContext,
view::{ExtractedView, ViewTarget},
};
@@ -20,7 +17,6 @@ pub struct MainPass2dNode {
&'static ExtractedCamera,
&'static RenderPhase,
&'static ViewTarget,
- &'static Camera2d,
),
With,
>,
@@ -46,28 +42,19 @@ impl Node for MainPass2dNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
- let Ok((camera, transparent_phase, target, camera_2d)) =
- self.query.get_manual(world, view_entity)
+ let Ok((camera, transparent_phase, target)) = self.query.get_manual(world, view_entity)
else {
// no target
return Ok(());
};
+
{
#[cfg(feature = "trace")]
let _main_pass_2d = info_span!("main_pass_2d").entered();
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_pass_2d"),
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load: match camera_2d.clear_color {
- ClearColorConfig::Default => {
- LoadOp::Clear(world.resource::().0.into())
- }
- ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
- ClearColorConfig::None => LoadOp::Load,
- },
- store: StoreOp::Store,
- }))],
+ color_attachments: &[Some(target.get_color_attachment())],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
@@ -88,10 +75,7 @@ impl Node for MainPass2dNode {
let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered();
let pass_descriptor = RenderPassDescriptor {
label: Some("reset_viewport_pass_2d"),
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }))],
+ color_attachments: &[Some(target.get_color_attachment())],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs
index 33579994c9aca..0b8e21da0eaee 100644
--- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs
+++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs
@@ -1,11 +1,8 @@
-use crate::{
- clear_color::ClearColorConfig,
- tonemapping::{DebandDither, Tonemapping},
-};
+use crate::tonemapping::{DebandDither, Tonemapping};
use bevy_ecs::prelude::*;
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{
- camera::{Camera, CameraRenderGraph, Projection},
+ camera::{Camera, CameraMainTextureUsages, CameraRenderGraph, Projection},
extract_component::ExtractComponent,
primitives::Frustum,
render_resource::{LoadOp, TextureUsages},
@@ -19,8 +16,6 @@ use serde::{Deserialize, Serialize};
#[extract_component_filter(With)]
#[reflect(Component)]
pub struct Camera3d {
- /// The clear color operation to perform for the main 3d pass.
- pub clear_color: ClearColorConfig,
/// The depth clear operation to perform for the main 3d pass.
pub depth_load_op: Camera3dDepthLoadOp,
/// The texture usages for the depth texture created for the main 3d pass.
@@ -37,7 +32,7 @@ pub struct Camera3d {
/// regardless of this setting.
/// - Setting this to `0` disables the screen-space refraction effect entirely, and falls
/// back to refracting only the environment map light's texture.
- /// - If set to more than `0`, any opaque [`clear_color`](Camera3d::clear_color) will obscure the environment
+ /// - If set to more than `0`, any opaque [`clear_color`](Camera::clear_color) will obscure the environment
/// map light's texture, preventing it from being visible “through” transmissive materials. If you'd like
/// to still have the environment map show up in your refractions, you can set the clear color's alpha to `0.0`.
/// Keep in mind that depending on the platform and your window settings, this may cause the window to become
@@ -55,7 +50,6 @@ pub struct Camera3d {
impl Default for Camera3d {
fn default() -> Self {
Self {
- clear_color: ClearColorConfig::Default,
depth_load_op: Default::default(),
depth_texture_usages: TextureUsages::RENDER_ATTACHMENT.into(),
screen_space_specular_transmission_steps: 1,
@@ -64,7 +58,8 @@ impl Default for Camera3d {
}
}
-#[derive(Clone, Copy, Reflect)]
+#[derive(Clone, Copy, Reflect, Serialize, Deserialize)]
+#[reflect(Serialize, Deserialize)]
pub struct Camera3dDepthTextureUsage(u32);
impl From for Camera3dDepthTextureUsage {
@@ -148,6 +143,7 @@ pub struct Camera3dBundle {
pub tonemapping: Tonemapping,
pub dither: DebandDither,
pub color_grading: ColorGrading,
+ pub main_texture_usages: CameraMainTextureUsages,
}
// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference
@@ -165,6 +161,7 @@ impl Default for Camera3dBundle {
tonemapping: Default::default(),
dither: DebandDither::Enabled,
color_grading: ColorGrading::default(),
+ main_texture_usages: Default::default(),
}
}
}
diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs
index 2de30a934af64..804f6afcf8e18 100644
--- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs
+++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs
@@ -1,25 +1,20 @@
use crate::{
- clear_color::{ClearColor, ClearColorConfig},
- core_3d::{Camera3d, Opaque3d},
- prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
+ core_3d::Opaque3d,
skybox::{SkyboxBindGroup, SkyboxPipelineId},
};
-use bevy_ecs::{prelude::*, query::QueryItem};
+use bevy_ecs::{prelude::World, query::QueryItem};
use bevy_render::{
camera::ExtractedCamera,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase,
- render_resource::{
- LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor,
- StoreOp,
- },
+ render_resource::{PipelineCache, RenderPassDescriptor, StoreOp},
renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget, ViewUniformOffset},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
-use super::{AlphaMask3d, Camera3dDepthLoadOp};
+use super::AlphaMask3d;
/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`].
#[derive(Default)]
@@ -29,13 +24,8 @@ impl ViewNode for MainOpaquePass3dNode {
&'static ExtractedCamera,
&'static RenderPhase,
&'static RenderPhase,
- &'static Camera3d,
&'static ViewTarget,
&'static ViewDepthTexture,
- Option<&'static DepthPrepass>,
- Option<&'static NormalPrepass>,
- Option<&'static MotionVectorPrepass>,
- Option<&'static DeferredPrepass>,
Option<&'static SkyboxPipelineId>,
Option<&'static SkyboxBindGroup>,
&'static ViewUniformOffset,
@@ -49,30 +39,14 @@ impl ViewNode for MainOpaquePass3dNode {
camera,
opaque_phase,
alpha_mask_phase,
- camera_3d,
target,
depth,
- depth_prepass,
- normal_prepass,
- motion_vector_prepass,
- deferred_prepass,
skybox_pipeline,
skybox_bind_group,
view_uniform_offset,
): QueryItem,
world: &World,
) -> Result<(), NodeRunError> {
- let load = if deferred_prepass.is_none() {
- match camera_3d.clear_color {
- ClearColorConfig::Default => LoadOp::Clear(world.resource::().0.into()),
- ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()),
- ClearColorConfig::None => LoadOp::Load,
- }
- } else {
- // If the deferred lighting pass has run, don't clear again in this pass.
- LoadOp::Load
- };
-
// Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")]
@@ -81,33 +55,8 @@ impl ViewNode for MainOpaquePass3dNode {
// Setup render pass
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_opaque_pass_3d"),
- // NOTE: The opaque pass loads the color
- // buffer as well as writing to it.
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load,
- store: StoreOp::Store,
- }))],
- depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
- view: &depth.view,
- // NOTE: The opaque main pass loads the depth buffer and possibly overwrites it
- depth_ops: Some(Operations {
- load: if depth_prepass.is_some()
- || normal_prepass.is_some()
- || motion_vector_prepass.is_some()
- || deferred_prepass.is_some()
- {
- // if any prepass runs, it will generate a depth buffer so we should use it,
- // even if only the normal_prepass is used.
- Camera3dDepthLoadOp::Load
- } else {
- // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections.
- camera_3d.depth_load_op.clone()
- }
- .into(),
- store: StoreOp::Store,
- }),
- stencil_ops: None,
- }),
+ color_attachments: &[Some(target.get_color_attachment())],
+ depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
timestamp_writes: None,
occlusion_query_set: None,
});
@@ -127,13 +76,17 @@ impl ViewNode for MainOpaquePass3dNode {
}
// Draw the skybox using a fullscreen triangle
- if let (Some(skybox_pipeline), Some(skybox_bind_group)) =
+ if let (Some(skybox_pipeline), Some(SkyboxBindGroup(skybox_bind_group))) =
(skybox_pipeline, skybox_bind_group)
{
let pipeline_cache = world.resource::();
if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) {
render_pass.set_render_pipeline(pipeline);
- render_pass.set_bind_group(0, &skybox_bind_group.0, &[view_uniform_offset.offset]);
+ render_pass.set_bind_group(
+ 0,
+ &skybox_bind_group.0,
+ &[view_uniform_offset.offset, skybox_bind_group.1],
+ );
render_pass.draw(0..3, 0..1);
}
}
diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs
index fbd290a4fea57..73a679ba047eb 100644
--- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs
+++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs
@@ -5,10 +5,7 @@ use bevy_render::{
camera::ExtractedCamera,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase,
- render_resource::{
- Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
- StoreOp,
- },
+ render_resource::{Extent3d, RenderPassDescriptor, StoreOp},
renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget},
};
@@ -45,20 +42,8 @@ impl ViewNode for MainTransmissivePass3dNode {
let render_pass_descriptor = RenderPassDescriptor {
label: Some("main_transmissive_pass_3d"),
- // NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate.
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }))],
- depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
- view: &depth.view,
- // NOTE: The transmissive main pass loads the depth buffer and possibly overwrites it
- depth_ops: Some(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }),
- stencil_ops: None,
- }),
+ color_attachments: &[Some(target.get_color_attachment())],
+ depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
timestamp_writes: None,
occlusion_query_set: None,
};
diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs
index 22bc3f5e91abc..1ffb059007c8f 100644
--- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs
+++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs
@@ -4,9 +4,7 @@ use bevy_render::{
camera::ExtractedCamera,
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase,
- render_resource::{
- LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor, StoreOp,
- },
+ render_resource::{RenderPassDescriptor, StoreOp},
renderer::RenderContext,
view::{ViewDepthTexture, ViewTarget},
};
@@ -41,25 +39,14 @@ impl ViewNode for MainTransparentPass3dNode {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_transparent_pass_3d"),
- // NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }))],
- depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
- view: &depth.view,
- // NOTE: For the transparent pass we load the depth buffer. There should be no
- // need to write to it, but store is set to `true` as a workaround for issue #3776,
- // https://github.com/bevyengine/bevy/issues/3776
- // so that wgpu does not clear the depth buffer.
- // As the opaque and alpha mask passes run first, opaque meshes can occlude
- // transparent ones.
- depth_ops: Some(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }),
- stencil_ops: None,
- }),
+ color_attachments: &[Some(target.get_color_attachment())],
+ // NOTE: For the transparent pass we load the depth buffer. There should be no
+ // need to write to it, but store is set to `true` as a workaround for issue #3776,
+ // https://github.com/bevyengine/bevy/issues/3776
+ // so that wgpu does not clear the depth buffer.
+ // As the opaque and alpha mask passes run first, opaque meshes can occlude
+ // transparent ones.
+ depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
timestamp_writes: None,
occlusion_query_set: None,
});
@@ -79,10 +66,7 @@ impl ViewNode for MainTransparentPass3dNode {
let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered();
let pass_descriptor = RenderPassDescriptor {
label: Some("reset_viewport_pass_3d"),
- color_attachments: &[Some(target.get_color_attachment(Operations {
- load: LoadOp::Load,
- store: StoreOp::Store,
- }))],
+ color_attachments: &[Some(target.get_color_attachment())],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs
index 1a38e5eb932c5..5d6e72e88953e 100644
--- a/crates/bevy_core_pipeline/src/core_3d/mod.rs
+++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs
@@ -42,6 +42,7 @@ use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::prelude::*;
use bevy_render::{
camera::{Camera, ExtractedCamera},
+ color::Color,
extract_component::ExtractComponentPlugin,
prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
@@ -54,7 +55,7 @@ use bevy_render::{
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
},
renderer::RenderDevice,
- texture::{BevyDefault, TextureCache},
+ texture::{BevyDefault, ColorAttachment, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
@@ -83,6 +84,8 @@ impl Plugin for Core3dPlugin {
fn build(&self, app: &mut App) {
app.register_type::()
.register_type::()
+ .register_type::()
+ .register_type::()
.add_plugins((SkyboxPlugin, ExtractComponentPlugin::::default()))
.add_systems(PostUpdate, check_msaa);
@@ -524,7 +527,7 @@ pub fn prepare_core_3d_depth_textures(
}
let mut textures = HashMap::default();
- for (entity, camera, _, _) in &views_3d {
+ for (entity, camera, _, camera_3d) in &views_3d {
let Some(physical_target_size) = camera.physical_target_size else {
continue;
};
@@ -558,10 +561,13 @@ pub fn prepare_core_3d_depth_textures(
})
.clone();
- commands.entity(entity).insert(ViewDepthTexture {
- texture: cached_texture.texture,
- view: cached_texture.default_view,
- });
+ commands.entity(entity).insert(ViewDepthTexture::new(
+ cached_texture,
+ match camera_3d.depth_load_op {
+ Camera3dDepthLoadOp::Clear(v) => Some(v),
+ Camera3dDepthLoadOp::Load => None,
+ },
+ ));
}
}
@@ -822,10 +828,14 @@ pub fn prepare_prepass_textures(
});
commands.entity(entity).insert(ViewPrepassTextures {
- depth: cached_depth_texture,
- normal: cached_normals_texture,
- motion_vectors: cached_motion_vectors_texture,
- deferred: cached_deferred_texture,
+ depth: cached_depth_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)),
+ normal: cached_normals_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)),
+ // Red and Green channels are X and Y components of the motion vectors
+ // Blue channel doesn't matter, but set to 0.0 for possible faster clear
+ // https://gpuopen.com/performance/#clears
+ motion_vectors: cached_motion_vectors_texture
+ .map(|t| ColorAttachment::new(t, None, Color::BLACK)),
+ deferred: cached_deferred_texture.map(|t| ColorAttachment::new(t, None, Color::BLACK)),
deferred_lighting_pass_id: deferred_lighting_pass_id_texture,
size,
});
diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs
index 26106016bb21c..e685090437ca1 100644
--- a/crates/bevy_core_pipeline/src/deferred/node.rs
+++ b/crates/bevy_core_pipeline/src/deferred/node.rs
@@ -8,18 +8,14 @@ use bevy_render::{
prelude::Color,
render_graph::{NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
- render_resource::{
- LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
- RenderPassDescriptor,
- },
+ render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor},
renderer::RenderContext,
view::ViewDepthTexture,
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
-use crate::core_3d::{Camera3d, Camera3dDepthLoadOp};
-use crate::prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass, ViewPrepassTextures};
+use crate::prepass::ViewPrepassTextures;
use super::{AlphaMask3dDeferred, Opaque3dDeferred};
@@ -36,10 +32,6 @@ impl ViewNode for DeferredGBufferPrepassNode {
&'static RenderPhase,
&'static ViewDepthTexture,
&'static ViewPrepassTextures,
- &'static Camera3d,
- Option<&'static DepthPrepass>,
- Option<&'static NormalPrepass>,
- Option<&'static MotionVectorPrepass>,
);
fn run(
@@ -52,10 +44,6 @@ impl ViewNode for DeferredGBufferPrepassNode {
alpha_mask_deferred_phase,
view_depth_texture,
view_prepass_textures,
- camera_3d,
- depth_prepass,
- normal_prepass,
- motion_vector_prepass,
): QueryItem,
world: &World,
) -> Result<(), NodeRunError> {
@@ -66,37 +54,14 @@ impl ViewNode for DeferredGBufferPrepassNode {
view_prepass_textures
.normal
.as_ref()
- .map(|view_normals_texture| RenderPassColorAttachment {
- view: &view_normals_texture.default_view,
- resolve_target: None,
- ops: Operations {
- load: if normal_prepass.is_some() {
- // Load if the normal_prepass has already run.
- // The prepass will have already cleared this for the current frame.
- LoadOp::Load
- } else {
- LoadOp::Clear(Color::BLACK.into())
- },
- store: StoreOp::Store,
- },
- }),
+ .map(|normals_texture| normals_texture.get_attachment()),
+ );
+ color_attachments.push(
+ view_prepass_textures
+ .motion_vectors
+ .as_ref()
+ .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()),
);
- color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map(
- |view_motion_vectors_texture| RenderPassColorAttachment {
- view: &view_motion_vectors_texture.default_view,
- resolve_target: None,
- ops: Operations {
- load: if motion_vector_prepass.is_some() {
- // Load if the motion_vector_prepass has already run.
- // The prepass will have already cleared this for the current frame.
- LoadOp::Load
- } else {
- LoadOp::Clear(Color::BLACK.into())
- },
- store: StoreOp::Store,
- },
- },
- ));
// If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors:
// Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format.
@@ -106,7 +71,7 @@ impl ViewNode for DeferredGBufferPrepassNode {
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
if let Some(deferred_texture) = &view_prepass_textures.deferred {
render_context.command_encoder().clear_texture(
- &deferred_texture.texture,
+ &deferred_texture.texture.texture,
&bevy_render::render_resource::ImageSubresourceRange::default(),
);
}
@@ -115,16 +80,20 @@ impl ViewNode for DeferredGBufferPrepassNode {
view_prepass_textures
.deferred
.as_ref()
- .map(|deferred_texture| RenderPassColorAttachment {
- view: &deferred_texture.default_view,
- resolve_target: None,
- ops: Operations {
- #[cfg(all(feature = "webgl", target_arch = "wasm32"))]
- load: LoadOp::Load,
- #[cfg(not(all(feature = "webgl", target_arch = "wasm32")))]
- load: LoadOp::Clear(Default::default()),
- store: StoreOp::Store,
- },
+ .map(|deferred_texture| {
+ #[cfg(all(feature = "webgl", target_arch = "wasm32"))]
+ {
+ RenderPassColorAttachment {
+ view: &deferred_texture.texture.default_view,
+ resolve_target: None,
+ ops: Operations {
+ load: LoadOp::Load,
+ store: StoreOp::Store,
+ },
+ }
+ }
+ #[cfg(not(all(feature = "webgl", target_arch = "wasm32")))]
+ deferred_texture.get_attachment()
}),
);
@@ -136,7 +105,7 @@ impl ViewNode for DeferredGBufferPrepassNode {
view: &deferred_lighting_pass_id.default_view,
resolve_target: None,
ops: Operations {
- load: LoadOp::Clear(Default::default()),
+ load: LoadOp::Clear(Color::BLACK.into()),
store: StoreOp::Store,
},
}),
@@ -152,24 +121,7 @@ impl ViewNode for DeferredGBufferPrepassNode {
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("deferred"),
color_attachments: &color_attachments,
- depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
- view: &view_depth_texture.view,
- depth_ops: Some(Operations {
- load: if depth_prepass.is_some()
- || normal_prepass.is_some()
- || motion_vector_prepass.is_some()
- {
- // If any prepass runs, it will generate a depth buffer so we should use it.
- Camera3dDepthLoadOp::Load
- } else {
- // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections.
- camera_3d.depth_load_op.clone()
- }
- .into(),
- store: StoreOp::Store,
- }),
- stencil_ops: None,
- }),
+ depth_stencil_attachment: Some(view_depth_texture.get_attachment(StoreOp::Store)),
timestamp_writes: None,
occlusion_query_set: None,
});
@@ -198,7 +150,7 @@ impl ViewNode for DeferredGBufferPrepassNode {
// Copy depth buffer to texture.
render_context.command_encoder().copy_texture_to_texture(
view_depth_texture.texture.as_image_copy(),
- prepass_depth_texture.texture.as_image_copy(),
+ prepass_depth_texture.texture.texture.as_image_copy(),
view_prepass_textures.size,
);
}
diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs
index f710b8c704650..5aad6703eda56 100644
--- a/crates/bevy_core_pipeline/src/lib.rs
+++ b/crates/bevy_core_pipeline/src/lib.rs
@@ -1,6 +1,5 @@
pub mod blit;
pub mod bloom;
-pub mod clear_color;
pub mod contrast_adaptive_sharpening;
pub mod core_2d;
pub mod core_3d;
@@ -29,7 +28,6 @@ pub mod experimental {
pub mod prelude {
#[doc(hidden)]
pub use crate::{
- clear_color::ClearColor,
core_2d::{Camera2d, Camera2dBundle},
core_3d::{Camera3d, Camera3dBundle},
};
@@ -38,7 +36,6 @@ pub mod prelude {
use crate::{
blit::BlitPlugin,
bloom::BloomPlugin,
- clear_color::{ClearColor, ClearColorConfig},
contrast_adaptive_sharpening::CASPlugin,
core_2d::Core2dPlugin,
core_3d::Core3dPlugin,
@@ -46,13 +43,13 @@ use crate::{
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
fxaa::FxaaPlugin,
msaa_writeback::MsaaWritebackPlugin,
- prepass::{DepthPrepass, NormalPrepass},
+ prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
tonemapping::TonemappingPlugin,
upscaling::UpscalingPlugin,
};
use bevy_app::{App, Plugin};
use bevy_asset::load_internal_asset;
-use bevy_render::{extract_resource::ExtractResourcePlugin, prelude::Shader};
+use bevy_render::prelude::Shader;
#[derive(Default)]
pub struct CorePipelinePlugin;
@@ -66,13 +63,11 @@ impl Plugin for CorePipelinePlugin {
Shader::from_wgsl
);
- app.register_type::()
- .register_type::()
- .register_type::()
+ app.register_type::()
.register_type::()
- .init_resource::