From 5fb3eb5cb975b41262c1355714177ce631ae534c Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 13 Sep 2023 11:29:19 -0700 Subject: [PATCH 01/72] Manual "Reflect Value" AssetPath impl to fix dynamic linking (#9752) # Objective Fix #9747 ## Solution Linkers don't like what we're doing with CowArc (I'm guessing it has something to do with `?Sized`). Weirdly the `Reflect` derive on `AssetPath` doesn't fail, despite `CowArc` not implementing `Reflect`. To resolve this, we manually implement "reflect value" for `AssetPath<'static>`. It sadly cannot use `impl_reflect_value` because that macro doesn't support static lifetimes. --------- Co-authored-by: Martin Dickopp --- crates/bevy_asset/src/path.rs | 143 ++++++++++++++++++++++++++++++- crates/bevy_utils/src/cow_arc.rs | 12 +++ 2 files changed, 151 insertions(+), 4 deletions(-) diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index db64a6481dc60..efd12041148e3 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -1,9 +1,14 @@ -use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; +use bevy_reflect::{ + std_traits::ReflectDefault, utility::NonGenericTypeInfoCell, FromReflect, FromType, + GetTypeRegistration, Reflect, ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, + ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, + Typed, ValueInfo, +}; use bevy_utils::CowArc; use serde::{de::Visitor, Deserialize, Serialize}; use std::{ fmt::{Debug, Display}, - hash::Hash, + hash::{Hash, Hasher}, ops::Deref, path::{Path, PathBuf}, }; @@ -40,8 +45,7 @@ use std::{ /// This means that the common case of `asset_server.load("my_scene.scn")` when it creates and /// clones internal owned [`AssetPaths`](AssetPath). /// This also means that you should use [`AssetPath::new`] in cases where `&str` is the explicit type. -#[derive(Eq, PartialEq, Hash, Clone, Reflect)] -#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Eq, PartialEq, Hash, Clone, Default)] pub struct AssetPath<'a> { path: CowArc<'a, Path>, label: Option>, @@ -267,3 +271,134 @@ impl<'de> Visitor<'de> for AssetPathVisitor { Ok(AssetPath::from(v)) } } + +// NOTE: We manually implement "reflect value" because deriving Reflect on `AssetPath` breaks dynamic linking +// See https://github.com/bevyengine/bevy/issues/9747 +// NOTE: This could use `impl_reflect_value` if it supported static lifetimes. + +impl GetTypeRegistration for AssetPath<'static> { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl TypePath for AssetPath<'static> { + fn type_path() -> &'static str { + "bevy_asset::path::AssetPath<'static>" + } + fn short_type_path() -> &'static str { + "AssetPath<'static>" + } + fn type_ident() -> Option<&'static str> { + Some("AssetPath<'static>") + } + fn crate_name() -> Option<&'static str> { + Option::None + } + fn module_path() -> Option<&'static str> { + Option::None + } +} +impl Typed for AssetPath<'static> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| { + let info = ValueInfo::new::(); + TypeInfo::Value(info) + }) + } +} +impl Reflect for AssetPath<'static> { + #[inline] + fn type_name(&self) -> &str { + ::core::any::type_name::() + } + #[inline] + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + #[inline] + fn into_any(self: Box) -> Box { + self + } + #[inline] + fn as_any(&self) -> &dyn ::core::any::Any { + self + } + #[inline] + fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { + self + } + #[inline] + fn into_reflect(self: Box) -> Box { + self + } + #[inline] + fn as_reflect(&self) -> &dyn Reflect { + self + } + #[inline] + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + 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::() { + *self = value.clone(); + } else { + panic!("Value is not {}.", std::any::type_name::()); + } + } + #[inline] + fn set( + &mut self, + value: Box, + ) -> Result<(), Box> { + *self = ::take(value)?; + Ok(()) + } + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Value(self) + } + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Value(self) + } + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Value(self) + } + fn reflect_hash(&self) -> Option { + let mut hasher = bevy_reflect::utility::reflect_hasher(); + Hash::hash(&::core::any::Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(Hasher::finish(&hasher)) + } + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + let value = ::as_any(value); + if let Some(value) = ::downcast_ref::(value) { + Some(::core::cmp::PartialEq::eq(self, value)) + } else { + Some(false) + } + } + fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(self, f) + } +} +impl FromReflect for AssetPath<'static> { + fn from_reflect(reflect: &dyn Reflect) -> Option { + Some(Clone::clone(::downcast_ref::< + AssetPath<'static>, + >(::as_any(reflect))?)) + } +} diff --git a/crates/bevy_utils/src/cow_arc.rs b/crates/bevy_utils/src/cow_arc.rs index 5e1259a82b82c..31a204863d3d3 100644 --- a/crates/bevy_utils/src/cow_arc.rs +++ b/crates/bevy_utils/src/cow_arc.rs @@ -101,6 +101,18 @@ impl<'a, T: PartialOrd + ?Sized> PartialOrd for CowArc<'a, T> { } } +impl Default for CowArc<'static, str> { + fn default() -> Self { + CowArc::Static(Default::default()) + } +} + +impl Default for CowArc<'static, Path> { + fn default() -> Self { + CowArc::Static(Path::new("")) + } +} + impl<'a, T: Ord + ?Sized> Ord for CowArc<'a, T> { #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { From 324c057b7195881397e2db2f0417cb105ff53c24 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 13 Sep 2023 12:10:11 -0700 Subject: [PATCH 02/72] Cache System Tracing Spans (#9390) # Objective - Reduce the overhead of tracing by caching the system spans. Yellow is this pr. Red is main. ![image](https://github.com/bevyengine/bevy/assets/2180432/fe9bb7c2-ae9a-4522-80a9-75a943a562b6) --- .../src/schedule/executor/multi_threaded.rs | 53 +++++++++---------- .../bevy_ecs/src/schedule/executor/simple.rs | 10 +--- .../src/schedule/executor/single_threaded.rs | 14 +---- crates/bevy_ecs/src/system/commands/mod.rs | 4 +- .../src/system/commands/parallel_scope.rs | 4 +- .../src/system/exclusive_function_system.rs | 3 ++ crates/bevy_ecs/src/system/function_system.rs | 17 +++++- 7 files changed, 47 insertions(+), 58 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 8f6a3133c5966..dad3fa4459d20 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -7,7 +7,7 @@ use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor}; use bevy_utils::default; use bevy_utils::syncunsafecell::SyncUnsafeCell; #[cfg(feature = "trace")] -use bevy_utils::tracing::{info_span, Instrument}; +use bevy_utils::tracing::{info_span, Instrument, Span}; use std::panic::AssertUnwindSafe; use async_channel::{Receiver, Sender}; @@ -62,6 +62,9 @@ struct SystemTaskMetadata { is_send: bool, /// Is `true` if the system is exclusive. is_exclusive: bool, + /// Cached tracing span for system task + #[cfg(feature = "trace")] + system_task_span: Span, } /// The result of running a system that is sent across a channel. @@ -153,6 +156,11 @@ impl SystemExecutor for MultiThreadedExecutor { dependents: schedule.system_dependents[index].clone(), is_send: schedule.systems[index].is_send(), is_exclusive: schedule.systems[index].is_exclusive(), + #[cfg(feature = "trace")] + system_task_span: info_span!( + "system_task", + name = &*schedule.systems[index].name() + ), }); } @@ -486,17 +494,9 @@ impl MultiThreadedExecutor { ) { // SAFETY: this system is not running, no other reference exists let system = unsafe { &mut *systems[system_index].get() }; - - #[cfg(feature = "trace")] - let task_span = info_span!("system_task", name = &*system.name()); - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*system.name()); - let sender = self.sender.clone(); let panic_payload = self.panic_payload.clone(); let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { // SAFETY: // - The caller ensures that we have permission to @@ -504,8 +504,6 @@ impl MultiThreadedExecutor { // - `update_archetype_component_access` has been called. unsafe { system.run_unsafe((), world) }; })); - #[cfg(feature = "trace")] - drop(system_guard); // tell the executor that the system finished sender .try_send(SystemResult { @@ -524,7 +522,11 @@ impl MultiThreadedExecutor { }; #[cfg(feature = "trace")] - let task = task.instrument(task_span); + let task = task.instrument( + self.system_task_metadata[system_index] + .system_task_span + .clone(), + ); let system_meta = &self.system_task_metadata[system_index]; self.active_access @@ -550,11 +552,6 @@ impl MultiThreadedExecutor { // SAFETY: this system is not running, no other reference exists let system = unsafe { &mut *systems[system_index].get() }; - #[cfg(feature = "trace")] - let task_span = info_span!("system_task", name = &*system.name()); - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*system.name()); - let sender = self.sender.clone(); let panic_payload = self.panic_payload.clone(); if is_apply_deferred(system) { @@ -562,11 +559,7 @@ impl MultiThreadedExecutor { let unapplied_systems = self.unapplied_systems.clone(); self.unapplied_systems.clear(); let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); let res = apply_deferred(&unapplied_systems, systems, world); - #[cfg(feature = "trace")] - drop(system_guard); // tell the executor that the system finished sender .try_send(SystemResult { @@ -582,17 +575,17 @@ impl MultiThreadedExecutor { }; #[cfg(feature = "trace")] - let task = task.instrument(task_span); + let task = task.instrument( + self.system_task_metadata[system_index] + .system_task_span + .clone(), + ); scope.spawn_on_scope(task); } else { let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { system.run((), world); })); - #[cfg(feature = "trace")] - drop(system_guard); // tell the executor that the system finished sender .try_send(SystemResult { @@ -612,7 +605,11 @@ impl MultiThreadedExecutor { }; #[cfg(feature = "trace")] - let task = task.instrument(task_span); + let task = task.instrument( + self.system_task_metadata[system_index] + .system_task_span + .clone(), + ); scope.spawn_on_scope(task); } @@ -718,8 +715,6 @@ unsafe fn evaluate_and_fold_conditions( conditions .iter_mut() .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); // SAFETY: The caller ensures that `world` has permission to // access any data required by the condition. unsafe { condition.run_unsafe((), world) } diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index e256064ff7879..c34a88c57a42c 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -77,13 +77,9 @@ impl SystemExecutor for SimpleExecutor { } let system = &mut schedule.systems[system_index]; - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { system.run((), world); })); - #[cfg(feature = "trace")] - system_span.exit(); if let Err(payload) = res { eprintln!("Encountered a panic in system `{}`!", &*system.name()); std::panic::resume_unwind(payload); @@ -113,10 +109,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W #[allow(clippy::unnecessary_fold)] conditions .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }) + .map(|condition| condition.run((), world)) .fold(true, |acc, res| acc && res) } diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index ab950c0e5c451..dd6c4e6a41c54 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -86,19 +86,11 @@ impl SystemExecutor for SingleThreadedExecutor { let system = &mut schedule.systems[system_index]; if is_apply_deferred(system) { - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); self.apply_deferred(schedule, world); - #[cfg(feature = "trace")] - system_span.exit(); } else { - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { system.run((), world); })); - #[cfg(feature = "trace")] - system_span.exit(); if let Err(payload) = res { eprintln!("Encountered a panic in system `{}`!", &*system.name()); std::panic::resume_unwind(payload); @@ -143,10 +135,6 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W #[allow(clippy::unnecessary_fold)] conditions .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }) + .map(|condition| condition.run((), world)) .fold(true, |acc, res| acc && res) } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 9009d63859be3..8eb1881273f1b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -118,9 +118,7 @@ impl SystemBuffer for CommandQueue { #[inline] fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { #[cfg(feature = "trace")] - let _system_span = - bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) - .entered(); + let _span_guard = _system_meta.commands_span.enter(); self.apply(world); } } diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 65b01a8f2156d..6296f0293393c 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -52,9 +52,7 @@ impl SystemBuffer for ParallelCommandQueue { #[inline] fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { #[cfg(feature = "trace")] - let _system_span = - bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) - .entered(); + let _system_span = _system_meta.commands_span.enter(); for cq in &mut self.thread_local_storage { cq.get_mut().apply(world); } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 6d2a31f554201..ef70330173068 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -93,6 +93,9 @@ where } fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { + #[cfg(feature = "trace")] + let _span_guard = self.system_meta.system_span.enter(); + let saved_last_tick = world.last_change_tick; world.last_change_tick = self.system_meta.last_run; diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index e245f93716d78..2ef46c971863a 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -10,6 +10,9 @@ use crate::{ use bevy_utils::all_tuples; use std::{any::TypeId, borrow::Cow, marker::PhantomData}; +#[cfg(feature = "trace")] +use bevy_utils::tracing::{info_span, Span}; + use super::{In, IntoSystem, ReadOnlySystem}; /// The metadata of a [`System`]. @@ -22,16 +25,25 @@ pub struct SystemMeta { // SystemParams from overriding each other is_send: bool, pub(crate) last_run: Tick, + #[cfg(feature = "trace")] + pub(crate) system_span: Span, + #[cfg(feature = "trace")] + pub(crate) commands_span: Span, } impl SystemMeta { pub(crate) fn new() -> Self { + let name = std::any::type_name::(); Self { - name: std::any::type_name::().into(), + name: name.into(), archetype_component_access: Access::default(), component_access_set: FilteredAccessSet::default(), is_send: true, last_run: Tick::new(0), + #[cfg(feature = "trace")] + system_span: info_span!("system", name = name), + #[cfg(feature = "trace")] + commands_span: info_span!("system_commands", name = name), } } @@ -444,6 +456,9 @@ where #[inline] unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out { + #[cfg(feature = "trace")] + let _span_guard = self.system_meta.system_span.enter(); + let change_tick = world.increment_change_tick(); // SAFETY: From 5eb688983290ee7fe86e712b76579f9dd78f90e0 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Thu, 14 Sep 2023 02:21:23 +0700 Subject: [PATCH 03/72] examples: Remove unused doc comments. (#9795) # Objective - Compile all targets without warnings about unused doc comments. ## Solution - Turn unused doc comments into regular comments. Doc comments aren't supported on expressions, so they can just be regular comments. --- examples/ui/grid.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/ui/grid.rs b/examples/ui/grid.rs index ed168f05cbc1c..af961b0daf6de 100644 --- a/examples/ui/grid.rs +++ b/examples/ui/grid.rs @@ -23,19 +23,19 @@ fn spawn_layout(mut commands: Commands, asset_server: Res) { commands .spawn(NodeBundle { style: Style { - /// Use the CSS Grid algorithm for laying out this node + // Use the CSS Grid algorithm for laying out this node display: Display::Grid, - /// Make node fill the entirety it's parent (in this case the window) + // Make node fill the entirety it's parent (in this case the window) width: Val::Percent(100.0), height: Val::Percent(100.0), - /// Set the grid to have 2 columns with sizes [min-content, minmax(0, 1fr)] - /// - The first column will size to the size of it's contents - /// - The second column will take up the remaining available space + // Set the grid to have 2 columns with sizes [min-content, minmax(0, 1fr)] + // - The first column will size to the size of it's contents + // - The second column will take up the remaining available space grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)], - /// Set the grid to have 3 rows with sizes [auto, minmax(0, 1fr), 20px] - /// - The first row will size to the size of it's contents - /// - The second row take up remaining available space (after rows 1 and 3 have both been sized) - /// - The third row will be exactly 20px high + // Set the grid to have 3 rows with sizes [auto, minmax(0, 1fr), 20px] + // - The first row will size to the size of it's contents + // - The second row take up remaining available space (after rows 1 and 3 have both been sized) + // - The third row will be exactly 20px high grid_template_rows: vec![ GridTrack::auto(), GridTrack::flex(1.0), @@ -52,7 +52,7 @@ fn spawn_layout(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { display: Display::Grid, - /// Make this node span two grid columns so that it takes up the entire top tow + // Make this node span two grid columns so that it takes up the entire top tow grid_column: GridPlacement::span(2), padding: UiRect::all(Val::Px(6.0)), ..default() @@ -67,22 +67,22 @@ fn spawn_layout(mut commands: Commands, asset_server: Res) { builder .spawn(NodeBundle { style: Style { - /// Make the height of the node fill its parent + // Make the height of the node fill its parent height: Val::Percent(100.0), - /// Make the grid have a 1:1 aspect ratio meaning it will scale as an exact square - /// As the height is set explicitly, this means the width will adjust to match the height + // Make the grid have a 1:1 aspect ratio meaning it will scale as an exact square + // As the height is set explicitly, this means the width will adjust to match the height aspect_ratio: Some(1.0), - /// Use grid layout for this node + // Use grid layout for this node display: Display::Grid, // Add 24px of padding around the grid padding: UiRect::all(Val::Px(24.0)), - /// Set the grid to have 4 columns all with sizes minmax(0, 1fr) - /// This creates 4 exactly evenly sized columns + // Set the grid to have 4 columns all with sizes minmax(0, 1fr) + // This creates 4 exactly evenly sized columns grid_template_columns: RepeatedGridTrack::flex(4, 1.0), - /// Set the grid to have 4 rows all with sizes minmax(0, 1fr) - /// This creates 4 exactly evenly sized rows + // Set the grid to have 4 rows all with sizes minmax(0, 1fr) + // This creates 4 exactly evenly sized rows grid_template_rows: RepeatedGridTrack::flex(4, 1.0), - /// Set a 12px gap/gutter between rows and columns + // Set a 12px gap/gutter between rows and columns row_gap: Val::Px(12.0), column_gap: Val::Px(12.0), ..default() From ca46d7c357882d3871617fb13c1f8c85dafd59b6 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 13 Sep 2023 20:24:24 +0100 Subject: [PATCH 04/72] Add missing `bevy_text` feature attribute to `TextBundle` from impl (#9785) # Objective Add the `bevy_text` feature attribute to the `TextBundle` from impl in node_bundle.rs. --- crates/bevy_ui/src/node_bundles.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 6a9a291090ed4..7488d5dc41271 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -268,6 +268,7 @@ impl TextBundle { } } +#[cfg(feature = "bevy_text")] impl From for TextBundle where I: Into, From 8681d1cb04deeb7b941a74d04aa12ba89171b592 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Wed, 13 Sep 2023 12:25:44 -0700 Subject: [PATCH 05/72] Fix animate_scale scaling z value in text2d example (#9769) # Objective I noticed this while testing #9733. It's not causing any problems, but we shouldn't teach users to scale 2d stuff in z. ## Solution Only scale in x and y. --- examples/2d/text2d.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index ad783173d1309..18a959bef1c2a 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -187,6 +187,9 @@ fn animate_scale( // rendered quad, resulting in a pixellated look. for mut transform in &mut query { transform.translation = Vec3::new(400.0, 0.0, 0.0); - transform.scale = Vec3::splat((time.elapsed_seconds().sin() + 1.1) * 2.0); + + let scale = (time.elapsed_seconds().sin() + 1.1) * 2.0; + transform.scale.x = scale; + transform.scale.y = scale; } } From 55678fee98e19588966478aaea7c9c6668d7c64d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:26:25 +0000 Subject: [PATCH 06/72] Bump actions/checkout from 2 to 4 (#9759) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4.
Release notes

Sourced from actions/checkout's releases.

v4.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v4.0.0

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

v3.5.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v3.5.1...v3.5.2

v3.5.1

What's Changed

New Contributors

... (truncated)

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=2&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 24 ++++++++++++------------ .github/workflows/daily.yml | 6 +++--- .github/workflows/dependencies.yml | 8 ++++---- .github/workflows/docs.yml | 2 +- .github/workflows/post-release.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/validation-jobs.yml | 12 ++++++------ 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e9e82dc2bfc1..440f342269041 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -101,7 +101,7 @@ jobs: timeout-minutes: 30 needs: ci steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -127,7 +127,7 @@ jobs: timeout-minutes: 30 needs: build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -151,7 +151,7 @@ jobs: needs: check-missing-features-in-docs if: always() steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 @@ -167,7 +167,7 @@ jobs: runs-on: windows-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -201,7 +201,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -233,7 +233,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: check for missing metadata id: missing-metadata run: cargo run -p build-templated-pages -- check-missing examples @@ -267,7 +267,7 @@ jobs: timeout-minutes: 30 needs: check-missing-examples-in-docs steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: check for missing features id: missing-features run: cargo run -p build-templated-pages -- check-missing features @@ -301,7 +301,7 @@ jobs: timeout-minutes: 30 needs: build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -338,7 +338,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check for bevy_internal imports shell: bash run: | diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 5520c96281871..a21d00dfc6e72 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -14,7 +14,7 @@ jobs: runs-on: macos-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -88,7 +88,7 @@ jobs: - device: "Samsung Galaxy S23" os_version: "13.0" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run Example run: | diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index bc3f49d1a78ce..c8f11c16bf9d9 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -19,7 +19,7 @@ jobs: check-advisories: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Install cargo-deny run: cargo install cargo-deny @@ -46,7 +46,7 @@ jobs: with: path: cargo-tree-from-main key: cargo-tree-from-main - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable # if not on main, check that the cargo tree output is unchanged - name: Check if the cargo tree changed from main @@ -64,7 +64,7 @@ jobs: check-licenses: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Install cargo-deny run: cargo install cargo-deny @@ -74,7 +74,7 @@ jobs: check-sources: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Install cargo-deny run: cargo install cargo-deny diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d58d174409e91..ac2ba5c3e0632 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index c7940a9d245bb..01a8467f998a4 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -11,7 +11,7 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install cargo-release run: cargo install cargo-release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 933adaf546fc6..14729c1c19035 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install cargo-release run: cargo install cargo-release diff --git a/.github/workflows/validation-jobs.yml b/.github/workflows/validation-jobs.yml index 4189e735002b6..f48bf24254f14 100644 --- a/.github/workflows/validation-jobs.yml +++ b/.github/workflows/validation-jobs.yml @@ -17,7 +17,7 @@ jobs: runs-on: macos-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -77,7 +77,7 @@ jobs: sudo add-apt-repository ppa:kisak/kisak-mesa -y sudo apt-get update sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | @@ -128,7 +128,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: @@ -189,7 +189,7 @@ jobs: crate: [bevy_ecs, bevy_reflect, bevy] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Install alsa and udev run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev @@ -218,7 +218,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: | From 90b741d3d3744ddf72d2134cfeacce0d333697d6 Mon Sep 17 00:00:00 2001 From: Joseph <21144246+JoJoJet@users.noreply.github.com> Date: Wed, 13 Sep 2023 18:44:53 -0700 Subject: [PATCH 07/72] Return a boolean from `set_if_neq` (#9801) # Objective When using `set_if_neq`, sometimes you'd like to know if the new value was different from the old value so that you can perform some additional branching. ## Solution Return a bool from this function, which indicates whether or not the value was overwritten. --- ## Changelog * `DetectChangesMut::set_if_neq` now returns a boolean indicating whether or not the value was changed. ## Migration Guide The trait method `DetectChangesMut::set_if_neq` now returns a boolean value indicating whether or not the value was changed. If you were implementing this function manually, you must now return `true` if the value was overwritten and `false` if the value was not. --- crates/bevy_ecs/src/change_detection.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index a00a5cd6bb66b..01284c067eb5f 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -125,12 +125,13 @@ pub trait DetectChangesMut: DetectChanges { /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. fn bypass_change_detection(&mut self) -> &mut Self::Inner; - /// Overwrites this smart pointer with the given value, if and only if `*self != value` + /// Overwrites this smart pointer with the given value, if and only if `*self != value`. + /// Returns `true` if the value was overwritten, and returns `false` if it was not. /// /// This is useful to ensure change detection is only triggered when the underlying value /// changes, instead of every time it is mutably accessed. /// - /// If you need to handle the previous value, use [`replace_if_neq`](DetectChangesMut::replace_if_neq). + /// If you need the previous value, use [`replace_if_neq`](DetectChangesMut::replace_if_neq). /// /// # Examples /// @@ -160,7 +161,7 @@ pub trait DetectChangesMut: DetectChanges { /// # assert!(!score_changed.run((), &mut world)); /// ``` #[inline] - fn set_if_neq(&mut self, value: Self::Inner) + fn set_if_neq(&mut self, value: Self::Inner) -> bool where Self::Inner: Sized + PartialEq, { @@ -168,16 +169,19 @@ pub trait DetectChangesMut: DetectChanges { if *old != value { *old = value; self.set_changed(); + true + } else { + false } } - /// Overwrites this smart pointer with the given value, if and only if `*self != value` + /// Overwrites this smart pointer with the given value, if and only if `*self != value`, /// returning the previous value if this occurs. /// /// This is useful to ensure change detection is only triggered when the underlying value /// changes, instead of every time it is mutably accessed. /// - /// If you don't need to handle the previous value, use [`set_if_neq`](DetectChangesMut::set_if_neq). + /// If you don't need the previous value, use [`set_if_neq`](DetectChangesMut::set_if_neq). /// /// # Examples /// From 8192ac6f1e572601b4ada08fa2f82114e07f8aec Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Thu, 14 Sep 2023 19:44:41 +0700 Subject: [PATCH 08/72] doc: Remove reference to `clippy::manual-strip`. (#9794) This should have been removed in 8a9f475edb51ea45c976c000113ca55bac751f96 when the rest of the references to / usages of `clippy::manual-strip` were removed. It was originally needed in the long ago past when supporting Rust 1.45 was a concern. # Objective - Docs on linting should match the actual current practice. - Docs currently mention `-A clippy::manual-strip` which hasn't been needed in a long time. ## Solution - Remove reference to `-A clippy::manual-strip`. --- docs/linters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/linters.md b/docs/linters.md index 549bcb2b69462..307e68cb7970d 100644 --- a/docs/linters.md +++ b/docs/linters.md @@ -13,7 +13,7 @@ cargo fmt --all Can be automatically run with [`cargo run -p ci`](../tools/ci) (which also runs other checks) or manually with this command: ```bash -cargo clippy --workspace --all-targets --all-features -- -D warnings -A clippy::type_complexity -A clippy::manual-strip +cargo clippy --workspace --all-targets --all-features -- -D warnings -A clippy::type_complexity ``` Explanation: From 5e003794319ad716333cdb389363a69981a65fc6 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 14 Sep 2023 21:10:57 +0200 Subject: [PATCH 09/72] Remove TypeRegistry re-export rename (#9807) # Objective The rename is confusing. Each time I import `TypeRegistry` I have to think at least 10 seconds about how to import it. And I've been working a lot with bevy reflect, which multiplies the papercut. In my crates, you can find lots of: ```rust use bevy::reflect::{TypeRegistryInternal as TypeRegistry}; ``` When I "go to definition" on `TypeRegistry` I get to `TypeRegistryArc`. And when I mean `TypeRegistry` in my function signature, 100% of the time I mean `TypeRegistry`, not the arc wrapper. Rust has borrowing, and most use-cases of the TypeRegistry accepts borrow of the registry, with no need to mutate it. `TypeRegistryInternal` is also confusing. In bevy crates, it doesn't exist. The bevy crate documentation often refers to `TypeRegistry` and link to `TypeRegistryInternal`. It only exists in the bevy re-exports. It makes it hard to understand which names qualifies which types. ## Solution Remove the rename, keep the type names as they are in `bevy_reflect` --- ## Changelog - Remove `TypeRegistry` and `TypeRegistryArc` renames from bevy `bevy_reflect` re-exports. ## Migration Guide - `TypeRegistry` as re-exported by the wrapper `bevy` crate is now `TypeRegistryArc` - `TypeRegistryInternal` as re-exported by the wrapper `bevy` crate is now `TypeRegistry` --- crates/bevy_internal/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 723e65afd9c8f..8e6bf57e52909 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -60,11 +60,8 @@ pub mod ptr { } pub mod reflect { - // TODO: remove these renames once TypeRegistryArc is no longer required //! Type reflection used for dynamically interacting with rust types. - pub use bevy_reflect::{ - TypeRegistry as TypeRegistryInternal, TypeRegistryArc as TypeRegistry, *, - }; + pub use bevy_reflect::*; } #[cfg(feature = "bevy_scene")] From 060711669903306c59eaea427498948992f0e768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurzhan=20Sak=C3=A9n?= Date: Fri, 15 Sep 2023 01:15:00 +0400 Subject: [PATCH 10/72] "serialize" feature no longer enables the optional "bevy_scene" feature if it's not enabled from elsewhere (#9803) # Objective Fixes #9787 ## Solution ~~"serialize" feature enables "bevy_asset" now~~ "serialize" feature no longer enables the optional "bevy_scene" feature if it's not enabled from elsewhere (thanks to @mockersf) --- crates/bevy_internal/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 95f2d888b54ec..1a71d43819078 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -61,7 +61,7 @@ symphonia-wav = ["bevy_audio/symphonia-wav"] shader_format_glsl = ["bevy_render/shader_format_glsl"] shader_format_spirv = ["bevy_render/shader_format_spirv"] -serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene/serialize"] +serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene?/serialize"] multi-threaded = ["bevy_asset/multi-threaded", "bevy_ecs/multi-threaded", "bevy_tasks/multi-threaded"] # Display server protocol support (X11 is enabled by default) From 9ee9d627d7b525ecbf5eff24fbf321c8809fbb09 Mon Sep 17 00:00:00 2001 From: louis-le-cam <141912515+louis-le-cam@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:37:20 +0200 Subject: [PATCH 11/72] Rename RemovedComponents::iter/iter_with_id to read/read_with_id (#9778) # Objective Rename RemovedComponents::iter/iter_with_id to read/read_with_id to make it clear that it consume the data Fixes #9755. (It's my first pull request, if i've made any mistake, please let me know) ## Solution Refactor RemovedComponents::iter/iter_with_id to read/read_with_id ## Changelog Refactor RemovedComponents::iter/iter_with_id to read/read_with_id Deprecate RemovedComponents::iter/iter_with_id Remove IntoIterator implementation Update removal_detection example accordingly --- ## Migration Guide Rename calls of RemovedComponents::iter/iter_with_id to read/read_with_id Replace IntoIterator iteration (&mut ) with .read() --------- Co-authored-by: denshi_ika --- crates/bevy_ecs/src/removal_detection.rs | 35 ++++++++++++----------- crates/bevy_ecs/src/schedule/condition.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 4 +-- crates/bevy_transform/src/systems.rs | 4 +-- crates/bevy_ui/src/layout/mod.rs | 6 ++-- crates/bevy_winit/src/system.rs | 2 +- examples/ecs/removal_detection.rs | 4 +-- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index 0118ab8dedc55..c645fe017a30d 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -200,7 +200,7 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { /// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the /// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events /// that happened before now. - pub fn iter(&mut self) -> RemovedIter<'_> { + pub fn read(&mut self) -> RemovedIter<'_> { self.reader_mut_with_events() .map(|(reader, events)| reader.read(events).cloned()) .into_iter() @@ -208,8 +208,16 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { .map(RemovedComponentEntity::into) } - /// Like [`iter`](Self::iter), except also returning the [`EventId`] of the events. - pub fn iter_with_id(&mut self) -> RemovedIterWithId<'_> { + /// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the + /// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events + /// that happened before now. + #[deprecated = "use `.read()` instead."] + pub fn iter(&mut self) -> RemovedIter<'_> { + self.read() + } + + /// Like [`read`](Self::read), except also returning the [`EventId`] of the events. + pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> { self.reader_mut_with_events() .map(|(reader, events)| reader.read_with_id(events)) .into_iter() @@ -217,6 +225,12 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { .map(map_id_events) } + /// Like [`iter`](Self::iter), except also returning the [`EventId`] of the events. + #[deprecated = "use `.read_with_id()` instead."] + pub fn iter_with_id(&mut self) -> RemovedIterWithId<'_> { + self.read_with_id() + } + /// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any. pub fn len(&self) -> usize { self.events() @@ -233,8 +247,8 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { /// Consumes all available events. /// - /// This means these events will not appear in calls to [`RemovedComponents::iter()`] or - /// [`RemovedComponents::iter_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`. + /// This means these events will not appear in calls to [`RemovedComponents::read()`] or + /// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`. pub fn clear(&mut self) { if let Some((reader, events)) = self.reader_mut_with_events() { reader.clear(events); @@ -242,17 +256,6 @@ impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> { } } -impl<'a, 'w, 's: 'a, T> IntoIterator for &'a mut RemovedComponents<'w, 's, T> -where - T: Component, -{ - type Item = Entity; - type IntoIter = RemovedIter<'a>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - // SAFETY: Only reads World removed component events unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {} diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 6c3b8e80fec2a..021964311e294 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -943,7 +943,7 @@ pub mod common_conditions { // Simply checking `is_empty` would not be enough. // PERF: note that `count` is efficient (not actually looping/iterating), // due to Bevy having a specialized implementation for events. - move |mut removals: RemovedComponents| removals.iter().count() != 0 + move |mut removals: RemovedComponents| removals.read().count() != 0 } /// Generates a [`Condition`](super::Condition) that inverses the result of passed one. diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 527864d298037..e07445aa6500c 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1172,7 +1172,7 @@ mod tests { mut n_systems: ResMut, ) { assert_eq!( - removed_i32.iter().collect::>(), + removed_i32.read().collect::>(), &[despawned.0], "despawning causes the correct entity to show up in the 'RemovedComponent' system parameter." ); @@ -1200,7 +1200,7 @@ mod tests { // The despawned entity from the previous frame was // double buffered so we now have it in this system as well. assert_eq!( - removed_i32.iter().collect::>(), + removed_i32.read().collect::>(), &[despawned.0, removed.0], "removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter." ); diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 76ba509f6743b..b25784c3889fe 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -34,7 +34,7 @@ pub fn sync_simple_transforms( }); // Update orphaned entities. let mut query = query.p1(); - let mut iter = query.iter_many_mut(orphaned.iter()); + let mut iter = query.iter_many_mut(orphaned.read()); while let Some((transform, mut global_transform)) = iter.fetch_next() { if !transform.is_changed() && !global_transform.is_added() { *global_transform = GlobalTransform::from(*transform); @@ -57,7 +57,7 @@ pub fn propagate_transforms( mut orphaned_entities: Local>, ) { orphaned_entities.clear(); - orphaned_entities.extend(orphaned.iter()); + orphaned_entities.extend(orphaned.read()); orphaned_entities.sort_unstable(); root_query.par_iter_mut().for_each( |(entity, children, transform, mut global_transform)| { diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 41db367209021..a8ea44f664b4e 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -281,10 +281,10 @@ pub fn ui_layout_system( } // clean up removed nodes - ui_surface.remove_entities(removed_nodes.iter()); + ui_surface.remove_entities(removed_nodes.read()); // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. - for entity in removed_content_sizes.iter() { + for entity in removed_content_sizes.read() { ui_surface.try_remove_measure(entity); } @@ -292,7 +292,7 @@ pub fn ui_layout_system( ui_surface.set_window_children(primary_window_entity, root_node_query.iter()); // update and remove children - for entity in removed_children.iter() { + for entity in removed_children.read() { ui_surface.try_remove_children(entity); } for (entity, children) in &children_query { diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 0cd89c429c450..896466f3c7073 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -110,7 +110,7 @@ pub(crate) fn despawn_windows( mut close_events: EventWriter, mut winit_windows: NonSendMut, ) { - for window in closed.iter() { + for window in closed.read() { info!("Closing window {:?}", window); // Guard to verify that the window is in fact actually gone, // rather than having the component added and removed in the same frame. diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 2a5d641ea031c..096d22472ad18 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -51,9 +51,9 @@ fn remove_component( } fn react_on_removal(mut removed: RemovedComponents, mut query: Query<&mut Sprite>) { - // `RemovedComponents::iter()` returns an iterator with the `Entity`s that had their + // `RemovedComponents::read()` returns an iterator with the `Entity`s that had their // `Component` `T` (in this case `MyComponent`) removed at some point earlier during the frame. - for entity in &mut removed { + for entity in removed.read() { if let Ok(mut sprite) = query.get_mut(entity) { sprite.color.set_r(0.0); } From 462d2ff238e7b1e370fbd74e729930035f3278a0 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 15 Sep 2023 13:45:32 +0100 Subject: [PATCH 12/72] Move `Val` into `geometry` (#9818) # Objective `Val`'s natural place is in the `geometry` module with `UiRect`, not in `ui_node` with the components. ## Solution Move `Val` into `geometry`. --- crates/bevy_ui/src/geometry.rs | 258 ++++++++++++++++++++++++++++++++- crates/bevy_ui/src/ui_node.rs | 257 +------------------------------- 2 files changed, 258 insertions(+), 257 deletions(-) diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index 8f940ac0ff2da..f799efe2ade89 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -1,5 +1,184 @@ -use crate::Val; +use bevy_math::Vec2; use bevy_reflect::Reflect; +use bevy_reflect::ReflectDeserialize; +use bevy_reflect::ReflectSerialize; +use serde::Deserialize; +use serde::Serialize; +use std::ops::{Div, DivAssign, Mul, MulAssign}; +use thiserror::Error; + +/// Represents the possible value types for layout properties. +/// +/// This enum allows specifying values for various [`Style`](crate::Style) properties in different units, +/// such as logical pixels, percentages, or automatically determined values. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, Reflect)] +#[reflect(PartialEq, Serialize, Deserialize)] +pub enum Val { + /// Automatically determine the value based on the context and other [`Style`](crate::Style) properties. + Auto, + /// Set this value in logical pixels. + Px(f32), + /// Set the value as a percentage of its parent node's length along a specific axis. + /// + /// If the UI node has no parent, the percentage is calculated based on the window's length + /// along the corresponding axis. + /// + /// The chosen axis depends on the [`Style`](crate::Style) field set: + /// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`. + /// * For `gap`, `min_size`, `size`, and `max_size`: + /// - `width` is relative to the parent's width. + /// - `height` is relative to the parent's height. + /// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width. + /// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height. + Percent(f32), + /// Set this value in percent of the viewport width + Vw(f32), + /// Set this value in percent of the viewport height + Vh(f32), + /// Set this value in percent of the viewport's smaller dimension. + VMin(f32), + /// Set this value in percent of the viewport's larger dimension. + VMax(f32), +} + +impl PartialEq for Val { + fn eq(&self, other: &Self) -> bool { + let same_unit = matches!( + (self, other), + (Self::Auto, Self::Auto) + | (Self::Px(_), Self::Px(_)) + | (Self::Percent(_), Self::Percent(_)) + | (Self::Vw(_), Self::Vw(_)) + | (Self::Vh(_), Self::Vh(_)) + | (Self::VMin(_), Self::VMin(_)) + | (Self::VMax(_), Self::VMax(_)) + ); + + let left = match self { + Self::Auto => None, + Self::Px(v) + | Self::Percent(v) + | Self::Vw(v) + | Self::Vh(v) + | Self::VMin(v) + | Self::VMax(v) => Some(v), + }; + + let right = match other { + Self::Auto => None, + Self::Px(v) + | Self::Percent(v) + | Self::Vw(v) + | Self::Vh(v) + | Self::VMin(v) + | Self::VMax(v) => Some(v), + }; + + match (same_unit, left, right) { + (true, a, b) => a == b, + // All zero-value variants are considered equal. + (false, Some(&a), Some(&b)) => a == 0. && b == 0., + _ => false, + } + } +} + +impl Val { + pub const DEFAULT: Self = Self::Auto; + pub const ZERO: Self = Self::Px(0.0); +} + +impl Default for Val { + fn default() -> Self { + Self::DEFAULT + } +} + +impl Mul for Val { + type Output = Val; + + fn mul(self, rhs: f32) -> Self::Output { + match self { + Val::Auto => Val::Auto, + Val::Px(value) => Val::Px(value * rhs), + Val::Percent(value) => Val::Percent(value * rhs), + Val::Vw(value) => Val::Vw(value * rhs), + Val::Vh(value) => Val::Vh(value * rhs), + Val::VMin(value) => Val::VMin(value * rhs), + Val::VMax(value) => Val::VMax(value * rhs), + } + } +} + +impl MulAssign for Val { + fn mul_assign(&mut self, rhs: f32) { + match self { + Val::Auto => {} + Val::Px(value) + | Val::Percent(value) + | Val::Vw(value) + | Val::Vh(value) + | Val::VMin(value) + | Val::VMax(value) => *value *= rhs, + } + } +} + +impl Div for Val { + type Output = Val; + + fn div(self, rhs: f32) -> Self::Output { + match self { + Val::Auto => Val::Auto, + Val::Px(value) => Val::Px(value / rhs), + Val::Percent(value) => Val::Percent(value / rhs), + Val::Vw(value) => Val::Vw(value / rhs), + Val::Vh(value) => Val::Vh(value / rhs), + Val::VMin(value) => Val::VMin(value / rhs), + Val::VMax(value) => Val::VMax(value / rhs), + } + } +} + +impl DivAssign for Val { + fn div_assign(&mut self, rhs: f32) { + match self { + Val::Auto => {} + Val::Px(value) + | Val::Percent(value) + | Val::Vw(value) + | Val::Vh(value) + | Val::VMin(value) + | Val::VMax(value) => *value /= rhs, + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] +pub enum ValArithmeticError { + #[error("the variants of the Vals don't match")] + NonIdenticalVariants, + #[error("the given variant of Val is not evaluateable (non-numeric)")] + NonEvaluateable, +} + +impl Val { + /// Resolves a [`Val`] to its value in logical pixels and returns this as an [`f32`]. + /// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to resolve into a concrete value. + /// + /// **Note:** If a [`Val::Px`] is resolved, it's inner value is returned unchanged. + pub fn resolve(self, parent_size: f32, viewport_size: Vec2) -> Result { + match self { + Val::Percent(value) => Ok(parent_size * value / 100.0), + Val::Px(value) => Ok(value), + Val::Vw(value) => Ok(viewport_size.x * value / 100.0), + Val::Vh(value) => Ok(viewport_size.y * value / 100.0), + Val::VMin(value) => Ok(viewport_size.min_element() * value / 100.0), + Val::VMax(value) => Ok(viewport_size.max_element() * value / 100.0), + Val::Auto => Err(ValArithmeticError::NonEvaluateable), + } + } +} /// A type which is commonly used to define margins, paddings and borders. /// @@ -338,7 +517,82 @@ impl Default for UiRect { #[cfg(test)] mod tests { - use super::*; + use crate::geometry::*; + use bevy_math::vec2; + + #[test] + fn val_evaluate() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let result = Val::Percent(80.).resolve(size, viewport_size).unwrap(); + + assert_eq!(result, size * 0.8); + } + + #[test] + fn val_resolve_px() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let result = Val::Px(10.).resolve(size, viewport_size).unwrap(); + + assert_eq!(result, 10.); + } + + #[test] + fn val_resolve_viewport_coords() { + let size = 250.; + let viewport_size = vec2(500., 500.); + + for value in (-10..10).map(|value| value as f32) { + // for a square viewport there should be no difference between `Vw` and `Vh` and between `Vmin` and `Vmax`. + assert_eq!( + Val::Vw(value).resolve(size, viewport_size), + Val::Vh(value).resolve(size, viewport_size) + ); + assert_eq!( + Val::VMin(value).resolve(size, viewport_size), + Val::VMax(value).resolve(size, viewport_size) + ); + assert_eq!( + Val::VMin(value).resolve(size, viewport_size), + Val::Vw(value).resolve(size, viewport_size) + ); + } + + let viewport_size = vec2(1000., 500.); + assert_eq!(Val::Vw(100.).resolve(size, viewport_size).unwrap(), 1000.); + assert_eq!(Val::Vh(100.).resolve(size, viewport_size).unwrap(), 500.); + assert_eq!(Val::Vw(60.).resolve(size, viewport_size).unwrap(), 600.); + assert_eq!(Val::Vh(40.).resolve(size, viewport_size).unwrap(), 200.); + assert_eq!(Val::VMin(50.).resolve(size, viewport_size).unwrap(), 250.); + assert_eq!(Val::VMax(75.).resolve(size, viewport_size).unwrap(), 750.); + } + + #[test] + fn val_auto_is_non_resolveable() { + let size = 250.; + let viewport_size = vec2(1000., 500.); + let resolve_auto = Val::Auto.resolve(size, viewport_size); + + assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluateable)); + } + + #[test] + fn val_arithmetic_error_messages() { + assert_eq!( + format!("{}", ValArithmeticError::NonIdenticalVariants), + "the variants of the Vals don't match" + ); + assert_eq!( + format!("{}", ValArithmeticError::NonEvaluateable), + "the given variant of Val is not evaluateable (non-numeric)" + ); + } + + #[test] + fn default_val_equals_const_default_val() { + assert_eq!(Val::default(), Val::DEFAULT); + } #[test] fn uirect_default_equals_const_default() { diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e0d2ea4ff9acf..14080c82228e0 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,4 +1,4 @@ -use crate::UiRect; +use crate::{UiRect, Val}; use bevy_asset::Handle; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::{Rect, Vec2}; @@ -7,10 +7,7 @@ use bevy_render::{color::Color, texture::Image}; use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use std::{ - num::{NonZeroI16, NonZeroU16}, - ops::{Div, DivAssign, Mul, MulAssign}, -}; +use std::num::{NonZeroI16, NonZeroU16}; use thiserror::Error; /// Describes the size of a UI node @@ -78,179 +75,6 @@ impl Default for Node { } } -/// Represents the possible value types for layout properties. -/// -/// This enum allows specifying values for various [`Style`] properties in different units, -/// such as logical pixels, percentages, or automatically determined values. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] -pub enum Val { - /// Automatically determine the value based on the context and other [`Style`] properties. - Auto, - /// Set this value in logical pixels. - Px(f32), - /// Set the value as a percentage of its parent node's length along a specific axis. - /// - /// If the UI node has no parent, the percentage is calculated based on the window's length - /// along the corresponding axis. - /// - /// The chosen axis depends on the `Style` field set: - /// * For `flex_basis`, the percentage is relative to the main-axis length determined by the `flex_direction`. - /// * For `gap`, `min_size`, `size`, and `max_size`: - /// - `width` is relative to the parent's width. - /// - `height` is relative to the parent's height. - /// * For `margin`, `padding`, and `border` values: the percentage is relative to the parent node's width. - /// * For positions, `left` and `right` are relative to the parent's width, while `bottom` and `top` are relative to the parent's height. - Percent(f32), - /// Set this value in percent of the viewport width - Vw(f32), - /// Set this value in percent of the viewport height - Vh(f32), - /// Set this value in percent of the viewport's smaller dimension. - VMin(f32), - /// Set this value in percent of the viewport's larger dimension. - VMax(f32), -} - -impl PartialEq for Val { - fn eq(&self, other: &Self) -> bool { - let same_unit = matches!( - (self, other), - (Self::Auto, Self::Auto) - | (Self::Px(_), Self::Px(_)) - | (Self::Percent(_), Self::Percent(_)) - | (Self::Vw(_), Self::Vw(_)) - | (Self::Vh(_), Self::Vh(_)) - | (Self::VMin(_), Self::VMin(_)) - | (Self::VMax(_), Self::VMax(_)) - ); - - let left = match self { - Self::Auto => None, - Self::Px(v) - | Self::Percent(v) - | Self::Vw(v) - | Self::Vh(v) - | Self::VMin(v) - | Self::VMax(v) => Some(v), - }; - - let right = match other { - Self::Auto => None, - Self::Px(v) - | Self::Percent(v) - | Self::Vw(v) - | Self::Vh(v) - | Self::VMin(v) - | Self::VMax(v) => Some(v), - }; - - match (same_unit, left, right) { - (true, a, b) => a == b, - // All zero-value variants are considered equal. - (false, Some(&a), Some(&b)) => a == 0. && b == 0., - _ => false, - } - } -} - -impl Val { - pub const DEFAULT: Self = Self::Auto; - pub const ZERO: Self = Self::Px(0.0); -} - -impl Default for Val { - fn default() -> Self { - Self::DEFAULT - } -} - -impl Mul for Val { - type Output = Val; - - fn mul(self, rhs: f32) -> Self::Output { - match self { - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value * rhs), - Val::Percent(value) => Val::Percent(value * rhs), - Val::Vw(value) => Val::Vw(value * rhs), - Val::Vh(value) => Val::Vh(value * rhs), - Val::VMin(value) => Val::VMin(value * rhs), - Val::VMax(value) => Val::VMax(value * rhs), - } - } -} - -impl MulAssign for Val { - fn mul_assign(&mut self, rhs: f32) { - match self { - Val::Auto => {} - Val::Px(value) - | Val::Percent(value) - | Val::Vw(value) - | Val::Vh(value) - | Val::VMin(value) - | Val::VMax(value) => *value *= rhs, - } - } -} - -impl Div for Val { - type Output = Val; - - fn div(self, rhs: f32) -> Self::Output { - match self { - Val::Auto => Val::Auto, - Val::Px(value) => Val::Px(value / rhs), - Val::Percent(value) => Val::Percent(value / rhs), - Val::Vw(value) => Val::Vw(value / rhs), - Val::Vh(value) => Val::Vh(value / rhs), - Val::VMin(value) => Val::VMin(value / rhs), - Val::VMax(value) => Val::VMax(value / rhs), - } - } -} - -impl DivAssign for Val { - fn div_assign(&mut self, rhs: f32) { - match self { - Val::Auto => {} - Val::Px(value) - | Val::Percent(value) - | Val::Vw(value) - | Val::Vh(value) - | Val::VMin(value) - | Val::VMax(value) => *value /= rhs, - } - } -} - -#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] -pub enum ValArithmeticError { - #[error("the variants of the Vals don't match")] - NonIdenticalVariants, - #[error("the given variant of Val is not evaluateable (non-numeric)")] - NonEvaluateable, -} - -impl Val { - /// Resolves a [`Val`] to its value in logical pixels and returns this as an [`f32`]. - /// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to resolve into a concrete value. - /// - /// **Note:** If a [`Val::Px`] is resolved, it's inner value is returned unchanged. - pub fn resolve(self, parent_size: f32, viewport_size: Vec2) -> Result { - match self { - Val::Percent(value) => Ok(parent_size * value / 100.0), - Val::Px(value) => Ok(value), - Val::Vw(value) => Ok(viewport_size.x * value / 100.0), - Val::Vh(value) => Ok(viewport_size.y * value / 100.0), - Val::VMin(value) => Ok(viewport_size.min_element() * value / 100.0), - Val::VMax(value) => Ok(viewport_size.max_element() * value / 100.0), - Val::Auto => Err(ValArithmeticError::NonEvaluateable), - } - } -} - /// Describes the style of a UI container node /// /// Node's can be laid out using either Flexbox or CSS Grid Layout.
@@ -1704,84 +1528,7 @@ impl Default for ZIndex { #[cfg(test)] mod tests { - use super::Val; use crate::GridPlacement; - use crate::ValArithmeticError; - use bevy_math::vec2; - - #[test] - fn val_evaluate() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let result = Val::Percent(80.).resolve(size, viewport_size).unwrap(); - - assert_eq!(result, size * 0.8); - } - - #[test] - fn val_resolve_px() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let result = Val::Px(10.).resolve(size, viewport_size).unwrap(); - - assert_eq!(result, 10.); - } - - #[test] - fn val_resolve_viewport_coords() { - let size = 250.; - let viewport_size = vec2(500., 500.); - - for value in (-10..10).map(|value| value as f32) { - // for a square viewport there should be no difference between `Vw` and `Vh` and between `Vmin` and `Vmax`. - assert_eq!( - Val::Vw(value).resolve(size, viewport_size), - Val::Vh(value).resolve(size, viewport_size) - ); - assert_eq!( - Val::VMin(value).resolve(size, viewport_size), - Val::VMax(value).resolve(size, viewport_size) - ); - assert_eq!( - Val::VMin(value).resolve(size, viewport_size), - Val::Vw(value).resolve(size, viewport_size) - ); - } - - let viewport_size = vec2(1000., 500.); - assert_eq!(Val::Vw(100.).resolve(size, viewport_size).unwrap(), 1000.); - assert_eq!(Val::Vh(100.).resolve(size, viewport_size).unwrap(), 500.); - assert_eq!(Val::Vw(60.).resolve(size, viewport_size).unwrap(), 600.); - assert_eq!(Val::Vh(40.).resolve(size, viewport_size).unwrap(), 200.); - assert_eq!(Val::VMin(50.).resolve(size, viewport_size).unwrap(), 250.); - assert_eq!(Val::VMax(75.).resolve(size, viewport_size).unwrap(), 750.); - } - - #[test] - fn val_auto_is_non_resolveable() { - let size = 250.; - let viewport_size = vec2(1000., 500.); - let resolve_auto = Val::Auto.resolve(size, viewport_size); - - assert_eq!(resolve_auto, Err(ValArithmeticError::NonEvaluateable)); - } - - #[test] - fn val_arithmetic_error_messages() { - assert_eq!( - format!("{}", ValArithmeticError::NonIdenticalVariants), - "the variants of the Vals don't match" - ); - assert_eq!( - format!("{}", ValArithmeticError::NonEvaluateable), - "the given variant of Val is not evaluateable (non-numeric)" - ); - } - - #[test] - fn default_val_equals_const_default_val() { - assert_eq!(Val::default(), Val::DEFAULT); - } #[test] fn invalid_grid_placement_values() { From e1904bcba186e94984fcb077bc2c80bce5ec96e6 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 15 Sep 2023 20:51:57 +0100 Subject: [PATCH 13/72] Derive Serialize and Deserialize for UiRect (#9820) # Objective Derive `Serialize` and `Deserialize` for `UiRect` --- crates/bevy_ui/src/geometry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index f799efe2ade89..734fcd65c62aa 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -224,8 +224,8 @@ impl Val { /// bottom: Val::Px(40.0), /// }; /// ``` -#[derive(Copy, Clone, PartialEq, Debug, Reflect)] -#[reflect(PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect(PartialEq, Serialize, Deserialize)] pub struct UiRect { /// The value corresponding to the left side of the UI rect. pub left: Val, From c61faf35b8089dd2d6d394aa907d4756de1e31ce Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 16 Sep 2023 02:27:13 -0700 Subject: [PATCH 14/72] fix deprecation warning in bench (#9823) # Objective - Fix deprecation warning when running benches. ## Solution - iter -> read --- benches/benches/bevy_ecs/events/iter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/benches/bevy_ecs/events/iter.rs b/benches/benches/bevy_ecs/events/iter.rs index 4c5ed5375fa84..f46372c26b82a 100644 --- a/benches/benches/bevy_ecs/events/iter.rs +++ b/benches/benches/bevy_ecs/events/iter.rs @@ -18,7 +18,7 @@ impl Benchmark { pub fn run(&mut self) { let mut reader = self.0.get_reader(); - for evt in reader.iter(&self.0) { + for evt in reader.read(&self.0) { std::hint::black_box(evt); } } From 9d23f828f6a6d6285317eac7306e8a537ff7fec4 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Sat, 16 Sep 2023 23:10:58 +0100 Subject: [PATCH 15/72] generate indices for Mikktspace (#8862) # Objective mikktspace tangent generation requires mesh indices, and currently fails when they are not present. we can just generate them instead. ## Solution generate the indices. --- crates/bevy_render/src/mesh/mesh/mod.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index c971538c71085..5830e26cb04df 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -981,7 +981,7 @@ impl RenderAsset for Mesh { } struct MikktspaceGeometryHelper<'a> { - indices: &'a Indices, + indices: Option<&'a Indices>, positions: &'a Vec<[f32; 3]>, normals: &'a Vec<[f32; 3]>, uvs: &'a Vec<[f32; 2]>, @@ -993,15 +993,19 @@ impl MikktspaceGeometryHelper<'_> { let index_index = face * 3 + vert; match self.indices { - Indices::U16(indices) => indices[index_index] as usize, - Indices::U32(indices) => indices[index_index] as usize, + Some(Indices::U16(indices)) => indices[index_index] as usize, + Some(Indices::U32(indices)) => indices[index_index] as usize, + None => index_index, } } } impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { fn num_faces(&self) -> usize { - self.indices.len() / 3 + self.indices + .map(Indices::len) + .unwrap_or_else(|| self.positions.len()) + / 3 } fn num_vertices_of_face(&self, _: usize) -> usize { @@ -1080,14 +1084,11 @@ fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTang )) } }; - let indices = mesh - .indices() - .ok_or(GenerateTangentsError::MissingIndices)?; let len = positions.len(); let tangents = vec![[0., 0., 0., 0.]; len]; let mut mikktspace_mesh = MikktspaceGeometryHelper { - indices, + indices: mesh.indices(), positions, normals, uvs, From 26359f9b379def94c63f31d04bfe1839a4ed78e6 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Sun, 17 Sep 2023 18:07:11 -0700 Subject: [PATCH 16/72] Remove some old references to CoreSet (#9833) # Objective Remove some references to `CoreSet` which was removed in #8079. --- crates/bevy_ecs/src/world/mod.rs | 4 ++-- examples/ecs/apply_deferred.rs | 2 +- examples/ecs/removal_detection.rs | 2 +- examples/stress_tests/transform_hierarchy.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 55221ff2861a0..918b976e7f841 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -885,8 +885,8 @@ impl World { /// of detection to be recorded. /// /// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the - /// main app, to run during `CoreSet::Last`, so you don't need to call it manually. When using - /// `bevy_ecs` as a separate standalone crate however, you need to call this manually. + /// main app, to run during `Last`, so you don't need to call it manually. When using `bevy_ecs` + /// as a separate standalone crate however, you need to call this manually. /// /// ``` /// # use bevy_ecs::prelude::*; diff --git a/examples/ecs/apply_deferred.rs b/examples/ecs/apply_deferred.rs index e9944e497ffd9..5942ca9262e49 100644 --- a/examples/ecs/apply_deferred.rs +++ b/examples/ecs/apply_deferred.rs @@ -108,7 +108,7 @@ fn setup(mut commands: Commands) { // has finished during this tick, we spawn a new Apple and a new Orange. // // The commands that we have added here will normally be flushed by Bevy -// as part of the `CoreSet::UpdateFlush` set, but because we have ordered +// after all systems in the schedule have run, but because we have ordered // this system to run before `apply_deferred.in_set(CustomFlush)`, // these commands added here will be flushed during our custom flush. fn despawn_old_and_spawn_new_fruits( diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 096d22472ad18..d0f6073aeee96 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -11,7 +11,7 @@ fn main() { // and the Component` is removed. // // With these constraints in mind we make sure to place the system that removes a `Component` in - // `CoreSet::Update', and the system that reacts on the removal in `CoreSet::PostUpdate`. + // `Update', and the system that reacts on the removal in `PostUpdate`. App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) diff --git a/examples/stress_tests/transform_hierarchy.rs b/examples/stress_tests/transform_hierarchy.rs index 4f294c077bef2..3e6064971e79a 100644 --- a/examples/stress_tests/transform_hierarchy.rs +++ b/examples/stress_tests/transform_hierarchy.rs @@ -185,7 +185,7 @@ fn main() { .insert_resource(cfg) .add_plugins((MinimalPlugins, TransformPlugin)) .add_systems(Startup, setup) - // Updating transforms *must* be done before `CoreSet::PostUpdate` + // Updating transforms *must* be done before `PostUpdate` // or the hierarchy will momentarily be in an invalid state. .add_systems(Update, update) .run(); From 577ad78d510653f5d34a0b8d66e1298f8f0a5325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 18 Sep 2023 08:01:44 +0200 Subject: [PATCH 17/72] don't enable filesystem_watcher when building for WebGPU (#9829) # Objective - There are errors when building for WebGPU, since Assets V2 PR ``` error[E0432]: unresolved import `file_id::get_file_id` --> /Users/francoismockers/.cargo/registry/src/index.crates.io-6f17d22bba15001f/notify-debouncer-full-0.2.0/src/cache.rs:6:15 | 6 | use file_id::{get_file_id, FileId}; | ^^^^^^^^^^^ no `get_file_id` in the root | note: found an item that was configured out --> /Users/francoismockers/.cargo/registry/src/index.crates.io-6f17d22bba15001f/file-id-0.1.0/src/lib.rs:41:8 | 41 | pub fn get_file_id(path: impl AsRef) -> io::Result { | ^^^^^^^^^^^ = note: the item is gated behind the `unix` feature note: found an item that was configured out --> /Users/francoismockers/.cargo/registry/src/index.crates.io-6f17d22bba15001f/file-id-0.1.0/src/lib.rs:54:8 | 54 | pub fn get_file_id(path: impl AsRef) -> io::Result { | ^^^^^^^^^^^ = note: the item is gated behind the `windows` feature For more information about this error, try `rustc --explain E0432`. error: could not compile `notify-debouncer-full` (lib) due to previous error ``` ## Solution - Don't enable feature `filesystem_watcher` in WebGPU as it can't work anyway --- tools/build-wasm-example/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/build-wasm-example/src/main.rs b/tools/build-wasm-example/src/main.rs index 02a01524e1b38..fbb472958c35f 100644 --- a/tools/build-wasm-example/src/main.rs +++ b/tools/build-wasm-example/src/main.rs @@ -71,7 +71,6 @@ fn main() { features.push("zstd"); features.push("vorbis"); features.push("x11"); - features.push("filesystem_watcher"); features.push("bevy_gizmos"); features.push("android_shared_stdcxx"); features.push("tonemapping_luts"); From dc124ee498c18ff776cba4ae9a1036673a17de67 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 18 Sep 2023 09:54:39 +0100 Subject: [PATCH 18/72] `ContentSize` replacement fix (#9753) # Objective If you remove a `ContentSize` component from a Bevy UI entity and then replace it `ui_layout_system` will remove the measure func from the internal Taffy layout tree but no new measure func will be generated to replace it since it's the widget systems that are responsible for creating their respective measure funcs not `ui_layout_system`. The widget systems only perform a measure func update on changes to a widget entity's content. This means that until its content is changed in some way, no content will be displayed by the node. ### Example This example spawns a text node which disappears after a few moments once its `ContentSize` component is replaced. ```rust use bevy::prelude::*; use bevy::ui::ContentSize; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, delayed_replacement) .run(); } fn setup(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); commands.spawn( TextBundle::from_section( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", TextStyle::default(), ) ); } // Waits a few frames to make sure the font is loaded and the text's glyph layout has been generated. fn delayed_replacement(mut commands: Commands, mut count: Local, query: Query>) { *count += 1; if *count == 10 { for item in query.iter() { commands .entity(item) .remove::() .insert(ContentSize::default()); } } } ``` ## Solution Perform `ui_layout_system`'s `ContentSize` removal detection and resolution first, before the measure func updates. Then in the widget systems, generate a new `Measure` when a `ContentSize` component is added to a widget entity. ## Changelog * `measure_text_system`, `update_image_content_size_system` and `update_atlas_content_size_system` generate a new `Measure` when a `ContentSize` component is added. --- crates/bevy_ui/src/layout/mod.rs | 10 +++++----- crates/bevy_ui/src/widget/image.rs | 11 +++++++++-- crates/bevy_ui/src/widget/text.rs | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index a8ea44f664b4e..80cd0c9f86092 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -274,6 +274,11 @@ pub fn ui_layout_system( } } + // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. + for entity in removed_content_sizes.read() { + ui_surface.try_remove_measure(entity); + } + for (entity, mut content_size) in measure_query.iter_mut() { if let Some(measure_func) = content_size.measure_func.take() { ui_surface.update_measure(entity, measure_func); @@ -283,11 +288,6 @@ pub fn ui_layout_system( // clean up removed nodes ui_surface.remove_entities(removed_nodes.read()); - // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. - for entity in removed_content_sizes.read() { - ui_surface.try_remove_measure(entity); - } - // update window children (for now assuming all Nodes live in the primary window) ui_surface.set_window_children(primary_window_entity, root_node_query.iter()); diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 022c174adf462..f7a54c0da6f42 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -3,6 +3,7 @@ use crate::{ }; use bevy_asset::{Assets, Handle}; +use bevy_ecs::change_detection::DetectChanges; use bevy_ecs::query::Without; use bevy_ecs::{ prelude::Component, @@ -96,7 +97,10 @@ pub fn update_image_content_size_system( texture.texture_descriptor.size.height as f32, ); // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor { + if size != image_size.size + || combined_scale_factor != *previous_combined_scale_factor + || content_size.is_added() + { image_size.size = size; content_size.set(ImageMeasure { // multiply the image size by the scale factor to get the physical size @@ -135,7 +139,10 @@ pub fn update_atlas_content_size_system( if let Some(atlas) = atlases.get(atlas) { let size = atlas.textures[atlas_image.index].size(); // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor { + if size != image_size.size + || combined_scale_factor != *previous_combined_scale_factor + || content_size.is_added() + { image_size.size = size; content_size.set(ImageMeasure { // multiply the image size by the scale factor to get the physical size diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 9700d77c967bd..0f96057eafa9a 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -128,7 +128,7 @@ pub fn measure_text_system( if *last_scale_factor == scale_factor { // scale factor unchanged, only create new measure funcs for modified text for (text, content_size, text_flags) in text_query.iter_mut() { - if text.is_changed() || text_flags.needs_new_measure_func { + if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() { create_text_measure(&fonts, scale_factor, text, content_size, text_flags); } } From 0bd4ea7cede2a54d8278e85795c6d5e66f0aeb9a Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Mon, 18 Sep 2023 15:41:51 +0200 Subject: [PATCH 19/72] Provide getters for fields of ReflectFromPtr (#9748) # Objective The reasoning is similar to #8687. I'm building a dynamic query. Currently, I store the ReflectFromPtr in my dynamic `Fetch` type. [See relevant code](https://github.com/nicopap/bevy_mod_dynamic_query/blob/97ba68ae1e13f15cabfac1dcd58c2483396dfd3f/src/fetches.rs#L14-L17) However, `ReflectFromPtr` is: - 16 bytes for TypeId - 8 bytes for the non-mutable function pointer - 8 bytes for the mutable function pointer It's a lot, it adds 32 bytes to my base `Fetch` which is only `ComponendId` (8 bytes) for a total of 40 bytes. I only need one function per fetch, reducing the total dynamic fetch size to 16 bytes. Since I'm querying the components by the ComponendId associated with the function pointer I'm using, I don't need the TypeId, it's a redundant check. In fact, I've difficulties coming up with situations where checking the TypeId beforehand is relevant. So to me, if ReflectFromPtr makes sense as a public API, exposing the function pointers also makes sense. ## Solution - Make the fields public through methods. --- ## Changelog - Add `from_ptr` and `from_ptr_mut` methods to `ReflectFromPtr` to access the underlying function pointers - `ReflectFromPtr::as_reflect_ptr` is now `ReflectFromPtr::as_reflect` - `ReflectFromPtr::as_reflect_ptr_mut` is now `ReflectFromPtr::as_reflect_mut` ## Migration guide - `ReflectFromPtr::as_reflect_ptr` is now `ReflectFromPtr::as_reflect` - `ReflectFromPtr::as_reflect_ptr_mut` is now `ReflectFromPtr::as_reflect_mut` --- crates/bevy_ecs/src/change_detection.rs | 4 +- crates/bevy_reflect/src/type_registry.rs | 58 +++++++++++++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 01284c067eb5f..0db970ba58831 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -833,7 +833,7 @@ impl<'a> MutUntyped<'a> { /// # let mut_untyped: MutUntyped = unimplemented!(); /// # let reflect_from_ptr: bevy_reflect::ReflectFromPtr = unimplemented!(); /// // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped` - /// mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) }); + /// mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_mut(ptr) }); /// ``` pub fn map_unchanged(self, f: impl FnOnce(PtrMut<'a>) -> &'a mut T) -> Mut<'a, T> { Mut { @@ -1168,7 +1168,7 @@ mod tests { let mut new = value.map_unchanged(|ptr| { // SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`. - let value = unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) }; + let value = unsafe { reflect_from_ptr.as_reflect_mut(ptr) }; value }); diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index aeaba79a2f6d5..b7ce14d8301c6 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -521,37 +521,63 @@ impl Deserialize<'a> + Reflect> FromType for ReflectDeserialize { /// let reflect_data = type_registry.get(std::any::TypeId::of::()).unwrap(); /// let reflect_from_ptr = reflect_data.data::().unwrap(); /// // SAFE: `value` is of type `Reflected`, which the `ReflectFromPtr` was created for -/// let value = unsafe { reflect_from_ptr.as_reflect_ptr(value) }; +/// let value = unsafe { reflect_from_ptr.as_reflect(value) }; /// /// assert_eq!(value.downcast_ref::().unwrap().0, "Hello world!"); /// ``` #[derive(Clone)] pub struct ReflectFromPtr { type_id: TypeId, - to_reflect: for<'a> unsafe fn(Ptr<'a>) -> &'a dyn Reflect, - to_reflect_mut: for<'a> unsafe fn(PtrMut<'a>) -> &'a mut dyn Reflect, + from_ptr: unsafe fn(Ptr) -> &dyn Reflect, + from_ptr_mut: unsafe fn(PtrMut) -> &mut dyn Reflect, } impl ReflectFromPtr { - /// Returns the [`TypeId`] that the [`ReflectFromPtr`] was constructed for + /// Returns the [`TypeId`] that the [`ReflectFromPtr`] was constructed for. pub fn type_id(&self) -> TypeId { self.type_id } + /// Convert `Ptr` into `&dyn Reflect`. + /// /// # Safety /// /// `val` must be a pointer to value of the type that the [`ReflectFromPtr`] was constructed for. /// This can be verified by checking that the type id returned by [`ReflectFromPtr::type_id`] is the expected one. - pub unsafe fn as_reflect_ptr<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect { - (self.to_reflect)(val) + pub unsafe fn as_reflect<'a>(&self, val: Ptr<'a>) -> &'a dyn Reflect { + (self.from_ptr)(val) } + /// Convert `PtrMut` into `&mut dyn Reflect`. + /// /// # Safety /// /// `val` must be a pointer to a value of the type that the [`ReflectFromPtr`] was constructed for /// This can be verified by checking that the type id returned by [`ReflectFromPtr::type_id`] is the expected one. - pub unsafe fn as_reflect_ptr_mut<'a>(&self, val: PtrMut<'a>) -> &'a mut dyn Reflect { - (self.to_reflect_mut)(val) + pub unsafe fn as_reflect_mut<'a>(&self, val: PtrMut<'a>) -> &'a mut dyn Reflect { + (self.from_ptr_mut)(val) + } + /// Get a function pointer to turn a `Ptr` into `&dyn Reflect` for + /// the type this [`ReflectFromPtr`] was constructed for. + /// + /// # Safety + /// + /// When calling the unsafe function returned by this method you must ensure that: + /// - The input `Ptr` points to the `Reflect` type this `ReflectFromPtr` + /// was constructed for. + pub fn from_ptr(&self) -> unsafe fn(Ptr) -> &dyn Reflect { + self.from_ptr + } + /// Get a function pointer to turn a `PtrMut` into `&mut dyn Reflect` for + /// the type this [`ReflectFromPtr`] was constructed for. + /// + /// # Safety + /// + /// When calling the unsafe function returned by this method you must ensure that: + /// - The input `PtrMut` points to the `Reflect` type this `ReflectFromPtr` + /// was constructed for. + pub fn from_ptr_mut(&self) -> unsafe fn(PtrMut) -> &mut dyn Reflect { + self.from_ptr_mut } } @@ -559,14 +585,14 @@ impl FromType for ReflectFromPtr { fn from_type() -> Self { ReflectFromPtr { type_id: std::any::TypeId::of::(), - to_reflect: |ptr| { - // SAFE: only called from `as_reflect`, where the `ptr` is guaranteed to be of type `T`, - // and `as_reflect_ptr`, where the caller promises to call it with type `T` + from_ptr: |ptr| { + // SAFETY: `from_ptr_mut` is either called in `ReflectFromPtr::as_reflect` + // or returned by `ReflectFromPtr::from_ptr`, both lay out the invariants + // required by `deref` unsafe { ptr.deref::() as &dyn Reflect } }, - to_reflect_mut: |ptr| { - // SAFE: only called from `as_reflect_mut`, where the `ptr` is guaranteed to be of type `T`, - // and `as_reflect_ptr_mut`, where the caller promises to call it with type `T` + from_ptr_mut: |ptr| { + // SAFETY: same as above, but foor `as_reflect_mut`, `from_ptr_mut` and `deref_mut`. unsafe { ptr.deref_mut::() as &mut dyn Reflect } }, } @@ -601,7 +627,7 @@ mod test { { let value = PtrMut::from(&mut value); // SAFETY: reflect_from_ptr was constructed for the correct type - let dyn_reflect = unsafe { reflect_from_ptr.as_reflect_ptr_mut(value) }; + let dyn_reflect = unsafe { reflect_from_ptr.as_reflect_mut(value) }; match dyn_reflect.reflect_mut() { bevy_reflect::ReflectMut::Struct(strukt) => { strukt.field_mut("a").unwrap().apply(&2.0f32); @@ -612,7 +638,7 @@ mod test { { // SAFETY: reflect_from_ptr was constructed for the correct type - let dyn_reflect = unsafe { reflect_from_ptr.as_reflect_ptr(Ptr::from(&value)) }; + let dyn_reflect = unsafe { reflect_from_ptr.as_reflect(Ptr::from(&value)) }; match dyn_reflect.reflect_ref() { bevy_reflect::ReflectRef::Struct(strukt) => { let a = strukt.field("a").unwrap().downcast_ref::().unwrap(); From 28060f3180743ce5bef4ada594c51347ccf6c406 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:55:24 +0100 Subject: [PATCH 20/72] invert face culling for negatively scaled gltf nodes (#8859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective according to [khronos](https://github.com/KhronosGroup/glTF/issues/1697), gltf nodes with inverted scales should invert the winding order of the mesh data. this is to allow negative scale to be used for mirrored geometry. ## Solution in the gltf loader, create a separate material with `cull_mode` set to `Face::Front` when the node scale is negative. note/alternatives: this applies for nodes where the scale is negative at gltf import time. that seems like enough for the mentioned use case of mirrored geometry. it doesn't help when scales dynamically go negative at runtime, but you can always set double sided in that case. i don't think there's any practical difference between using front-face culling and setting a clockwise winding order explicitly, but winding order is supported by wgpu so we could add the field to StandardMaterial/StandardMaterialKey and set it directly on the pipeline descriptor if there's a reason to. it wouldn't help with dynamic scale adjustments anyway, and would still require a separate material. fixes #4738, probably fixes #7901. --------- Co-authored-by: François --- crates/bevy_gltf/src/loader.rs | 35 +++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 64d17305b3e04..069e1e6ed513f 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -296,7 +296,7 @@ async fn load_gltf<'a, 'b, 'c>( let mut named_materials = HashMap::default(); // NOTE: materials must be loaded after textures because image load() calls will happen before load_with_settings, preventing is_srgb from being set properly for material in gltf.materials() { - let handle = load_material(&material, load_context); + let handle = load_material(&material, load_context, false); if let Some(name) = material.name() { named_materials.insert(name.to_string(), handle.clone()); } @@ -504,6 +504,7 @@ async fn load_gltf<'a, 'b, 'c>( &mut node_index_to_entity_map, &mut entity_to_skin_index_map, &mut active_camera_found, + &Transform::default(), ); if result.is_err() { err = Some(result); @@ -665,8 +666,12 @@ async fn load_image<'a, 'b>( } /// Loads a glTF material as a bevy [`StandardMaterial`] and returns it. -fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle { - let material_label = material_label(material); +fn load_material( + material: &Material, + load_context: &mut LoadContext, + is_scale_inverted: bool, +) -> Handle { + let material_label = material_label(material, is_scale_inverted); load_context.labeled_asset_scope(material_label, |load_context| { let pbr = material.pbr_metallic_roughness(); @@ -712,6 +717,8 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< double_sided: material.double_sided(), cull_mode: if material.double_sided() { None + } else if is_scale_inverted { + Some(Face::Front) } else { Some(Face::Back) }, @@ -734,10 +741,19 @@ fn load_node( node_index_to_entity_map: &mut HashMap, entity_to_skin_index_map: &mut HashMap, active_camera_found: &mut bool, + parent_transform: &Transform, ) -> Result<(), GltfError> { let transform = gltf_node.transform(); let mut gltf_error = None; let transform = Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())); + let world_transform = *parent_transform * transform; + // according to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#instantiation, + // if the determinant of the transform is negative we must invert the winding order of + // triangles in meshes on the node. + // instead we equivalently test if the global scale is inverted by checking if the number + // of negative scale factors is odd. if so we will assign a copy of the material with face + // culling inverted, rather than modifying the mesh data directly. + let is_scale_inverted = world_transform.scale.is_negative_bitmask().count_ones() & 1 == 1; let mut node = world_builder.spawn(SpatialBundle::from(transform)); node.insert(node_name(gltf_node)); @@ -812,13 +828,14 @@ fn load_node( // append primitives for primitive in mesh.primitives() { let material = primitive.material(); - let material_label = material_label(&material); + let material_label = material_label(&material, is_scale_inverted); // This will make sure we load the default material now since it would not have been // added when iterating over all the gltf materials (since the default material is // not explicitly listed in the gltf). + // It also ensures an inverted scale copy is instantiated if required. if !load_context.has_labeled_asset(&material_label) { - load_material(&material, load_context); + load_material(&material, load_context, is_scale_inverted); } let primitive_label = primitive_label(&mesh, &primitive); @@ -948,6 +965,7 @@ fn load_node( node_index_to_entity_map, entity_to_skin_index_map, active_camera_found, + &world_transform, ) { gltf_error = Some(err); return; @@ -990,9 +1008,12 @@ fn morph_targets_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { } /// Returns the label for the `material`. -fn material_label(material: &gltf::Material) -> String { +fn material_label(material: &gltf::Material, is_scale_inverted: bool) -> String { if let Some(index) = material.index() { - format!("Material{index}") + format!( + "Material{index}{}", + if is_scale_inverted { " (inverted)" } else { "" } + ) } else { "MaterialDefault".to_string() } From 68fa81e42dc0ef0927d9e59e6dd5e42a82ed35bd Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Mon, 18 Sep 2023 17:02:58 +0100 Subject: [PATCH 21/72] Round up for the batch size to improve par_iter performance (#9814) # Objective The default division for a `usize` rounds down which means the batch sizes were too small when the `max_size` isn't exactly divisible by the batch count. ## Solution Changing the division to round up fixes this which can dramatically improve performance when using `par_iter`. I created a small example to proof this out and measured some results. I don't know if it's worth committing this permanently so I left it out of the PR for now. ```rust use std::{thread, time::Duration}; use bevy::{ prelude::*, window::{PresentMode, WindowPlugin}, }; fn main() { App::new() .add_plugins((DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() }),)) .add_systems(Startup, spawn) .add_systems(Update, update_counts) .run(); } #[derive(Component, Default, Debug, Clone, Reflect)] pub struct Count(u32); fn spawn(mut commands: Commands) { // Worst case let tasks = bevy::tasks::available_parallelism() * 5 - 1; // Best case // let tasks = bevy::tasks::available_parallelism() * 5 + 1; for _ in 0..tasks { commands.spawn(Count(0)); } } // changing the bounds of the text will cause a recomputation fn update_counts(mut count_query: Query<&mut Count>) { count_query.par_iter_mut().for_each(|mut count| { count.0 += 1; thread::sleep(Duration::from_millis(10)) }); } ``` ## Results I ran this four times, with and without the change, with best case (should favour the old maths) and worst case (should favour the new maths) task numbers. ### Worst case Before the change the batches were 9 on each thread, plus the 5 remainder ran on one of the threads in addition. With the change its 10 on each thread apart from one which has 9. The results show a decrease from ~140ms to ~100ms which matches what you would expect from the maths (`10 * 10ms` vs `(9 + 4) * 10ms`). ![Screenshot from 2023-09-14 20-24-36](https://github.com/bevyengine/bevy/assets/1353401/82099ee4-83a8-47f4-bb6b-944f1e87a818) ### Best case Before the change the batches were 10 on each thread, plus the 1 remainder ran on one of the threads in addition. With the change its 11 on each thread apart from one which has 5. The results slightly favour the new change but are basically identical as the total time is determined by the worse case which is `11 * 10ms` for both tests. ![Screenshot from 2023-09-14 20-48-51](https://github.com/bevyengine/bevy/assets/1353401/4532211d-ab36-435b-b864-56af3370d90e) --- crates/bevy_ecs/src/query/par_iter.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 8d0aa79f42203..97165883694f8 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -11,7 +11,7 @@ use super::{QueryItem, QueryState, ReadOnlyWorldQuery, WorldQuery}; /// /// By default, this batch size is automatically determined by dividing /// the size of the largest matched archetype by the number -/// of threads. This attempts to minimize the overhead of scheduling +/// of threads (rounded up). This attempts to minimize the overhead of scheduling /// tasks onto multiple threads, but assumes each entity has roughly the /// same amount of work to be done, which may not hold true in every /// workload. @@ -197,7 +197,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { .max() .unwrap_or(0) }; - let batch_size = max_size / (thread_count * self.batching_strategy.batches_per_thread); + + let batches = thread_count * self.batching_strategy.batches_per_thread; + // Round up to the nearest batch size. + let batch_size = (max_size + batches - 1) / batches; batch_size.clamp( self.batching_strategy.batch_size_limits.start, self.batching_strategy.batch_size_limits.end, From 359e6c718d88e47343803bf64613ffb701e0a376 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Mon, 18 Sep 2023 18:06:42 +0200 Subject: [PATCH 22/72] Use single threaded executor for archetype benches (#9835) # Objective `no_archetype` benchmark group results were very noisy ## Solution Use the `SingeThreaded` executor. On my machine, this makes the `no_archetype` bench group 20 to 30 times faster. Meaning that most of the runtime was accounted by the multithreaded scheduler. ie: the benchmark was not testing system archetype update, but the overhead of multithreaded scheduling. With this change, the benchmark results are more meaningful. The add_archetypes function is also simplified. --- .../bevy_ecs/components/archetype_updates.rs | 77 +++++++------------ 1 file changed, 28 insertions(+), 49 deletions(-) diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index ec7703a0b1d49..b11c5b2b7576e 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -1,4 +1,9 @@ -use bevy_ecs::{component::Component, schedule::Schedule, world::World}; +use bevy_ecs::{ + component::Component, + prelude::EntityWorldMut, + schedule::{ExecutorKind, Schedule}, + world::World, +}; use criterion::{BenchmarkId, Criterion}; #[derive(Component)] @@ -8,6 +13,7 @@ fn setup(system_count: usize) -> (World, Schedule) { let mut world = World::new(); fn empty() {} let mut schedule = Schedule::default(); + schedule.set_executor_kind(ExecutorKind::SingleThreaded); for _ in 0..system_count { schedule.add_systems(empty); } @@ -15,58 +21,31 @@ fn setup(system_count: usize) -> (World, Schedule) { (world, schedule) } +fn insert_if_bit_enabled(entity: &mut EntityWorldMut, i: u16) { + if i & 1 << B != 0 { + entity.insert(A::(1.0)); + } +} /// create `count` entities with distinct archetypes fn add_archetypes(world: &mut World, count: u16) { for i in 0..count { let mut e = world.spawn_empty(); - if i & 1 << 0 != 0 { - e.insert(A::<0>(1.0)); - } - if i & 1 << 1 != 0 { - e.insert(A::<1>(1.0)); - } - if i & 1 << 2 != 0 { - e.insert(A::<2>(1.0)); - } - if i & 1 << 3 != 0 { - e.insert(A::<3>(1.0)); - } - if i & 1 << 4 != 0 { - e.insert(A::<4>(1.0)); - } - if i & 1 << 5 != 0 { - e.insert(A::<5>(1.0)); - } - if i & 1 << 6 != 0 { - e.insert(A::<6>(1.0)); - } - if i & 1 << 7 != 0 { - e.insert(A::<7>(1.0)); - } - if i & 1 << 8 != 0 { - e.insert(A::<8>(1.0)); - } - if i & 1 << 9 != 0 { - e.insert(A::<9>(1.0)); - } - if i & 1 << 10 != 0 { - e.insert(A::<10>(1.0)); - } - if i & 1 << 11 != 0 { - e.insert(A::<11>(1.0)); - } - if i & 1 << 12 != 0 { - e.insert(A::<12>(1.0)); - } - if i & 1 << 13 != 0 { - e.insert(A::<13>(1.0)); - } - if i & 1 << 14 != 0 { - e.insert(A::<14>(1.0)); - } - if i & 1 << 15 != 0 { - e.insert(A::<15>(1.0)); - } + insert_if_bit_enabled::<0>(&mut e, i); + insert_if_bit_enabled::<1>(&mut e, i); + insert_if_bit_enabled::<2>(&mut e, i); + insert_if_bit_enabled::<3>(&mut e, i); + insert_if_bit_enabled::<4>(&mut e, i); + insert_if_bit_enabled::<5>(&mut e, i); + insert_if_bit_enabled::<6>(&mut e, i); + insert_if_bit_enabled::<7>(&mut e, i); + insert_if_bit_enabled::<8>(&mut e, i); + insert_if_bit_enabled::<9>(&mut e, i); + insert_if_bit_enabled::<10>(&mut e, i); + insert_if_bit_enabled::<11>(&mut e, i); + insert_if_bit_enabled::<12>(&mut e, i); + insert_if_bit_enabled::<13>(&mut e, i); + insert_if_bit_enabled::<14>(&mut e, i); + insert_if_bit_enabled::<15>(&mut e, i); } } From 5e91e5f3cee1ab7905fadad3b7a08dc365329d46 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 19 Sep 2023 02:43:56 +0700 Subject: [PATCH 23/72] Improve doc formatting. (#9840) # Objective - Identifiers in docs should be marked up with backticks. ## Solution - Mark up more identifiers in the docs with backticks. --- crates/bevy_core_pipeline/src/fxaa/mod.rs | 2 +- crates/bevy_diagnostic/src/diagnostic.rs | 6 +- .../bevy_ecs/src/reflect/entity_commands.rs | 8 +- crates/bevy_ecs/src/storage/mod.rs | 2 +- crates/bevy_ecs/src/storage/sparse_set.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 73 ++++++++++--------- .../bevy_ecs/src/world/unsafe_world_cell.rs | 12 +-- crates/bevy_input/src/axis.rs | 2 +- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_render/src/extract_param.rs | 2 +- crates/bevy_tasks/src/task.rs | 2 +- crates/bevy_time/src/lib.rs | 2 +- crates/bevy_utils/src/label.rs | 4 +- 13 files changed, 60 insertions(+), 59 deletions(-) diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index f26e63a2d08c2..df37eaa1a8e76 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -55,7 +55,7 @@ pub struct Fxaa { /// Use lower sensitivity for a sharper, faster, result. /// Use higher sensitivity for a slower, smoother, result. - /// [Ultra](`Sensitivity::Ultra`) and [Extreme](`Sensitivity::Extreme`) + /// [`Ultra`](`Sensitivity::Ultra`) and [`Extreme`](`Sensitivity::Extreme`) /// settings can result in significant smearing and loss of detail. /// The minimum amount of local contrast required to apply algorithm. diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index 693642c5d6a39..70b8362b152f5 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -6,7 +6,7 @@ use std::{borrow::Cow, collections::VecDeque}; use crate::MAX_DIAGNOSTIC_NAME_WIDTH; -/// Unique identifier for a [Diagnostic] +/// Unique identifier for a [`Diagnostic`]. #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct DiagnosticId(pub Uuid); @@ -22,7 +22,7 @@ impl Default for DiagnosticId { } } -/// A single measurement of a [Diagnostic] +/// A single measurement of a [`Diagnostic`]. #[derive(Debug)] pub struct DiagnosticMeasurement { pub time: Instant, @@ -195,7 +195,7 @@ impl Diagnostic { } } -/// A collection of [Diagnostic]s +/// A collection of [`Diagnostic`]s. #[derive(Debug, Default, Resource)] pub struct DiagnosticsStore { // This uses a [`StableHashMap`] to ensure that the iteration order is deterministic between diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index da9374bbf8a06..ca61ebb85c866 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -209,7 +209,7 @@ fn insert_reflect( pub struct InsertReflect { /// The entity on which the component will be inserted. pub entity: Entity, - /// The reflect [Component](crate::component::Component) that will be added to the entity. + /// The reflect [`Component`](crate::component::Component) that will be added to the entity. pub component: Box, } @@ -228,7 +228,7 @@ pub struct InsertReflectWithRegistry> { /// The entity on which the component will be inserted. pub entity: Entity, pub _t: PhantomData, - /// The reflect [Component](crate::component::Component) that will be added to the entity. + /// The reflect [`Component`](crate::component::Component) that will be added to the entity. pub component: Box, } @@ -267,7 +267,7 @@ fn remove_reflect( pub struct RemoveReflect { /// The entity from which the component will be removed. pub entity: Entity, - /// The [Component](crate::component::Component) type name that will be used to remove a component + /// The [`Component`](crate::component::Component) type name that will be used to remove a component /// of the same type from the entity. pub component_type_name: Cow<'static, str>, } @@ -292,7 +292,7 @@ pub struct RemoveReflectWithRegistry> { /// The entity from which the component will be removed. pub entity: Entity, pub _t: PhantomData, - /// The [Component](crate::component::Component) type name that will be used to remove a component + /// The [`Component`](crate::component::Component) type name that will be used to remove a component /// of the same type from the entity. pub component_type_name: Cow<'static, str>, } diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2cabf44303040..72b1dced7daf8 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -29,7 +29,7 @@ pub use resource::*; pub use sparse_set::*; pub use table::*; -/// The raw data stores of a [World](crate::world::World) +/// The raw data stores of a [`World`](crate::world::World) #[derive(Default)] pub struct Storages { /// Backing storage for [`SparseSet`] components. diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 5d7a9d85d8b7a..9779f38d9185b 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -111,7 +111,7 @@ impl SparseArray { } } -/// A sparse data structure of [Components](crate::component::Component) +/// A sparse data structure of [`Component`](crate::component::Component)s. /// /// Designed for relatively fast insertions and deletions. #[derive(Debug)] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 918b976e7f841..8c55546427400 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -42,9 +42,9 @@ use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; /// Stores and exposes operations on [entities](Entity), [components](Component), resources, /// and their associated metadata. /// -/// Each [Entity] has a set of components. Each component can have up to one instance of each +/// Each [`Entity`] has a set of components. Each component can have up to one instance of each /// component type. Entity components can be created, updated, removed, and queried using a given -/// [World]. +/// [`World`]. /// /// For complex access patterns involving [`SystemParam`](crate::system::SystemParam), /// consider using [`SystemState`](crate::system::SystemState). @@ -94,7 +94,8 @@ impl Default for World { } impl World { - /// Creates a new empty [World] + /// Creates a new empty [`World`]. + /// /// # Panics /// /// If [`usize::MAX`] [`World`]s have been created. @@ -123,13 +124,13 @@ impl World { UnsafeWorldCell::new_readonly(self) } - /// Retrieves this world's [Entities] collection + /// Retrieves this world's [`Entities`] collection. #[inline] pub fn entities(&self) -> &Entities { &self.entities } - /// Retrieves this world's [Entities] collection mutably + /// Retrieves this world's [`Entities`] collection mutably. /// /// # Safety /// Mutable reference must not be used to put the [`Entities`] data @@ -139,25 +140,25 @@ impl World { &mut self.entities } - /// Retrieves this world's [Archetypes] collection + /// Retrieves this world's [`Archetypes`] collection. #[inline] pub fn archetypes(&self) -> &Archetypes { &self.archetypes } - /// Retrieves this world's [Components] collection + /// Retrieves this world's [`Components`] collection. #[inline] pub fn components(&self) -> &Components { &self.components } - /// Retrieves this world's [Storages] collection + /// Retrieves this world's [`Storages`] collection. #[inline] pub fn storages(&self) -> &Storages { &self.storages } - /// Retrieves this world's [Bundles] collection + /// Retrieves this world's [`Bundles`] collection. #[inline] pub fn bundles(&self) -> &Bundles { &self.bundles @@ -767,10 +768,10 @@ impl World { EntityWorldMut::new(self, entity, location) } - /// Spawns a batch of entities with the same component [Bundle] type. Takes a given [Bundle] - /// iterator and returns a corresponding [Entity] iterator. + /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given + /// [`Bundle`] iterator and returns a corresponding [`Entity`] iterator. /// This is more efficient than spawning entities and adding components to them individually, - /// but it is limited to spawning entities with the same [Bundle] type, whereas spawning + /// but it is limited to spawning entities with the same [`Bundle`] type, whereas spawning /// individually is more flexible. /// /// ``` @@ -797,8 +798,8 @@ impl World { SpawnBatchIter::new(self, iter.into_iter()) } - /// Retrieves a reference to the given `entity`'s [Component] of the given type. - /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// Retrieves a reference to the given `entity`'s [`Component`] of the given type. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. /// ``` /// use bevy_ecs::{component::Component, world::World}; /// @@ -818,8 +819,8 @@ impl World { self.get_entity(entity)?.get() } - /// Retrieves a mutable reference to the given `entity`'s [Component] of the given type. - /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. /// ``` /// use bevy_ecs::{component::Component, world::World}; /// @@ -844,7 +845,7 @@ impl World { } /// Despawns the given `entity`, if it exists. This will also remove all of the entity's - /// [Component]s. Returns `true` if the `entity` is successfully despawned and `false` if + /// [`Component`]s. Returns `true` if the `entity` is successfully despawned and `false` if /// the `entity` does not exist. /// ``` /// use bevy_ecs::{component::Component, world::World}; @@ -952,7 +953,7 @@ impl World { /// To iterate over entities in a deterministic order, /// sort the results of the query using the desired component as a key. /// Note that this requires fetching the whole result set from the query - /// and allocation of a [Vec] to store it. + /// and allocation of a [`Vec`] to store it. /// /// ``` /// use bevy_ecs::{component::Component, entity::Entity, world::World}; @@ -1122,7 +1123,7 @@ impl World { }); } - /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. + /// Removes the resource of a given type and returns it, if it exists. Otherwise returns `None`. #[inline] pub fn remove_resource(&mut self) -> Option { let component_id = self.components.get_resource_id(TypeId::of::())?; @@ -1345,7 +1346,7 @@ impl World { } /// Gets a reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None]. + /// Otherwise returns `None`. /// /// # Panics /// This function will panic if it isn't called from the same thread that the resource was inserted from. @@ -1358,7 +1359,7 @@ impl World { } /// Gets a mutable reference to the non-send resource of the given type, if it exists. - /// Otherwise returns [None] + /// Otherwise returns `None`. /// /// # Panics /// This function will panic if it isn't called from the same thread that the resource was inserted from. @@ -1390,12 +1391,12 @@ impl World { Some(resource.id()) } - /// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given - /// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists). + /// For a given batch of ([`Entity`], [`Bundle`]) pairs, either spawns each [`Entity`] with the given + /// bundle (if the entity does not exist), or inserts the [`Bundle`] (if the entity already exists). /// This is faster than doing equivalent operations one-by-one. - /// Returns [Ok] if all entities were successfully inserted into or spawned. Otherwise it returns an [Err] + /// Returns `Ok` if all entities were successfully inserted into or spawned. Otherwise it returns an `Err` /// with a list of entities that could not be spawned or inserted into. A "spawn or insert" operation can - /// only fail if an [Entity] is passed in with an "invalid generation" that conflicts with an existing [Entity]. + /// only fail if an [`Entity`] is passed in with an "invalid generation" that conflicts with an existing [`Entity`]. /// /// # Note /// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`World::spawn_batch`]. @@ -1702,9 +1703,9 @@ impl World { component_id } - /// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype). + /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, - /// such as inserting a [Component]. + /// such as inserting a [`Component`]. pub(crate) fn flush(&mut self) { let empty_archetype = self.archetypes.empty_mut(); let table = &mut self.storages.tables[empty_archetype.table_id()]; @@ -1807,7 +1808,7 @@ impl World { /// Clears all resources in this [`World`]. /// - /// **Note:** Any resource fetch to this [World] will fail unless they are re-initialized, + /// **Note:** Any resource fetch to this [`World`] will fail unless they are re-initialized, /// including engine-internal resources that are only initialized on app/world construction. /// /// This can easily cause systems expecting certain resources to immediately start panicking. @@ -1893,7 +1894,7 @@ impl World { } } - /// Removes the resource of a given type, if it exists. Otherwise returns [None]. + /// Removes the resource of a given type, if it exists. Otherwise returns `None`. /// /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** @@ -1905,7 +1906,7 @@ impl World { Some(()) } - /// Removes the resource of a given type, if it exists. Otherwise returns [None]. + /// Removes the resource of a given type, if it exists. Otherwise returns `None`. /// /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** @@ -1920,8 +1921,8 @@ impl World { Some(()) } - /// Retrieves an immutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. - /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// Retrieves an immutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. /// /// **You should prefer to use the typed API [`World::get_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** @@ -1940,8 +1941,8 @@ impl World { } } - /// Retrieves a mutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. - /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. /// /// **You should prefer to use the typed API [`World::get_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** @@ -2103,11 +2104,11 @@ unsafe impl Send for World {} unsafe impl Sync for World {} /// Creates an instance of the type this trait is implemented for -/// using data from the supplied [World]. +/// using data from the supplied [`World`]. /// /// This can be helpful for complex initialization or context-aware defaults. pub trait FromWorld { - /// Creates `Self` using data from the given [World] + /// Creates `Self` using data from the given [`World`]. fn from_world(world: &mut World) -> Self; } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 7ab49ef407c27..85dcbc5554cdf 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -200,7 +200,7 @@ impl<'w> UnsafeWorldCell<'w> { unsafe { self.world_metadata() }.id() } - /// Retrieves this world's [Entities] collection + /// Retrieves this world's [`Entities`] collection. #[inline] pub fn entities(self) -> &'w Entities { // SAFETY: @@ -208,7 +208,7 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.entities } - /// Retrieves this world's [Archetypes] collection + /// Retrieves this world's [`Archetypes`] collection. #[inline] pub fn archetypes(self) -> &'w Archetypes { // SAFETY: @@ -216,7 +216,7 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.archetypes } - /// Retrieves this world's [Components] collection + /// Retrieves this world's [`Components`] collection. #[inline] pub fn components(self) -> &'w Components { // SAFETY: @@ -224,7 +224,7 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.components } - /// Retrieves this world's [Bundles] collection + /// Retrieves this world's [`Bundles`] collection. #[inline] pub fn bundles(self) -> &'w Bundles { // SAFETY: @@ -838,8 +838,8 @@ impl<'w> UnsafeEntityCell<'w> { } } - /// Retrieves a mutable untyped reference to the given `entity`'s [Component] of the given [`ComponentId`]. - /// Returns [None] if the `entity` does not have a [Component] of the given type. + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. /// /// **You should prefer to use the typed API [`UnsafeEntityCell::get_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index 5fc3b787ae061..21e4b7165b444 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -40,7 +40,7 @@ where /// /// If the `input_device`: /// - was present before, the position data is updated, and the old value is returned. - /// - wasn't present before, [None] is returned. + /// - wasn't present before, `None` is returned. pub fn set(&mut self, input_device: T, position_data: f32) -> Option { self.axis_data.insert(input_device, position_data) } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index a12649b616bb9..e62ac1ae857d5 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -67,7 +67,7 @@ pub struct Wireframe; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] #[reflect(Resource)] pub struct WireframeConfig { - /// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered. + /// Whether to show wireframes for all meshes. If `false`, only meshes with a [`Wireframe`] component will be rendered. pub global: bool, } diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 57a1267ce5953..e6b1ea1802fd3 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -19,7 +19,7 @@ use std::ops::{Deref, DerefMut}; /// ## Context /// /// [`ExtractSchedule`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the -/// render world. The render world drives rendering each frame (generally to a [Window]). +/// render world. The render world drives rendering each frame (generally to a `Window`). /// This design is used to allow performing calculations related to rendering a prior frame at the same /// time as the next frame is simulated, which increases throughput (FPS). /// diff --git a/crates/bevy_tasks/src/task.rs b/crates/bevy_tasks/src/task.rs index 360909c2e9671..4ea2ed8dfea99 100644 --- a/crates/bevy_tasks/src/task.rs +++ b/crates/bevy_tasks/src/task.rs @@ -9,7 +9,7 @@ use std::{ /// Tasks are also futures themselves and yield the output of the spawned future. /// /// When a task is dropped, its gets canceled and won't be polled again. To cancel a task a bit -/// more gracefully and wait until it stops running, use the [`cancel()`][Task::cancel()] method. +/// more gracefully and wait until it stops running, use the [`Task::cancel()`] method. /// /// Tasks that panic get immediately canceled. Awaiting a canceled task also causes a panic. /// Wraps `async_executor::Task` diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 8850572b35961..f9b26d480fd2a 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -35,7 +35,7 @@ use crate::fixed_timestep::run_fixed_update_schedule; pub struct TimePlugin; #[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)] -/// Updates the elapsed time. Any system that interacts with [Time] component should run after +/// Updates the elapsed time. Any system that interacts with [`Time`] component should run after /// this. pub struct TimeSystem; diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 6eff79c0bb389..29b2d1b5dee37 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -73,8 +73,8 @@ macro_rules! define_boxed_label { ($label_trait_name:ident) => { /// A strongly-typed label. pub trait $label_trait_name: 'static + Send + Sync + ::std::fmt::Debug { - /// Return's the [TypeId] of this label, or the the ID of the - /// wrappped label type for `Box` /// From 444245106ebdc04cab59660ca5a3922113794fdd Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 19 Sep 2023 04:42:04 +0700 Subject: [PATCH 24/72] docs: Improve some `ComponentId` doc cross-linking. (#9839) # Objective - When reading API docs and seeing a reference to `ComponentId`, it isn't immediately clear how to get one from your `Component`. It could be made to be more clear. ## Solution - Improve cross-linking of docs about `ComponentId` --- crates/bevy_ecs/src/component.rs | 37 ++++++++++++++++++++++++++++---- crates/bevy_ecs/src/world/mod.rs | 5 +++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 4bdc7572e02e0..d3b4970f21f6a 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -282,6 +282,10 @@ impl ComponentInfo { /// A `ComponentId` is tightly coupled to its parent `World`. Attempting to use a `ComponentId` from /// one `World` to access the metadata of a `Component` in a different `World` is undefined behavior /// and must not be attempted. +/// +/// Given a type `T` which implements [`Component`], the `ComponentId` for `T` can be retrieved +/// from a `World` using [`World::component_id()`] or via [`Components::component_id()`]. Access +/// to the `ComponentId` for a [`Resource`] is available via [`Components::resource_id()`]. #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ComponentId(usize); @@ -442,6 +446,11 @@ impl Components { /// Initializes a component of type `T` with this instance. /// If a component of this type has already been initialized, this will return /// the ID of the pre-existing component. + /// + /// # See also + /// + /// * [`Components::component_id()`] + /// * [`Components::init_component_with_descriptor()`] #[inline] pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { let type_id = TypeId::of::(); @@ -463,6 +472,11 @@ impl Components { /// /// If this method is called multiple times with identical descriptors, a distinct `ComponentId` /// will be created for each one. + /// + /// # See also + /// + /// * [`Components::component_id()`] + /// * [`Components::init_component()`] pub fn init_component_with_descriptor( &mut self, storages: &mut Storages, @@ -525,7 +539,7 @@ impl Components { self.components.get_unchecked(id.0) } - /// Type-erased equivalent of [`Components::component_id`]. + /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { self.indices.get(&type_id).map(|index| ComponentId(*index)) @@ -538,7 +552,7 @@ impl Components { /// instance. /// /// Returns [`None`] if the `Component` type has not - /// yet been initialized using [`Components::init_component`]. + /// yet been initialized using [`Components::init_component()`]. /// /// ```rust /// use bevy_ecs::prelude::*; @@ -552,12 +566,18 @@ impl Components { /// /// assert_eq!(component_a_id, world.components().component_id::().unwrap()) /// ``` + /// + /// # See also + /// + /// * [`Components::get_id()`] + /// * [`Components::resource_id()`] + /// * [`World::component_id()`] #[inline] pub fn component_id(&self) -> Option { self.get_id(TypeId::of::()) } - /// Type-erased equivalent of [`Components::resource_id`]. + /// Type-erased equivalent of [`Components::resource_id()`]. #[inline] pub fn get_resource_id(&self, type_id: TypeId) -> Option { self.resource_indices @@ -572,7 +592,7 @@ impl Components { /// instance. /// /// Returns [`None`] if the `Resource` type has not - /// yet been initialized using [`Components::init_resource`]. + /// yet been initialized using [`Components::init_resource()`]. /// /// ```rust /// use bevy_ecs::prelude::*; @@ -586,6 +606,11 @@ impl Components { /// /// assert_eq!(resource_a_id, world.components().resource_id::().unwrap()) /// ``` + /// + /// # See also + /// + /// * [`Components::component_id()`] + /// * [`Components::get_resource_id()`] #[inline] pub fn resource_id(&self) -> Option { self.get_resource_id(TypeId::of::()) @@ -594,6 +619,10 @@ impl Components { /// Initializes a [`Resource`] of type `T` with this instance. /// If a resource of this type has already been initialized, this will return /// the ID of the pre-existing resource. + /// + /// # See also + /// + /// * [`Components::resource_id()`] #[inline] pub fn init_resource(&mut self) -> ComponentId { // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8c55546427400..be43fcce3d770 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -219,6 +219,11 @@ impl World { /// /// assert_eq!(component_a_id, world.component_id::().unwrap()) /// ``` + /// + /// # See also + /// + /// * [`Components::component_id()`] + /// * [`Components::get_id()`] #[inline] pub fn component_id(&self) -> Option { self.components.component_id::() From 07f61a11463f667c79794a3b498b47d76b00f566 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 18 Sep 2023 23:55:43 +0100 Subject: [PATCH 25/72] Round UI coordinates after scaling (#9784) # Objective Fixes #9754 ## Solution Don't round UI coordinates until they've been multiplied by the inverse scale factor. --- crates/bevy_ui/src/layout/mod.rs | 79 +++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 80cd0c9f86092..3084604c6f069 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -317,24 +317,23 @@ pub fn ui_layout_system( ) { if let Ok((mut node, mut transform)) = node_transform_query.get_mut(entity) { let layout = ui_surface.get_layout(entity).unwrap(); - let layout_size = Vec2::new(layout.size.width, layout.size.height); - let layout_location = Vec2::new(layout.location.x, layout.location.y); + let layout_size = + inverse_target_scale_factor * Vec2::new(layout.size.width, layout.size.height); + let layout_location = + inverse_target_scale_factor * Vec2::new(layout.location.x, layout.location.y); absolute_location += layout_location; - let rounded_location = round_layout_coords(layout_location); let rounded_size = round_layout_coords(absolute_location + layout_size) - round_layout_coords(absolute_location); - - let new_size = inverse_target_scale_factor * rounded_size; - let new_position = - inverse_target_scale_factor * rounded_location + 0.5 * (new_size - parent_size); + let rounded_location = + round_layout_coords(layout_location) + 0.5 * (rounded_size - parent_size); // only trigger change detection when the new values are different - if node.calculated_size != new_size { - node.calculated_size = new_size; + if node.calculated_size != rounded_size { + node.calculated_size = rounded_size; } - if transform.translation.truncate() != new_position { - transform.translation = new_position.extend(0.); + if transform.translation.truncate() != rounded_location { + transform.translation = rounded_location.extend(0.); } if let Ok(children) = children_query.get(entity) { for &child_uinode in children { @@ -344,7 +343,7 @@ pub fn ui_layout_system( node_transform_query, children_query, inverse_target_scale_factor, - new_size, + rounded_size, absolute_location, ); } @@ -400,11 +399,13 @@ mod tests { use crate::ui_layout_system; use crate::ContentSize; use crate::UiSurface; + use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; use bevy_ecs::schedule::Schedule; use bevy_ecs::world::World; use bevy_hierarchy::despawn_with_children_recursive; use bevy_hierarchy::BuildWorldChildren; + use bevy_hierarchy::Children; use bevy_math::vec2; use bevy_math::Vec2; use bevy_utils::prelude::default; @@ -714,4 +715,58 @@ mod tests { assert_eq!(layout.size.width, 0.); assert_eq!(layout.size.height, 0.); } + + #[test] + fn ui_rounding_test() { + let (mut world, mut ui_schedule) = setup_ui_test_world(); + + let parent = world + .spawn(NodeBundle { + style: Style { + display: Display::Grid, + grid_template_columns: RepeatedGridTrack::min_content(2), + margin: UiRect::all(Val::Px(4.0)), + ..Default::default() + }, + ..Default::default() + }) + .with_children(|commands| { + for _ in 0..2 { + commands.spawn(NodeBundle { + style: Style { + display: Display::Grid, + width: Val::Px(160.), + height: Val::Px(160.), + ..Default::default() + }, + ..Default::default() + }); + } + }) + .id(); + + let children = world + .entity(parent) + .get::() + .unwrap() + .iter() + .copied() + .collect::>(); + + for r in [2, 3, 5, 7, 11, 13, 17, 19, 21, 23, 29, 31].map(|n| (n as f64).recip()) { + let mut s = r; + while s <= 5. { + world.resource_mut::().0 = s; + ui_schedule.run(&mut world); + let width_sum: f32 = children + .iter() + .map(|child| world.get::(*child).unwrap().calculated_size.x) + .sum(); + let parent_width = world.get::(parent).unwrap().calculated_size.x; + assert!((width_sum - parent_width).abs() < 0.001); + assert!((width_sum - 320.).abs() <= 1.); + s += r; + } + } + } } From d5d355ae1ff20b6bfff2337507b5e8b0e132345d Mon Sep 17 00:00:00 2001 From: Joseph <21144246+JoJoJet@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:35:22 -0700 Subject: [PATCH 26/72] Fix the `clippy::explicit_iter_loop` lint (#9834) # Objective Replace instances of ```rust for x in collection.iter{_mut}() { ``` with ```rust for x in &{mut} collection { ``` This also changes CI to no longer suppress this lint. Note that since this lint only shows up when using clippy in pedantic mode, it was probably unnecessary to suppress this lint in the first place. --- crates/bevy_app/src/app.rs | 2 +- crates/bevy_asset/macros/src/lib.rs | 2 +- crates/bevy_asset/src/lib.rs | 4 ++-- crates/bevy_asset/src/processor/process.rs | 2 +- crates/bevy_ecs/macros/src/lib.rs | 2 +- crates/bevy_ecs/src/schedule/schedule.rs | 16 ++++++++-------- crates/bevy_pbr/src/light.rs | 14 +++++++------- .../bevy_reflect_derive/src/impls/enums.rs | 2 +- crates/bevy_render/macros/src/as_bind_group.rs | 2 +- crates/bevy_render/src/render_graph/graph.rs | 4 ++-- crates/bevy_render/src/render_resource/shader.rs | 2 +- crates/bevy_render/src/view/visibility/mod.rs | 2 +- crates/bevy_sprite/src/texture_atlas_builder.rs | 2 +- crates/bevy_ui/src/accessibility.rs | 2 +- crates/bevy_ui/src/focus.rs | 2 +- crates/bevy_ui/src/layout/debug.rs | 2 +- crates/bevy_ui/src/layout/mod.rs | 2 +- crates/bevy_ui/src/widget/text.rs | 8 ++++---- crates/bevy_winit/src/accessibility.rs | 2 +- tools/ci/src/main.rs | 3 +-- 20 files changed, 38 insertions(+), 39 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4773ad80a54db..566de2686c9ca 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -243,7 +243,7 @@ impl App { let _bevy_main_update_span = info_span!("main app").entered(); self.world.run_schedule(&*self.main_schedule_label); } - for (_label, sub_app) in self.sub_apps.iter_mut() { + for (_label, sub_app) in &mut self.sub_apps { #[cfg(feature = "trace")] let _sub_app_span = info_span!("sub app", name = ?_label).entered(); sub_app.extract(&mut self.world); diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index 0dee6e24ab727..580587248649d 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -43,7 +43,7 @@ fn derive_dependency_visitor_internal( ) -> Result { let mut field_visitors = Vec::new(); if let Data::Struct(data_struct) = &ast.data { - for field in data_struct.fields.iter() { + for field in &data_struct.fields { if field .attrs .iter() diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 0dc9d0589b63e..fcf4b09f19ba3 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -233,7 +233,7 @@ impl VisitAssetDependencies for Option { impl VisitAssetDependencies for Vec> { fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) { - for dependency in self.iter() { + for dependency in self { visit(dependency.id().untyped()); } } @@ -241,7 +241,7 @@ impl VisitAssetDependencies for Vec> { impl VisitAssetDependencies for Vec { fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)) { - for dependency in self.iter() { + for dependency in self { visit(dependency.id()); } } diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index d0e9964c05c63..e63dae81ff8e4 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -241,7 +241,7 @@ impl<'a> ProcessContext<'a> { true, ) .await?; - for (path, full_hash) in loaded_asset.loader_dependencies.iter() { + for (path, full_hash) in &loaded_asset.loader_dependencies { self.new_processed_info .process_dependencies .push(ProcessDependencyInfo { diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 5bfbb3cfee1e6..28d63c6911b14 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -37,7 +37,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let mut field_kind = Vec::with_capacity(named_fields.len()); - for field in named_fields.iter() { + for field in named_fields { for attr in field .attrs .iter() diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index a70df83c011bc..79e4e99916893 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -95,7 +95,7 @@ impl Schedules { let _all_span = info_span!("check stored schedule ticks").entered(); // label used when trace feature is enabled #[allow(unused_variables)] - for (label, schedule) in self.inner.iter_mut() { + for (label, schedule) in &mut self.inner { #[cfg(feature = "trace")] let name = format!("{label:?}"); #[cfg(feature = "trace")] @@ -106,7 +106,7 @@ impl Schedules { /// Applies the provided [`ScheduleBuildSettings`] to all schedules. pub fn configure_schedules(&mut self, schedule_build_settings: ScheduleBuildSettings) { - for (_, schedule) in self.inner.iter_mut() { + for (_, schedule) in &mut self.inner { schedule.set_build_settings(schedule_build_settings.clone()); } } @@ -297,13 +297,13 @@ impl Schedule { } for conditions in &mut self.executable.system_conditions { - for system in conditions.iter_mut() { + for system in conditions { system.check_change_tick(change_tick); } } for conditions in &mut self.executable.set_conditions { - for system in conditions.iter_mut() { + for system in conditions { system.check_change_tick(change_tick); } } @@ -973,7 +973,7 @@ impl ScheduleGraph { // have to do it like this to preserve transitivity let mut dependency_flattened = self.dependency.graph.clone(); let mut temp = Vec::new(); - for (&set, systems) in set_systems.iter() { + for (&set, systems) in set_systems { if systems.is_empty() { for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) { for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) { @@ -1456,7 +1456,7 @@ impl ScheduleGraph { dep_results: &CheckGraphResults, hier_results_connected: &HashSet<(NodeId, NodeId)>, ) -> Result<(), ScheduleBuildError> { - for &(a, b) in dep_results.connected.iter() { + for &(a, b) in &dep_results.connected { if hier_results_connected.contains(&(a, b)) || hier_results_connected.contains(&(b, a)) { let name_a = self.get_node_name(&a); @@ -1474,7 +1474,7 @@ impl ScheduleGraph { set_system_bitsets: &HashMap, ) -> Result<(), ScheduleBuildError> { // check that there is no ordering between system sets that intersect - for (a, b) in dep_results_connected.iter() { + for (a, b) in dep_results_connected { if !(a.is_set() && b.is_set()) { continue; } @@ -1497,7 +1497,7 @@ impl ScheduleGraph { &self, set_systems: &HashMap>, ) -> Result<(), ScheduleBuildError> { - for (&id, systems) in set_systems.iter() { + for (&id, systems) in set_systems { let set = &self.system_sets[id.index()]; if set.is_system_type() { let instances = systems.len(); diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index e52f3fe806882..d68036a830848 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -422,7 +422,7 @@ pub fn update_directional_light_cascades( }) .collect::>(); - for (transform, directional_light, cascades_config, mut cascades) in lights.iter_mut() { + for (transform, directional_light, cascades_config, mut cascades) in &mut lights { if !directional_light.shadows_enabled { continue; } @@ -1882,7 +1882,7 @@ pub fn update_spot_light_frusta( Or<(Changed, Changed)>, >, ) { - for (entity, transform, spot_light, mut frustum) in views.iter_mut() { + for (entity, transform, spot_light, mut frustum) in &mut views { // The frusta are used for culling meshes to the light for shadow mapping // so if shadow mapping is disabled for this light, then the frusta are // not needed. @@ -1969,7 +1969,7 @@ pub fn check_light_mesh_visibility( { // Re-use already allocated entries where possible. let mut views_to_remove = Vec::new(); - for (view, cascade_view_entities) in visible_entities.entities.iter_mut() { + for (view, cascade_view_entities) in &mut visible_entities.entities { match frusta.frusta.get(view) { Some(view_frusta) => { cascade_view_entities.resize(view_frusta.len(), Default::default()); @@ -1980,7 +1980,7 @@ pub fn check_light_mesh_visibility( None => views_to_remove.push(*view), }; } - for (view, frusta) in frusta.frusta.iter() { + for (view, frusta) in &frusta.frusta { visible_entities .entities .entry(*view) @@ -2017,7 +2017,7 @@ pub fn check_light_mesh_visibility( // If we have an aabb and transform, do frustum culling if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { - for (view, view_frusta) in frusta.frusta.iter() { + for (view, view_frusta) in &frusta.frusta { let view_visible_entities = visible_entities .entities .get_mut(view) @@ -2050,7 +2050,7 @@ pub fn check_light_mesh_visibility( } } - for (_, cascade_view_entities) in visible_entities.entities.iter_mut() { + for (_, cascade_view_entities) in &mut visible_entities.entities { cascade_view_entities.iter_mut().for_each(shrink_entities); } } @@ -2153,7 +2153,7 @@ pub fn check_light_mesh_visibility( maybe_entity_mask, maybe_aabb, maybe_transform, - ) in visible_entity_query.iter_mut() + ) in &mut visible_entity_query { if !inherited_visibility.get() { continue; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 41a47c2552c15..19b6bebbd78ec 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -348,7 +348,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden ) -> Vec { let mut constructor_argument = Vec::new(); let mut reflect_idx = 0; - for field in fields.iter() { + for field in fields { if field.attrs.ignore.is_ignored() { // Ignored field continue; diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index df137e95d8b55..5602efd8939dc 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -116,7 +116,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { }; // Read field-level attributes - for field in fields.iter() { + for field in fields { // Search ahead for texture attributes so we can use them with any // corresponding sampler attribute. let mut tex_attrs = None; diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index e0bdc9283ce1d..03d06b9a4f0ec 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -150,7 +150,7 @@ impl RenderGraph { if let Some(node_state) = self.nodes.remove(&id) { // Remove all edges from other nodes to this one. Note that as we're removing this // node, we don't need to remove its input edges - for input_edge in node_state.edges.input_edges().iter() { + for input_edge in node_state.edges.input_edges() { match input_edge { Edge::SlotEdge { output_node, .. } | Edge::NodeEdge { @@ -165,7 +165,7 @@ impl RenderGraph { } // Remove all edges from this node to other nodes. Note that as we're removing this // node, we don't need to remove its output edges - for output_edge in node_state.edges.output_edges().iter() { + for output_edge in node_state.edges.output_edges() { match output_edge { Edge::SlotEdge { output_node: _, diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 3993e0693a44b..acf46932b463f 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -271,7 +271,7 @@ impl AssetLoader for ShaderLoader { }; // collect file dependencies - for import in shader.imports.iter() { + for import in &shader.imports { if let ShaderImport::AssetPath(asset_path) = import { // TODO: should we just allow this handle to be dropped? let _handle: Handle = load_context.load(asset_path); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index f55563751ff78..e004d9976d991 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -475,7 +475,7 @@ pub fn check_visibility( }, ); - for cell in thread_queues.iter_mut() { + for cell in &mut thread_queues { visible_entities.entities.append(cell.get_mut()); } } diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 085cda173d724..82ef8bd69584f 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -225,7 +225,7 @@ impl TextureAtlasBuilder { let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len()); let mut texture_ids = HashMap::default(); - for (image_id, (_, packed_location)) in rect_placements.packed_locations().iter() { + for (image_id, (_, packed_location)) in rect_placements.packed_locations() { let texture = textures.get(*image_id).unwrap(); let min = Vec2::new(packed_location.x() as f32, packed_location.y() as f32); let max = min diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index a1203a766d0f9..74a8ae58963ef 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -21,7 +21,7 @@ use bevy_transform::prelude::GlobalTransform; fn calc_name(texts: &Query<&Text>, children: &Children) -> Option> { let mut name = None; - for child in children.iter() { + for child in children { if let Ok(text) = texts.get(*child) { let values = text .sections diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index c5211cd14850e..91454adeeaada 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -155,7 +155,7 @@ pub fn ui_focus_system( let mouse_released = mouse_button_input.just_released(MouseButton::Left) || touches_input.any_just_released(); if mouse_released { - for node in node_query.iter_mut() { + for node in &mut node_query { if let Some(mut interaction) = node.interaction { if *interaction == Interaction::Pressed { *interaction = Interaction::None; diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index cc04b6d31033c..28798bb7b3a12 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -12,7 +12,7 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { .iter() .map(|(entity, node)| (*node, *entity)) .collect(); - for (&entity, &node) in ui_surface.window_nodes.iter() { + for (&entity, &node) in &ui_surface.window_nodes { let mut out = String::new(); print_node( ui_surface, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 3084604c6f069..1ea53f3b3ac5c 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -279,7 +279,7 @@ pub fn ui_layout_system( ui_surface.try_remove_measure(entity); } - for (entity, mut content_size) in measure_query.iter_mut() { + for (entity, mut content_size) in &mut measure_query { if let Some(measure_func) = content_size.measure_func.take() { ui_surface.update_measure(entity, measure_func); } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 0f96057eafa9a..9a400ed209147 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -127,7 +127,7 @@ pub fn measure_text_system( #[allow(clippy::float_cmp)] if *last_scale_factor == scale_factor { // scale factor unchanged, only create new measure funcs for modified text - for (text, content_size, text_flags) in text_query.iter_mut() { + for (text, content_size, text_flags) in &mut text_query { if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() { create_text_measure(&fonts, scale_factor, text, content_size, text_flags); } @@ -136,7 +136,7 @@ pub fn measure_text_system( // scale factor changed, create new measure funcs for all text *last_scale_factor = scale_factor; - for (text, content_size, text_flags) in text_query.iter_mut() { + for (text, content_size, text_flags) in &mut text_query { create_text_measure(&fonts, scale_factor, text, content_size, text_flags); } } @@ -232,7 +232,7 @@ pub fn text_system( let inverse_scale_factor = scale_factor.recip(); if *last_scale_factor == scale_factor { // Scale factor unchanged, only recompute text for modified text nodes - for (node, text, text_layout_info, text_flags) in text_query.iter_mut() { + for (node, text, text_layout_info, text_flags) in &mut text_query { if node.is_changed() || text_flags.needs_recompute { queue_text( &fonts, @@ -255,7 +255,7 @@ pub fn text_system( // Scale factor changed, recompute text for all text nodes *last_scale_factor = scale_factor; - for (node, text, text_layout_info, text_flags) in text_query.iter_mut() { + for (node, text, text_layout_info, text_flags) in &mut text_query { queue_text( &fonts, &mut text_pipeline, diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 49a4c0d859c6d..f22291af8d533 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -131,7 +131,7 @@ fn update_accessibility_nodes( root_children.push(entity.to_node_id()); } if let Some(children) = children { - for child in children.iter() { + for child in children { if node_entities.get(*child).is_ok() { node.push_child(child.to_node_id()); } diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 423ad68fd64ac..703c1e062bd95 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -17,9 +17,8 @@ bitflags! { } } -const CLIPPY_FLAGS: [&str; 8] = [ +const CLIPPY_FLAGS: [&str; 7] = [ "-Aclippy::type_complexity", - "-Aclippy::explicit_iter_loop", "-Wclippy::doc_markdown", "-Wclippy::redundant_else", "-Wclippy::match_same_arms", From d3f612c37693048962a88cd5fa1e5b963b47fc39 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 18 Sep 2023 22:57:21 -0700 Subject: [PATCH 27/72] Fix ambiguities in transform example (#9845) # Objective Fix these ``` -- rotate_cube and move_cube conflict on: ["bevy_transform::components::transform::Transform", "transform::CubeState"] -- rotate_cube and scale_down_sphere_proportional_to_cube_travel_distance conflict on: ["bevy_transform::components::transform::Transform", "transform::CubeState"] -- move_cube and scale_down_sphere_proportional_to_cube_travel_distance conflict on: ["bevy_transform::components::transform::Transform", "transform::CubeState"] ``` The three systems in this example depend on the results of the others. This leads to minor but detectable differences in output between runs by automated screenshot diffing depending on the order of the schedule. We don't necessarily need to be able to do this for **every** example, but I think this is a case where fixing it is easy / maybe the right thing to do anyway. ## Solution Chain the three systems --- examples/transforms/transform.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/transforms/transform.rs b/examples/transforms/transform.rs index 0267f1eae6eb3..38bad31c4fa30 100644 --- a/examples/transforms/transform.rs +++ b/examples/transforms/transform.rs @@ -31,7 +31,8 @@ fn main() { move_cube, rotate_cube, scale_down_sphere_proportional_to_cube_travel_distance, - ), + ) + .chain(), ) .run(); } From 73e06e33daedf4aa549e420dbdb05685071972d4 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 18 Sep 2023 22:57:25 -0700 Subject: [PATCH 28/72] Use a seeded rng for custom_skinned_mesh example (#9846) # Objective Make the output of this example repeatable so it can be utilized by automated screenshot diffing. ## Solution - Use a seeded RNG for the random colors - Offset the meshes slightly in z so they don't intersect each other at the extents of their animations. --- examples/animation/custom_skinned_mesh.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/animation/custom_skinned_mesh.rs b/examples/animation/custom_skinned_mesh.rs index e3f8bfd16b26e..62918473dfb9e 100644 --- a/examples/animation/custom_skinned_mesh.rs +++ b/examples/animation/custom_skinned_mesh.rs @@ -11,7 +11,7 @@ use bevy::{ Indices, PrimitiveTopology, VertexAttributeValues, }, }; -use rand::Rng; +use rand::{rngs::StdRng, Rng, SeedableRng}; fn main() { App::new() @@ -116,13 +116,16 @@ fn setup( ]))); let mesh = meshes.add(mesh); + + let mut rng = StdRng::seed_from_u64(42); + for i in -5..5 { // Create joint entities let joint_0 = commands .spawn(TransformBundle::from(Transform::from_xyz( i as f32 * 1.5, 0.0, - 0.0, + i as f32 * 0.1, ))) .id(); let joint_1 = commands @@ -141,9 +144,9 @@ fn setup( mesh: mesh.clone(), material: materials.add( Color::rgb( - rand::thread_rng().gen_range(0.0..1.0), - rand::thread_rng().gen_range(0.0..1.0), - rand::thread_rng().gen_range(0.0..1.0), + rng.gen_range(0.0..1.0), + rng.gen_range(0.0..1.0), + rng.gen_range(0.0..1.0), ) .into(), ), From 401b2e77f3d4e8fe39f21ef51961d2a5bbedf9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 19 Sep 2023 07:57:54 +0200 Subject: [PATCH 29/72] renderer init: create a detached task only on wasm, block otherwise (#9830) # Objective - When initializing the renderer, Bevy currently create a detached task - This is needed on wasm but not on native ## Solution - Don't create a detached task on native but block on the future --- crates/bevy_render/src/lib.rs | 68 ++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2f36d3a1cf8a6..cc62f3fcead84 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -251,41 +251,43 @@ impl Plugin for RenderPlugin { let primary_window = system_state.get(&app.world).get_single().ok().cloned(); let settings = self.wgpu_settings.clone(); - bevy_tasks::IoTaskPool::get() - .spawn_local(async move { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler: settings.dx12_shader_compiler.clone(), - }); - let surface = primary_window.map(|wrapper| unsafe { - // SAFETY: Plugins should be set up on the main thread. - let handle = wrapper.get_handle(); - instance - .create_surface(&handle) - .expect("Failed to create wgpu surface") - }); - - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: settings.power_preference, - compatible_surface: surface.as_ref(), - ..Default::default() - }; - - let (device, queue, adapter_info, render_adapter) = - renderer::initialize_renderer( - &instance, - &settings, - &request_adapter_options, - ) + let async_renderer = async move { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + dx12_shader_compiler: settings.dx12_shader_compiler.clone(), + }); + let surface = primary_window.map(|wrapper| unsafe { + // SAFETY: Plugins should be set up on the main thread. + let handle = wrapper.get_handle(); + instance + .create_surface(&handle) + .expect("Failed to create wgpu surface") + }); + + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: settings.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }; + + let (device, queue, adapter_info, render_adapter) = + renderer::initialize_renderer(&instance, &settings, &request_adapter_options) .await; - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_renderer_resources_inner = - future_renderer_resources_wrapper.lock().unwrap(); - *future_renderer_resources_inner = - Some((device, queue, adapter_info, render_adapter, instance)); - }) + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + let mut future_renderer_resources_inner = + future_renderer_resources_wrapper.lock().unwrap(); + *future_renderer_resources_inner = + Some((device, queue, adapter_info, render_adapter, instance)); + }; + // In wasm, spawn a task and detach it for execution + #[cfg(target_arch = "wasm32")] + bevy_tasks::IoTaskPool::get() + .spawn_local(async_renderer) .detach(); + // Otherwise, just block for it to complete + #[cfg(not(target_arch = "wasm32"))] + futures_lite::future::block_on(async_renderer); app.init_resource::(); From b995827013304e51cf4d324c99e6c2df3edbedaf Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 19 Sep 2023 16:14:46 +0100 Subject: [PATCH 30/72] Have a separate implicit viewport node per root node + make viewport node `Display::Grid` (#9637) # Objective Make `bevy_ui` "root" nodes more intuitive to use/style by: - Removing the implicit flexbox styling (such as stretch alignment) that is applied to them, and replacing it with more intuitive CSS Grid styling (notably with stretch alignment disabled in both axes). - Making root nodes layout independently of each other. Instead of there being a single implicit "viewport" node that all root nodes are children of, there is now an implicit "viewport" node *per root node*. And layout of each tree is computed separately. ## Solution - Remove the global implicit viewport node, and instead create an implicit viewport node for each user-specified root node. - Keep track of both the user-specified root nodes and the implicit viewport nodes in a separate `Vec`. - Use the window's size as the `available_space` parameter to `Taffy.compute_layout` rather than setting it on the implicit viewport node (and set the viewport to `height: 100%; width: 100%` to make this "just work"). --- ## Changelog - Bevy UI now lays out root nodes independently of each other in separate layout contexts. - The implicit viewport node (which contains each user-specified root node) is now `Display::Grid` with `align_items` and `justify_items` both set to `Start`. ## Migration Guide - Bevy UI now lays out root nodes independently of each other in separate layout contexts. If you were relying on your root nodes being able to affect each other's layouts, then you may need to wrap them in a single root node. - The implicit viewport node (which contains each user-specified root node) is now `Display::Grid` with `align_items` and `justify_items` both set to `Start`. You may need to add `height: Val::Percent(100.)` to your root nodes if you were previously relying on being implicitly set. --- crates/bevy_ui/src/layout/debug.rs | 22 +++-- crates/bevy_ui/src/layout/mod.rs | 111 +++++++++++++---------- examples/ecs/apply_deferred.rs | 1 + examples/ecs/state.rs | 1 + examples/games/game_menu.rs | 6 ++ examples/stress_tests/many_buttons.rs | 1 + examples/stress_tests/many_glyphs.rs | 2 +- examples/ui/borders.rs | 3 +- examples/ui/button.rs | 1 + examples/ui/display_and_visibility.rs | 6 +- examples/ui/overflow.rs | 3 +- examples/ui/relative_cursor_position.rs | 1 + examples/ui/size_constraints.rs | 3 +- examples/ui/text_wrap_debug.rs | 3 +- examples/ui/transparency_ui.rs | 1 + examples/ui/ui.rs | 1 + examples/ui/ui_texture_atlas.rs | 3 +- examples/ui/z_index.rs | 1 + examples/window/scale_factor_override.rs | 1 + 19 files changed, 105 insertions(+), 66 deletions(-) diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 28798bb7b3a12..c47b2ca8e802b 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -12,17 +12,19 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { .iter() .map(|(entity, node)| (*node, *entity)) .collect(); - for (&entity, &node) in &ui_surface.window_nodes { + for (&entity, roots) in &ui_surface.window_roots { let mut out = String::new(); - print_node( - ui_surface, - &taffy_to_entity, - entity, - node, - false, - String::new(), - &mut out, - ); + for root in roots { + print_node( + ui_surface, + &taffy_to_entity, + entity, + root.implicit_viewport_node, + false, + String::new(), + &mut out, + ); + } bevy_log::info!("Layout tree for window entity: {entity:?}\n{out}"); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1ea53f3b3ac5c..90603577b1d65 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -15,10 +15,10 @@ use bevy_hierarchy::{Children, Parent}; use bevy_log::warn; use bevy_math::Vec2; use bevy_transform::components::Transform; -use bevy_utils::HashMap; +use bevy_utils::{default, HashMap}; use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged}; use std::fmt; -use taffy::{prelude::Size, style_helpers::TaffyMaxContent, Taffy}; +use taffy::Taffy; pub struct LayoutContext { pub scale_factor: f64, @@ -39,10 +39,18 @@ impl LayoutContext { } } +#[derive(Debug, Clone, PartialEq, Eq)] +struct RootNodePair { + // The implicit "viewport" node created by Bevy + implicit_viewport_node: taffy::node::Node, + // The root (parentless) node specified by the user + user_root_node: taffy::node::Node, +} + #[derive(Resource)] pub struct UiSurface { entity_to_taffy: HashMap, - window_nodes: HashMap, + window_roots: HashMap>, taffy: Taffy, } @@ -57,7 +65,7 @@ impl fmt::Debug for UiSurface { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("UiSurface") .field("entity_to_taffy", &self.entity_to_taffy) - .field("window_nodes", &self.window_nodes) + .field("window_nodes", &self.window_roots) .finish() } } @@ -68,7 +76,7 @@ impl Default for UiSurface { taffy.disable_rounding(); Self { entity_to_taffy: Default::default(), - window_nodes: Default::default(), + window_roots: Default::default(), taffy, } } @@ -132,50 +140,64 @@ without UI components as a child of an entity with UI components, results may be } } - /// Retrieve or insert the root layout node and update its size to match the size of the window. - pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) { - let taffy = &mut self.taffy; - let node = self - .window_nodes - .entry(window) - .or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap()); - - taffy - .set_style( - *node, - taffy::style::Style { - size: taffy::geometry::Size { - width: taffy::style::Dimension::Points( - window_resolution.physical_width() as f32 - ), - height: taffy::style::Dimension::Points( - window_resolution.physical_height() as f32, - ), - }, - ..Default::default() - }, - ) - .unwrap(); - } - /// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout. pub fn set_window_children( &mut self, - parent_window: Entity, + window_id: Entity, children: impl Iterator, ) { - let taffy_node = self.window_nodes.get(&parent_window).unwrap(); - let child_nodes = children - .map(|e| *self.entity_to_taffy.get(&e).unwrap()) - .collect::>(); - self.taffy.set_children(*taffy_node, &child_nodes).unwrap(); + let viewport_style = taffy::style::Style { + display: taffy::style::Display::Grid, + // Note: Taffy percentages are floats ranging from 0.0 to 1.0. + // So this is setting width:100% and height:100% + size: taffy::geometry::Size { + width: taffy::style::Dimension::Percent(1.0), + height: taffy::style::Dimension::Percent(1.0), + }, + align_items: Some(taffy::style::AlignItems::Start), + justify_items: Some(taffy::style::JustifyItems::Start), + ..default() + }; + + let existing_roots = self.window_roots.entry(window_id).or_default(); + let mut new_roots = Vec::new(); + for entity in children { + let node = *self.entity_to_taffy.get(&entity).unwrap(); + let root_node = existing_roots + .iter() + .find(|n| n.user_root_node == node) + .cloned() + .unwrap_or_else(|| RootNodePair { + implicit_viewport_node: self + .taffy + .new_with_children(viewport_style.clone(), &[node]) + .unwrap(), + user_root_node: node, + }); + new_roots.push(root_node); + } + + // Cleanup the implicit root nodes of any user root nodes that have been removed + for old_root in existing_roots { + if !new_roots.contains(old_root) { + self.taffy.remove(old_root.implicit_viewport_node).unwrap(); + } + } + + self.window_roots.insert(window_id, new_roots); } /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_window_layouts(&mut self) { - for window_node in self.window_nodes.values() { + pub fn compute_window_layout(&mut self, window: Entity, window_resolution: &WindowResolution) { + let available_space = taffy::geometry::Size { + width: taffy::style::AvailableSpace::Definite(window_resolution.physical_width() as f32), + height: taffy::style::AvailableSpace::Definite( + window_resolution.physical_height() as f32 + ), + }; + for root_nodes in self.window_roots.entry(window).or_default() { self.taffy - .compute_layout(*window_node, Size::MAX_CONTENT) + .compute_layout(root_nodes.implicit_viewport_node, available_space) .unwrap(); } } @@ -251,11 +273,6 @@ pub fn ui_layout_system( .read() .any(|resized_window| resized_window.window == primary_window_entity); - // update window root nodes - for (entity, window) in windows.iter() { - ui_surface.update_window(entity, &window.resolution); - } - let scale_factor = logical_to_physical_factor * ui_scale.0; let layout_context = LayoutContext::new(scale_factor, physical_size); @@ -302,7 +319,9 @@ pub fn ui_layout_system( } // compute layouts - ui_surface.compute_window_layouts(); + for (entity, window) in windows.iter() { + ui_surface.compute_window_layout(entity, &window.resolution); + } let inverse_target_scale_factor = 1. / scale_factor; diff --git a/examples/ecs/apply_deferred.rs b/examples/ecs/apply_deferred.rs index 5942ca9262e49..fb0020e6f08ed 100644 --- a/examples/ecs/apply_deferred.rs +++ b/examples/ecs/apply_deferred.rs @@ -70,6 +70,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, flex_direction: FlexDirection::Column, diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 31c0346206bdb..c4603196f461b 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -54,6 +54,7 @@ fn setup_menu(mut commands: Commands) { style: Style { // center button width: Val::Percent(100.), + height: Val::Percent(100.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index cbd300d83c0d3..c6ee0a6e52e2a 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -84,6 +84,7 @@ mod splash { align_items: AlignItems::Center, justify_content: JustifyContent::Center, width: Val::Percent(100.0), + height: Val::Percent(100.0), ..default() }, ..default() @@ -151,6 +152,7 @@ mod game { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), // center children align_items: AlignItems::Center, justify_content: JustifyContent::Center, @@ -421,6 +423,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -546,6 +549,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -611,6 +615,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() @@ -714,6 +719,7 @@ mod menu { NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index 1095076e16fc6..d7c8533c37a07 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -120,6 +120,7 @@ fn setup_flex(mut commands: Commands, asset_server: Res, args: Res< justify_content: JustifyContent::Center, align_items: AlignItems::Center, width: Val::Percent(100.), + height: Val::Percent(100.), ..default() }, ..default() diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index 0b095c170bee7..398ac3cb64f9b 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -54,7 +54,7 @@ fn setup(mut commands: Commands) { commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.), + width: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/ui/borders.rs b/examples/ui/borders.rs index bf6cac544ce75..9e6cf4e2b3289 100644 --- a/examples/ui/borders.rs +++ b/examples/ui/borders.rs @@ -14,8 +14,9 @@ fn setup(mut commands: Commands) { let root = commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.0), margin: UiRect::all(Val::Px(25.0)), + align_self: AlignSelf::Stretch, + justify_self: JustifySelf::Stretch, flex_wrap: FlexWrap::Wrap, justify_content: JustifyContent::FlexStart, align_items: AlignItems::FlexStart, diff --git a/examples/ui/button.rs b/examples/ui/button.rs index fef128f6f8c57..27ab4a19a12d5 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -58,6 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/ui/display_and_visibility.rs b/examples/ui/display_and_visibility.rs index 370ad677b85f9..55810a84b4fc6 100644 --- a/examples/ui/display_and_visibility.rs +++ b/examples/ui/display_and_visibility.rs @@ -82,8 +82,9 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); commands.spawn(NodeBundle { style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), flex_direction: FlexDirection::Column, - flex_basis: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::SpaceEvenly, ..Default::default() @@ -189,9 +190,6 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec) { commands .spawn(NodeBundle { style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, - width: Val::Percent(100.), ..Default::default() }, background_color: Color::ANTIQUE_WHITE.into(), diff --git a/examples/ui/relative_cursor_position.rs b/examples/ui/relative_cursor_position.rs index 079637d8c4d67..84b9fa2fe19d4 100644 --- a/examples/ui/relative_cursor_position.rs +++ b/examples/ui/relative_cursor_position.rs @@ -19,6 +19,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, flex_direction: FlexDirection::Column, diff --git a/examples/ui/size_constraints.rs b/examples/ui/size_constraints.rs index 5f0892c14afa8..72daae989dee3 100644 --- a/examples/ui/size_constraints.rs +++ b/examples/ui/size_constraints.rs @@ -51,7 +51,8 @@ fn setup(mut commands: Commands, asset_server: Res) { commands .spawn(NodeBundle { style: Style { - flex_basis: Val::Percent(100.0), + width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..Default::default() diff --git a/examples/ui/text_wrap_debug.rs b/examples/ui/text_wrap_debug.rs index 374799ec8189a..a7d01e3ab8e1e 100644 --- a/examples/ui/text_wrap_debug.rs +++ b/examples/ui/text_wrap_debug.rs @@ -24,8 +24,9 @@ fn spawn(mut commands: Commands, asset_server: Res) { let root = commands .spawn(NodeBundle { style: Style { - flex_direction: FlexDirection::Column, width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::Column, ..Default::default() }, background_color: Color::BLACK.into(), diff --git a/examples/ui/transparency_ui.rs b/examples/ui/transparency_ui.rs index 2a6fe7f9b93d4..2aabe43dc85d0 100644 --- a/examples/ui/transparency_ui.rs +++ b/examples/ui/transparency_ui.rs @@ -20,6 +20,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::SpaceAround, ..default() diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 2dea49e6eac6a..3eabcdd15604d 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -29,6 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::SpaceBetween, ..default() }, diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index d6b16ce38af0f..8609288bf790a 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -40,8 +40,9 @@ fn setup( commands .spawn(NodeBundle { style: Style { - flex_direction: FlexDirection::Column, width: Val::Percent(100.0), + height: Val::Percent(100.0), + flex_direction: FlexDirection::Column, justify_content: JustifyContent::Center, align_items: AlignItems::Center, row_gap: Val::Px(text_style.font_size * 2.), diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index f6eae5fe5c124..0580391018846 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -23,6 +23,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.), + height: Val::Percent(100.), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() diff --git a/examples/window/scale_factor_override.rs b/examples/window/scale_factor_override.rs index a465432c000ac..bfa74cfe116dd 100644 --- a/examples/window/scale_factor_override.rs +++ b/examples/window/scale_factor_override.rs @@ -28,6 +28,7 @@ fn setup(mut commands: Commands) { .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), + height: Val::Percent(100.0), justify_content: JustifyContent::SpaceBetween, ..default() }, From e4b368721dbd6ce68a07f5f96ee417303fea81ed Mon Sep 17 00:00:00 2001 From: Trashtalk217 Date: Tue, 19 Sep 2023 22:17:05 +0200 Subject: [PATCH 31/72] One Shot Systems (#8963) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'm adopting this ~~child~~ PR. # Objective - Working with exclusive world access is not always easy: in many cases, a standard system or three is more ergonomic to write, and more modularly maintainable. - For small, one-off tasks (commonly handled with scripting), running an event-reader system incurs a small but flat overhead cost and muddies the schedule. - Certain forms of logic (e.g. turn-based games) want very fine-grained linear and/or branching control over logic. - SystemState is not automatically cached, and so performance can suffer and change detection breaks. - Fixes https://github.com/bevyengine/bevy/issues/2192. - Partial workaround for https://github.com/bevyengine/bevy/issues/279. ## Solution - Adds a SystemRegistry resource to the World, which stores initialized systems keyed by their SystemSet. - Allows users to call world.run_system(my_system) and commands.run_system(my_system), without re-initializing or losing state (essential for change detection). - Add a Callback type to enable convenient use of dynamic one shot systems and reduce the mental overhead of working with Box. - Allow users to run systems based on their SystemSet, enabling more complex user-made abstractions. ## Future work - Parameterized one-shot systems would improve reusability and bring them closer to events and commands. The API could be something like run_system_with_input(my_system, my_input) and use the In SystemParam. - We should evaluate the unification of commands and one-shot systems since they are two different ways to run logic on demand over a World. ### Prior attempts - https://github.com/bevyengine/bevy/pull/2234 - https://github.com/bevyengine/bevy/pull/2417 - https://github.com/bevyengine/bevy/pull/4090 - https://github.com/bevyengine/bevy/pull/7999 This PR continues the work done in https://github.com/bevyengine/bevy/pull/7999. --------- Co-authored-by: Alice Cecile Co-authored-by: Federico Rinaldi Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Aevyrie Co-authored-by: Alejandro Pascual Pozo Co-authored-by: Rob Parrett Co-authored-by: François Co-authored-by: Dmytro Banin Co-authored-by: James Liu --- Cargo.toml | 10 + crates/bevy_ecs/src/schedule/set.rs | 1 + crates/bevy_ecs/src/system/commands/mod.rs | 11 +- crates/bevy_ecs/src/system/function_system.rs | 6 +- crates/bevy_ecs/src/system/mod.rs | 2 + crates/bevy_ecs/src/system/system.rs | 98 +++++- crates/bevy_ecs/src/system/system_registry.rs | 301 ++++++++++++++++++ crates/bevy_utils/src/label.rs | 2 +- examples/README.md | 1 + examples/ecs/one_shot_systems.rs | 59 ++++ 10 files changed, 474 insertions(+), 17 deletions(-) create mode 100644 crates/bevy_ecs/src/system/system_registry.rs create mode 100644 examples/ecs/one_shot_systems.rs diff --git a/Cargo.toml b/Cargo.toml index b6967a880d1e6..d06f251e497ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1284,6 +1284,16 @@ description = "Shows how to iterate over combinations of query results" category = "ECS (Entity Component System)" wasm = true +[[example]] +name = "one_shot_systems" +path = "examples/ecs/one_shot_systems.rs" + +[package.metadata.example.one_shot_systems] +name = "One Shot Systems" +description = "Shows how to flexibly run systems without scheduling them" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "parallel_query" path = "examples/ecs/parallel_query.rs" diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index aa7ef806155f8..769507ff48835 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -81,6 +81,7 @@ impl Hash for SystemTypeSet { // all systems of a given type are the same } } + impl Clone for SystemTypeSet { fn clone(&self) -> Self { *self diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 8eb1881273f1b..512dadff88017 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -5,6 +5,7 @@ use crate::{ self as bevy_ecs, bundle::Bundle, entity::{Entities, Entity}, + system::{RunSystem, SystemId}, world::{EntityWorldMut, FromWorld, World}, }; use bevy_ecs_macros::SystemParam; @@ -517,11 +518,19 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(RemoveResource::::new()); } + /// Runs the system corresponding to the given [`SystemId`]. + /// Systems are ran in an exclusive and single threaded way. + /// Running slow systems can become a bottleneck. + /// + /// Calls [`World::run_system`](crate::system::World::run_system). + pub fn run_system(&mut self, id: SystemId) { + self.queue.push(RunSystem::new(id)); + } + /// Pushes a generic [`Command`] to the command queue. /// /// `command` can be a built-in command, custom struct that implements [`Command`] or a closure /// that takes [`&mut World`](World) as an argument. - /// /// # Example /// /// ``` diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 2ef46c971863a..aa11b1a03f3cb 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -72,8 +72,10 @@ impl SystemMeta { // (to avoid the need for unwrapping to retrieve SystemMeta) /// Holds on to persistent state required to drive [`SystemParam`] for a [`System`]. /// -/// This is a very powerful and convenient tool for working with exclusive world access, +/// This is a powerful and convenient tool for working with exclusive world access, /// allowing you to fetch data from the [`World`] as if you were running a [`System`]. +/// However, simply calling `world::run_system(my_system)` using a [`World::run_system`](crate::system::World::run_system) +/// can be significantly simpler and ensures that change detection and command flushing work as expected. /// /// Borrow-checking is handled for you, allowing you to mutably access multiple compatible system parameters at once, /// and arbitrary system parameters (like [`EventWriter`](crate::event::EventWriter)) can be conveniently fetched. @@ -89,6 +91,8 @@ impl SystemMeta { /// - [`Local`](crate::system::Local) variables that hold state /// - [`EventReader`](crate::event::EventReader) system parameters, which rely on a [`Local`](crate::system::Local) to track which events have been seen /// +/// Note that this is automatically handled for you when using a [`World::run_system`](crate::system::World::run_system). +/// /// # Example /// /// Basic usage: diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index e07445aa6500c..ac73d676c9d6e 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -112,6 +112,7 @@ mod query; #[allow(clippy::module_inception)] mod system; mod system_param; +mod system_registry; use std::borrow::Cow; @@ -124,6 +125,7 @@ pub use function_system::*; pub use query::*; pub use system::*; pub use system_param::*; +pub use system_registry::*; use crate::world::World; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a4e1b0625efa1..0c8c839f95e42 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -165,6 +165,30 @@ impl Debug for dyn System { /// This function is not an efficient method of running systems and its meant to be used as a utility /// for testing and/or diagnostics. /// +/// Systems called through [`run_system_once`](crate::system::RunSystemOnce::run_system_once) do not hold onto any state, +/// as they are created and destroyed every time [`run_system_once`](crate::system::RunSystemOnce::run_system_once) is called. +/// Practically, this means that [`Local`](crate::system::Local) variables are +/// reset on every run and change detection does not work. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_ecs::system::RunSystemOnce; +/// #[derive(Resource, Default)] +/// struct Counter(u8); +/// +/// fn increment(mut counter: Local) { +/// counter.0 += 1; +/// println!("{}", counter.0); +/// } +/// +/// let mut world = World::default(); +/// world.run_system_once(increment); // prints 1 +/// world.run_system_once(increment); // still prints 1 +/// ``` +/// +/// If you do need systems to hold onto state between runs, use the [`World::run_system`](crate::system::World::run_system) +/// and run the system by their [`SystemId`](crate::system::SystemId). +/// /// # Usage /// Typically, to test a system, or to extract specific diagnostics information from a world, /// you'd need a [`Schedule`](crate::schedule::Schedule) to run the system. This can create redundant boilerplate code @@ -180,9 +204,9 @@ impl Debug for dyn System { /// This usage is helpful when trying to test systems or functions that operate on [`Commands`](crate::system::Commands): /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystem; +/// # use bevy_ecs::system::RunSystemOnce; /// let mut world = World::default(); -/// let entity = world.run_system(|mut commands: Commands| { +/// let entity = world.run_system_once(|mut commands: Commands| { /// commands.spawn_empty().id() /// }); /// # assert!(world.get_entity(entity).is_some()); @@ -193,7 +217,7 @@ impl Debug for dyn System { /// This usage is helpful when trying to run an arbitrary query on a world for testing or debugging purposes: /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystem; +/// # use bevy_ecs::system::RunSystemOnce; /// /// #[derive(Component)] /// struct T(usize); @@ -202,7 +226,7 @@ impl Debug for dyn System { /// world.spawn(T(0)); /// world.spawn(T(1)); /// world.spawn(T(1)); -/// let count = world.run_system(|query: Query<&T>| { +/// let count = world.run_system_once(|query: Query<&T>| { /// query.iter().filter(|t| t.0 == 1).count() /// }); /// @@ -213,7 +237,7 @@ impl Debug for dyn System { /// /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::RunSystem; +/// # use bevy_ecs::system::RunSystemOnce; /// /// #[derive(Component)] /// struct T(usize); @@ -226,26 +250,26 @@ impl Debug for dyn System { /// world.spawn(T(0)); /// world.spawn(T(1)); /// world.spawn(T(1)); -/// let count = world.run_system(count); +/// let count = world.run_system_once(count); /// /// # assert_eq!(count, 2); /// ``` -pub trait RunSystem: Sized { +pub trait RunSystemOnce: Sized { /// Runs a system and applies its deferred parameters. - fn run_system, Out, Marker>(self, system: T) -> Out { - self.run_system_with((), system) + fn run_system_once, Out, Marker>(self, system: T) -> Out { + self.run_system_once_with((), system) } /// Runs a system with given input and applies its deferred parameters. - fn run_system_with, In, Out, Marker>( + fn run_system_once_with, In, Out, Marker>( self, input: In, system: T, ) -> Out; } -impl RunSystem for &mut World { - fn run_system_with, In, Out, Marker>( +impl RunSystemOnce for &mut World { + fn run_system_once_with, In, Out, Marker>( self, input: In, system: T, @@ -261,10 +285,11 @@ impl RunSystem for &mut World { #[cfg(test)] mod tests { use super::*; + use crate as bevy_ecs; use crate::prelude::*; #[test] - fn run_system() { + fn run_system_once() { struct T(usize); impl Resource for T {} @@ -275,8 +300,53 @@ mod tests { } let mut world = World::default(); - let n = world.run_system_with(1, system); + let n = world.run_system_once_with(1, system); assert_eq!(n, 2); assert_eq!(world.resource::().0, 1); } + + #[derive(Resource, Default, PartialEq, Debug)] + struct Counter(u8); + + #[allow(dead_code)] + fn count_up(mut counter: ResMut) { + counter.0 += 1; + } + + #[test] + fn run_two_systems() { + let mut world = World::new(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + world.run_system_once(count_up); + assert_eq!(*world.resource::(), Counter(1)); + world.run_system_once(count_up); + assert_eq!(*world.resource::(), Counter(2)); + } + + #[allow(dead_code)] + fn spawn_entity(mut commands: Commands) { + commands.spawn_empty(); + } + + #[test] + fn command_processing() { + let mut world = World::new(); + assert_eq!(world.entities.len(), 0); + world.run_system_once(spawn_entity); + assert_eq!(world.entities.len(), 1); + } + + #[test] + fn non_send_resources() { + fn non_send_count_down(mut ns: NonSendMut) { + ns.0 -= 1; + } + + let mut world = World::new(); + world.insert_non_send_resource(Counter(10)); + assert_eq!(*world.non_send_resource::(), Counter(10)); + world.run_system_once(non_send_count_down); + assert_eq!(*world.non_send_resource::(), Counter(9)); + } } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs new file mode 100644 index 0000000000000..f0522606d978f --- /dev/null +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -0,0 +1,301 @@ +use crate::entity::Entity; +use crate::system::{BoxedSystem, Command, IntoSystem}; +use crate::world::World; +use crate::{self as bevy_ecs}; +use bevy_ecs_macros::Component; + +/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. +#[derive(Component)] +struct RegisteredSystem { + initialized: bool, + system: BoxedSystem, +} + +/// A system that has been removed from the registry. +/// It contains the system and whether or not it has been initialized. +/// +/// This struct is returned by [`World::remove_system`]. +pub struct RemovedSystem { + initialized: bool, + system: BoxedSystem, +} + +impl RemovedSystem { + /// Is the system initialized? + /// A system is initialized the first time it's ran. + pub fn initialized(&self) -> bool { + self.initialized + } + + /// The system removed from the storage. + pub fn system(self) -> BoxedSystem { + self.system + } +} + +/// An identifier for a registered system. +/// +/// These are opaque identifiers, keyed to a specific [`World`], +/// and are created via [`World::register_system`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SystemId(Entity); + +impl World { + /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. + /// + /// It's possible to register the same systems more than once, they'll be stored seperately. + /// + /// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule), + /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system. + /// This allows for running systems in a pushed-based fashion. + /// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases + /// due to its better performance and abillity to run non-conflicting systems simultaneously. + pub fn register_system + 'static>( + &mut self, + system: S, + ) -> SystemId { + SystemId( + self.spawn(RegisteredSystem { + initialized: false, + system: Box::new(IntoSystem::into_system(system)), + }) + .id(), + ) + } + + /// Removes a registered system and returns the system, if it exists. + /// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors. + /// Re-adding the removed system will register it on a new [`SystemId`]. + /// + /// If no system corresponds to the given [`SystemId`], this method returns an error. + /// Systems are also not allowed to remove themselves, this returns an error too. + pub fn remove_system(&mut self, id: SystemId) -> Result { + match self.get_entity_mut(id.0) { + Some(mut entity) => { + let registered_system = entity + .take::() + .ok_or(RegisteredSystemError::SelfRemove(id))?; + entity.despawn(); + Ok(RemovedSystem { + initialized: registered_system.initialized, + system: registered_system.system, + }) + } + None => Err(RegisteredSystemError::SystemIdNotRegistered(id)), + } + } + + /// Run stored systems by their [`SystemId`]. + /// Before running a system, it must first be registered. + /// The method [`World::register_system`] stores a given system and returns a [`SystemId`]. + /// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once), + /// because it keeps local state between calls and change detection works correctly. + /// + /// # Limitations + /// + /// - Stored systems cannot be chained: they can neither have an [`In`](crate::system::In) nor return any values. + /// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands). + /// - Exclusive systems cannot be used. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource, Default)] + /// struct Counter(u8); + /// + /// fn increment(mut counter: Local) { + /// counter.0 += 1; + /// println!("{}", counter.0); + /// } + /// + /// let mut world = World::default(); + /// let counter_one = world.register_system(increment); + /// let counter_two = world.register_system(increment); + /// world.run_system(counter_one); // -> 1 + /// world.run_system(counter_one); // -> 2 + /// world.run_system(counter_two); // -> 1 + /// ``` + /// + /// Change detection: + /// + /// ```rust + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource, Default)] + /// struct ChangeDetector; + /// + /// let mut world = World::default(); + /// world.init_resource::(); + /// let detector = world.register_system(|change_detector: ResMut| { + /// if change_detector.is_changed() { + /// println!("Something happened!"); + /// } else { + /// println!("Nothing happened."); + /// } + /// }); + /// + /// // Resources are changed when they are first added + /// let _ = world.run_system(detector); // -> Something happened! + /// let _ = world.run_system(detector); // -> Nothing happened. + /// world.resource_mut::().set_changed(); + /// let _ = world.run_system(detector); // -> Something happened! + /// ``` + pub fn run_system(&mut self, id: SystemId) -> Result<(), RegisteredSystemError> { + // lookup + let mut entity = self + .get_entity_mut(id.0) + .ok_or(RegisteredSystemError::SystemIdNotRegistered(id))?; + + // take ownership of system trait object + let RegisteredSystem { + mut initialized, + mut system, + } = entity + .take::() + .ok_or(RegisteredSystemError::Recursive(id))?; + + // run the system + if !initialized { + system.initialize(self); + initialized = true; + } + system.run((), self); + system.apply_deferred(self); + + // return ownership of system trait object (if entity still exists) + if let Some(mut entity) = self.get_entity_mut(id.0) { + entity.insert::(RegisteredSystem { + initialized, + system, + }); + } + Ok(()) + } +} + +/// The [`Command`] type for [`World::run_system`]. +/// +/// This command runs systems in an exclusive and single threaded way. +/// Running slow systems can become a bottleneck. +#[derive(Debug, Clone)] +pub struct RunSystem { + system_id: SystemId, +} + +impl RunSystem { + /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) + pub fn new(system_id: SystemId) -> Self { + Self { system_id } + } +} + +impl Command for RunSystem { + #[inline] + fn apply(self, world: &mut World) { + let _ = world.run_system(self.system_id); + } +} + +/// An operation with stored systems failed. +#[derive(Debug)] +pub enum RegisteredSystemError { + /// A system was run by id, but no system with that id was found. + /// + /// Did you forget to register it? + SystemIdNotRegistered(SystemId), + /// A system tried to run itself recursively. + Recursive(SystemId), + /// A system tried to remove itself. + SelfRemove(SystemId), +} + +mod tests { + use crate as bevy_ecs; + use crate::prelude::*; + + #[derive(Resource, Default, PartialEq, Debug)] + struct Counter(u8); + + #[test] + fn change_detection() { + #[derive(Resource, Default)] + struct ChangeDetector; + + fn count_up_iff_changed( + mut counter: ResMut, + change_detector: ResMut, + ) { + if change_detector.is_changed() { + counter.0 += 1; + } + } + + let mut world = World::new(); + world.init_resource::(); + world.init_resource::(); + assert_eq!(*world.resource::(), Counter(0)); + // Resources are changed when they are first added. + let id = world.register_system(count_up_iff_changed); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(1)); + // Nothing changed + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(1)); + // Making a change + world.resource_mut::().set_changed(); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(2)); + } + + #[test] + fn local_variables() { + // The `Local` begins at the default value of 0 + fn doubling(last_counter: Local, mut counter: ResMut) { + counter.0 += last_counter.0 .0; + last_counter.0 .0 = counter.0; + } + + let mut world = World::new(); + world.insert_resource(Counter(1)); + assert_eq!(*world.resource::(), Counter(1)); + let id = world.register_system(doubling); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(1)); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(2)); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(4)); + let _ = world.run_system(id); + assert_eq!(*world.resource::(), Counter(8)); + } + + #[test] + fn nested_systems() { + use crate::system::SystemId; + + #[derive(Component)] + struct Callback(SystemId); + + fn nested(query: Query<&Callback>, mut commands: Commands) { + for callback in query.iter() { + commands.run_system(callback.0); + } + } + + let mut world = World::new(); + world.insert_resource(Counter(0)); + + let increment_two = world.register_system(|mut counter: ResMut| { + counter.0 += 2; + }); + let increment_three = world.register_system(|mut counter: ResMut| { + counter.0 += 3; + }); + let nested_id = world.register_system(nested); + + world.spawn(Callback(increment_two)); + world.spawn(Callback(increment_three)); + let _ = world.run_system(nested_id); + assert_eq!(*world.resource::(), Counter(5)); + } +} diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 29b2d1b5dee37..019965804e58f 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -179,7 +179,7 @@ macro_rules! define_label { } $(#[$label_attr])* - pub trait $label_name: 'static { + pub trait $label_name: Send + Sync + 'static { /// Converts this type into an opaque, strongly-typed label. fn as_label(&self) -> $id_name { let id = self.type_id(); diff --git a/examples/README.md b/examples/README.md index d345cd5707edd..ed3229fe4a50d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -223,6 +223,7 @@ Example | Description [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results [Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this. +[One Shot Systems](../examples/ecs/one_shot_systems.rs) | Shows how to flexibly run systems without scheduling them [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` [Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame [Run Conditions](../examples/ecs/run_conditions.rs) | Run systems only when one or multiple conditions are met diff --git a/examples/ecs/one_shot_systems.rs b/examples/ecs/one_shot_systems.rs new file mode 100644 index 0000000000000..f7f76d4fa7a98 --- /dev/null +++ b/examples/ecs/one_shot_systems.rs @@ -0,0 +1,59 @@ +//! Demonstrates the use of "one-shot systems", which run once when triggered. +//! +//! These can be useful to help structure your logic in a push-based fashion, +//! reducing the overhead of running extremely rarely run systems +//! and improving schedule flexibility. +//! +//! See the [`World::run_system`](bevy::ecs::World::run_system) or +//! [`World::run_system_once`](bevy::ecs::World::run_system_once) docs for more +//! details. + +use bevy::{ + ecs::system::{RunSystemOnce, SystemId}, + prelude::*, +}; + +fn main() { + App::new() + .add_systems(Startup, (count_entities, setup)) + .add_systems(PostUpdate, count_entities) + .add_systems(Update, evaluate_callbacks) + .run(); +} + +// Any ordinary system can be run via commands.run_system or world.run_system. +fn count_entities(all_entities: Query<()>) { + dbg!(all_entities.iter().count()); +} + +#[derive(Component)] +struct Callback(SystemId); + +#[derive(Component)] +struct Triggered; + +fn setup(world: &mut World) { + let button_pressed_id = world.register_system(button_pressed); + world.spawn((Callback(button_pressed_id), Triggered)); + // This entity does not have a Triggered component, so its callback won't run. + let slider_toggled_id = world.register_system(slider_toggled); + world.spawn(Callback(slider_toggled_id)); + world.run_system_once(count_entities); +} + +fn button_pressed() { + println!("A button was pressed!"); +} + +fn slider_toggled() { + println!("A slider was toggled!"); +} + +/// Runs the systems associated with each `Callback` component if the entity also has a Triggered component. +/// +/// This could be done in an exclusive system rather than using `Commands` if preferred. +fn evaluate_callbacks(query: Query<&Callback, With>, mut commands: Commands) { + for callback in query.iter() { + commands.run_system(callback.0); + } +} From 41a35ff3d40334751d3059854167ac13237f9254 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Tue, 19 Sep 2023 23:45:40 +0200 Subject: [PATCH 32/72] Fix clippy lint in single_threaded_task_pool (#9851) # Objective `single_threaded_task_pool` emitted a warning: ``` warning: use of `default` to create a unit struct --> crates/bevy_tasks/src/single_threaded_task_pool.rs:22:25 | 22 | Self(PhantomData::default()) | ^^^^^^^^^^^ help: remove this call to `default` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs = note: `#[warn(clippy::default_constructed_unit_structs)]` on by default ``` ## Solution fix the lint --- crates/bevy_tasks/src/single_threaded_task_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index e39ac43aad5b1..36e38df5a7970 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -19,7 +19,7 @@ pub struct ThreadExecutor<'a>(PhantomData<&'a ()>); impl<'a> ThreadExecutor<'a> { /// Creates a new `ThreadExecutor` pub fn new() -> Self { - Self(PhantomData::default()) + Self::default() } } From 9e52697572bd856de070fe4faad830d54d7ac0d8 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Tue, 19 Sep 2023 23:49:33 +0200 Subject: [PATCH 33/72] Add mutual exclusion safety info on filter_fetch (#9836) # Objective Currently, in bevy, it's valid to do `Query<&mut Foo, Changed>`. This assumes that `filter_fetch` and `fetch` are mutually exclusive, because of the mutable reference to the tick that `Mut` implies and the reference that `Changed` implies. However nothing guarantees that. ## Solution Documenting this assumption as a safety invariant is the least thing. --- crates/bevy_ecs/src/query/fetch.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 3178c8a1fc128..1f7fe29ec729f 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -410,6 +410,9 @@ pub unsafe trait WorldQuery { /// /// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and /// `table_row` must be in the range of the current table and archetype. + /// + /// If this includes any mutable access, then this should never be called + /// while the return value of [`WorldQuery::fetch`] for the same entity is live. #[allow(unused_variables)] #[inline(always)] unsafe fn filter_fetch( From 692ef9508cd3d2b20f4118e7418347a307f1b514 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Tue, 19 Sep 2023 23:53:14 +0200 Subject: [PATCH 34/72] Cleanup `visibility` module (#9850) # Objective - `check_visibility` system in `bevy_render` had an `Option<&NoFrustumCulling>` that could be replaced by `Has`, which is theoretically faster and semantically more correct. - It also had some awkward indenting due to very large closure argument lists. - Some of the tests could be written more concisely ## Solution Use `Has`, move the tuple destructuring in a `let` binding, create a function for the tests. ## Note to reviewers Enable the "no white space diff" in the diff viewer to have a more meaningful diff in the `check_visibility` system. In the "Files changed" view, click on the little cog right of the "Jump to" text, on the row where the "Review changes" button is. then enable the "Hide whitespace" checkbox and click reload. --- ## Migration Guide - The `check_visibility` system's `Option<&NoFrustumCulling>` parameter has been replaced by `Has`, if you were calling it manually, you should change the type to match it --------- Co-authored-by: Rob Parrett --- crates/bevy_render/src/view/visibility/mod.rs | 202 +++++++----------- 1 file changed, 73 insertions(+), 129 deletions(-) diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index e004d9976d991..ca60da1788f5b 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -392,7 +392,7 @@ pub fn check_visibility( Option<&RenderLayers>, &Aabb, &GlobalTransform, - Option<&NoFrustumCulling>, + Has, )>, mut visible_no_aabb_query: Query< ( @@ -408,72 +408,72 @@ pub fn check_visibility( let view_mask = maybe_view_mask.copied().unwrap_or_default(); visible_entities.entities.clear(); - visible_aabb_query.par_iter_mut().for_each( - |( + visible_aabb_query.par_iter_mut().for_each(|query_item| { + let ( entity, inherited_visibility, mut view_visibility, maybe_entity_mask, model_aabb, transform, - maybe_no_frustum_culling, - )| { - // Skip computing visibility for entities that are configured to be hidden. - // ViewVisibility has already been reset in `reset_view_visibility`. - if !inherited_visibility.get() { + no_frustum_culling, + ) = query_item; + + // Skip computing visibility for entities that are configured to be hidden. + // ViewVisibility has already been reset in `reset_view_visibility`. + if !inherited_visibility.get() { + return; + } + + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !view_mask.intersects(&entity_mask) { + return; + } + + // If we have an aabb and transform, do frustum culling + if !no_frustum_culling { + let model = transform.affine(); + let model_sphere = Sphere { + center: model.transform_point3a(model_aabb.center), + radius: transform.radius_vec3a(model_aabb.half_extents), + }; + // Do quick sphere-based frustum culling + if !frustum.intersects_sphere(&model_sphere, false) { return; } - - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { + // If we have an aabb, do aabb-based frustum culling + if !frustum.intersects_obb(model_aabb, &model, true, false) { return; } + } - // If we have an aabb and transform, do frustum culling - if maybe_no_frustum_culling.is_none() { - let model = transform.affine(); - let model_sphere = Sphere { - center: model.transform_point3a(model_aabb.center), - radius: transform.radius_vec3a(model_aabb.half_extents), - }; - // Do quick sphere-based frustum culling - if !frustum.intersects_sphere(&model_sphere, false) { - return; - } - // If we have an aabb, do aabb-based frustum culling - if !frustum.intersects_obb(model_aabb, &model, true, false) { - return; - } - } + view_visibility.set(); + let cell = thread_queues.get_or_default(); + let mut queue = cell.take(); + queue.push(entity); + cell.set(queue); + }); - view_visibility.set(); - let cell = thread_queues.get_or_default(); - let mut queue = cell.take(); - queue.push(entity); - cell.set(queue); - }, - ); + visible_no_aabb_query.par_iter_mut().for_each(|query_item| { + let (entity, inherited_visibility, mut view_visibility, maybe_entity_mask) = query_item; - visible_no_aabb_query.par_iter_mut().for_each( - |(entity, inherited_visibility, mut view_visibility, maybe_entity_mask)| { - // Skip computing visibility for entities that are configured to be hidden. - // ViewVisiblity has already been reset in `reset_view_visibility`. - if !inherited_visibility.get() { - return; - } + // Skip computing visibility for entities that are configured to be hidden. + // ViewVisiblity has already been reset in `reset_view_visibility`. + if !inherited_visibility.get() { + return; + } - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !view_mask.intersects(&entity_mask) { - return; - } + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !view_mask.intersects(&entity_mask) { + return; + } - view_visibility.set(); - let cell = thread_queues.get_or_default(); - let mut queue = cell.take(); - queue.push(entity); - cell.set(queue); - }, - ); + view_visibility.set(); + let cell = thread_queues.get_or_default(); + let mut queue = cell.take(); + queue.push(entity); + cell.set(queue); + }); for cell in &mut thread_queues { visible_entities.entities.append(cell.get_mut()); @@ -490,26 +490,21 @@ mod test { use bevy_hierarchy::BuildWorldChildren; + fn visibility_bundle(visibility: Visibility) -> VisibilityBundle { + VisibilityBundle { + visibility, + ..Default::default() + } + } + #[test] fn visibility_propagation() { let mut app = App::new(); app.add_systems(Update, visibility_propagate_system); - let root1 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); + let root1 = app.world.spawn(visibility_bundle(Visibility::Hidden)).id(); let root1_child1 = app.world.spawn(VisibilityBundle::default()).id(); - let root1_child2 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); + let root1_child2 = app.world.spawn(visibility_bundle(Visibility::Hidden)).id(); let root1_child1_grandchild1 = app.world.spawn(VisibilityBundle::default()).id(); let root1_child2_grandchild1 = app.world.spawn(VisibilityBundle::default()).id(); @@ -525,13 +520,7 @@ mod test { let root2 = app.world.spawn(VisibilityBundle::default()).id(); let root2_child1 = app.world.spawn(VisibilityBundle::default()).id(); - let root2_child2 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); + let root2_child2 = app.world.spawn(visibility_bundle(Visibility::Hidden)).id(); let root2_child1_grandchild1 = app.world.spawn(VisibilityBundle::default()).id(); let root2_child2_grandchild1 = app.world.spawn(VisibilityBundle::default()).id(); @@ -599,59 +588,19 @@ mod test { #[test] fn visibility_propagation_unconditional_visible() { + use Visibility::{Hidden, Inherited, Visible}; + let mut app = App::new(); app.add_systems(Update, visibility_propagate_system); - let root1 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Visible, - ..Default::default() - }) - .id(); - let root1_child1 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Inherited, - ..Default::default() - }) - .id(); - let root1_child2 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); - let root1_child1_grandchild1 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Visible, - ..Default::default() - }) - .id(); - let root1_child2_grandchild1 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Visible, - ..Default::default() - }) - .id(); - - let root2 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Inherited, - ..Default::default() - }) - .id(); - let root3 = app - .world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); + let root1 = app.world.spawn(visibility_bundle(Visible)).id(); + let root1_child1 = app.world.spawn(visibility_bundle(Inherited)).id(); + let root1_child2 = app.world.spawn(visibility_bundle(Hidden)).id(); + let root1_child1_grandchild1 = app.world.spawn(visibility_bundle(Visible)).id(); + let root1_child2_grandchild1 = app.world.spawn(visibility_bundle(Visible)).id(); + + let root2 = app.world.spawn(visibility_bundle(Inherited)).id(); + let root3 = app.world.spawn(visibility_bundle(Hidden)).id(); app.world .entity_mut(root1) @@ -709,12 +658,7 @@ mod test { let id2 = world.spawn(VisibilityBundle::default()).id(); world.entity_mut(id1).push_children(&[id2]); - let id3 = world - .spawn(VisibilityBundle { - visibility: Visibility::Hidden, - ..Default::default() - }) - .id(); + let id3 = world.spawn(visibility_bundle(Visibility::Hidden)).id(); world.entity_mut(id2).push_children(&[id3]); let id4 = world.spawn(VisibilityBundle::default()).id(); From 7163aabf29d521ca74898f22c229931fdc40731c Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 20 Sep 2023 00:17:44 +0200 Subject: [PATCH 35/72] Use a single line for of large binding lists (#9849) # Objective - When adding/removing bindings in large binding lists, git would generate very difficult-to-read diffs ## Solution - Move the `@group(X) @binding(Y)` into the same line as the binding type declaration --- assets/shaders/array_texture.wgsl | 6 +- assets/shaders/cubemap_unlit.wgsl | 9 +- assets/shaders/custom_material.wgsl | 9 +- .../custom_material_screenspace_texture.wgsl | 6 +- assets/shaders/custom_vertex_attribute.wgsl | 3 +- assets/shaders/fallback_image_test.wgsl | 36 +++----- assets/shaders/game_of_life.wgsl | 3 +- assets/shaders/line_material.wgsl | 3 +- assets/shaders/post_processing.wgsl | 9 +- assets/shaders/shader_defs.wgsl | 3 +- assets/shaders/show_prepass.wgsl | 3 +- assets/shaders/texture_binding_array.wgsl | 6 +- crates/bevy_core_pipeline/src/blit/blit.wgsl | 6 +- .../bevy_core_pipeline/src/bloom/bloom.wgsl | 9 +- .../robust_contrast_adaptive_sharpening.wgsl | 9 +- crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl | 6 +- .../bevy_core_pipeline/src/skybox/skybox.wgsl | 9 +- .../src/tonemapping/tonemapping.wgsl | 17 ++-- .../src/tonemapping/tonemapping_shared.wgsl | 12 +-- crates/bevy_gizmos/src/lines.wgsl | 6 +- crates/bevy_pbr/src/material.rs | 9 +- .../src/prepass/prepass_bindings.wgsl | 10 +-- crates/bevy_pbr/src/render/mesh.rs | 6 +- crates/bevy_pbr/src/render/mesh_bindings.wgsl | 12 +-- .../src/render/mesh_view_bindings.wgsl | 84 +++++++------------ crates/bevy_pbr/src/render/morph.wgsl | 12 +-- crates/bevy_pbr/src/render/pbr_bindings.wgsl | 39 +++------ crates/bevy_pbr/src/render/skinning.wgsl | 10 +-- .../src/render_resource/bind_group.rs | 15 ++-- .../src/mesh2d/color_material.wgsl | 9 +- crates/bevy_sprite/src/mesh2d/material.rs | 9 +- .../src/mesh2d/mesh2d_bindings.wgsl | 3 +- .../src/mesh2d/mesh2d_view_bindings.wgsl | 6 +- crates/bevy_sprite/src/render/sprite.wgsl | 9 +- crates/bevy_ui/src/render/ui.wgsl | 9 +- examples/2d/mesh2d_manual.rs | 3 +- 36 files changed, 137 insertions(+), 278 deletions(-) diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index 4650491724d23..828bb0689a150 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -4,10 +4,8 @@ #import bevy_core_pipeline::tonemapping tone_mapping #import bevy_pbr::pbr_functions as fns -@group(1) @binding(0) -var my_array_texture: texture_2d_array; -@group(1) @binding(1) -var my_array_texture_sampler: sampler; +@group(1) @binding(0) var my_array_texture: texture_2d_array; +@group(1) @binding(1) var my_array_texture_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/cubemap_unlit.wgsl b/assets/shaders/cubemap_unlit.wgsl index b0c3672848fcf..2e05147b15865 100644 --- a/assets/shaders/cubemap_unlit.wgsl +++ b/assets/shaders/cubemap_unlit.wgsl @@ -1,15 +1,12 @@ #import bevy_pbr::mesh_vertex_output MeshVertexOutput #ifdef CUBEMAP_ARRAY -@group(1) @binding(0) -var base_color_texture: texture_cube_array; +@group(1) @binding(0) var base_color_texture: texture_cube_array; #else -@group(1) @binding(0) -var base_color_texture: texture_cube; +@group(1) @binding(0) var base_color_texture: texture_cube; #endif -@group(1) @binding(1) -var base_color_sampler: sampler; +@group(1) @binding(1) var base_color_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl index d09f5b4e3d2a4..c8e5a50ad34e7 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -4,12 +4,9 @@ struct CustomMaterial { color: vec4, }; -@group(1) @binding(0) -var material: CustomMaterial; -@group(1) @binding(1) -var base_color_texture: texture_2d; -@group(1) @binding(2) -var base_color_sampler: sampler; +@group(1) @binding(0) var material: CustomMaterial; +@group(1) @binding(1) var base_color_texture: texture_2d; +@group(1) @binding(2) var base_color_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_material_screenspace_texture.wgsl b/assets/shaders/custom_material_screenspace_texture.wgsl index 99c100d15e1ca..c285b7587aa82 100644 --- a/assets/shaders/custom_material_screenspace_texture.wgsl +++ b/assets/shaders/custom_material_screenspace_texture.wgsl @@ -2,10 +2,8 @@ #import bevy_pbr::mesh_vertex_output MeshVertexOutput #import bevy_pbr::utils coords_to_viewport_uv -@group(1) @binding(0) -var texture: texture_2d; -@group(1) @binding(1) -var texture_sampler: sampler; +@group(1) @binding(0) var texture: texture_2d; +@group(1) @binding(1) var texture_sampler: sampler; @fragment fn fragment( diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index 79454073a6fb4..01f6af42c4cb8 100644 --- a/assets/shaders/custom_vertex_attribute.wgsl +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -4,8 +4,7 @@ struct CustomMaterial { color: vec4, }; -@group(1) @binding(0) -var material: CustomMaterial; +@group(1) @binding(0) var material: CustomMaterial; struct Vertex { @builtin(instance_index) instance_index: u32, diff --git a/assets/shaders/fallback_image_test.wgsl b/assets/shaders/fallback_image_test.wgsl index 5bad13f899355..175d3d08da592 100644 --- a/assets/shaders/fallback_image_test.wgsl +++ b/assets/shaders/fallback_image_test.wgsl @@ -2,35 +2,23 @@ #import bevy_pbr::mesh_bindings #import bevy_pbr::mesh_vertex_output MeshVertexOutput -@group(1) @binding(0) -var test_texture_1d: texture_1d; -@group(1) @binding(1) -var test_texture_1d_sampler: sampler; +@group(1) @binding(0) var test_texture_1d: texture_1d; +@group(1) @binding(1) var test_texture_1d_sampler: sampler; -@group(1) @binding(2) -var test_texture_2d: texture_2d; -@group(1) @binding(3) -var test_texture_2d_sampler: sampler; +@group(1) @binding(2) var test_texture_2d: texture_2d; +@group(1) @binding(3) var test_texture_2d_sampler: sampler; -@group(1) @binding(4) -var test_texture_2d_array: texture_2d_array; -@group(1) @binding(5) -var test_texture_2d_array_sampler: sampler; +@group(1) @binding(4) var test_texture_2d_array: texture_2d_array; +@group(1) @binding(5) var test_texture_2d_array_sampler: sampler; -@group(1) @binding(6) -var test_texture_cube: texture_cube; -@group(1) @binding(7) -var test_texture_cube_sampler: sampler; +@group(1) @binding(6) var test_texture_cube: texture_cube; +@group(1) @binding(7) var test_texture_cube_sampler: sampler; -@group(1) @binding(8) -var test_texture_cube_array: texture_cube_array; -@group(1) @binding(9) -var test_texture_cube_array_sampler: sampler; +@group(1) @binding(8) var test_texture_cube_array: texture_cube_array; +@group(1) @binding(9) var test_texture_cube_array_sampler: sampler; -@group(1) @binding(10) -var test_texture_3d: texture_3d; -@group(1) @binding(11) -var test_texture_3d_sampler: sampler; +@group(1) @binding(10) var test_texture_3d: texture_3d; +@group(1) @binding(11) var test_texture_3d_sampler: sampler; @fragment fn fragment(in: MeshVertexOutput) {} diff --git a/assets/shaders/game_of_life.wgsl b/assets/shaders/game_of_life.wgsl index 8858b20751e3c..22305b31971f5 100644 --- a/assets/shaders/game_of_life.wgsl +++ b/assets/shaders/game_of_life.wgsl @@ -1,5 +1,4 @@ -@group(0) @binding(0) -var texture: texture_storage_2d; +@group(0) @binding(0) var texture: texture_storage_2d; fn hash(value: u32) -> u32 { var state = value; diff --git a/assets/shaders/line_material.wgsl b/assets/shaders/line_material.wgsl index dcf4cac57a438..7e897e6cc1fb3 100644 --- a/assets/shaders/line_material.wgsl +++ b/assets/shaders/line_material.wgsl @@ -4,8 +4,7 @@ struct LineMaterial { color: vec4, }; -@group(1) @binding(0) -var material: LineMaterial; +@group(1) @binding(0) var material: LineMaterial; @fragment fn fragment( diff --git a/assets/shaders/post_processing.wgsl b/assets/shaders/post_processing.wgsl index 4b1fed2aa2a15..398c08d320893 100644 --- a/assets/shaders/post_processing.wgsl +++ b/assets/shaders/post_processing.wgsl @@ -22,10 +22,8 @@ // You don't need to worry about this too much since bevy will compute the correct UVs for you. #import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput -@group(0) @binding(0) -var screen_texture: texture_2d; -@group(0) @binding(1) -var texture_sampler: sampler; +@group(0) @binding(0) var screen_texture: texture_2d; +@group(0) @binding(1) var texture_sampler: sampler; struct PostProcessSettings { intensity: f32, #ifdef SIXTEEN_BYTE_ALIGNMENT @@ -33,8 +31,7 @@ struct PostProcessSettings { _webgl2_padding: vec3 #endif } -@group(0) @binding(2) -var settings: PostProcessSettings; +@group(0) @binding(2) var settings: PostProcessSettings; @fragment fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { diff --git a/assets/shaders/shader_defs.wgsl b/assets/shaders/shader_defs.wgsl index fae9de396d0f8..10495abf62ba3 100644 --- a/assets/shaders/shader_defs.wgsl +++ b/assets/shaders/shader_defs.wgsl @@ -4,8 +4,7 @@ struct CustomMaterial { color: vec4, }; -@group(1) @binding(0) -var material: CustomMaterial; +@group(1) @binding(0) var material: CustomMaterial; @fragment fn fragment( diff --git a/assets/shaders/show_prepass.wgsl b/assets/shaders/show_prepass.wgsl index bd5abf401b599..34f1f814576ed 100644 --- a/assets/shaders/show_prepass.wgsl +++ b/assets/shaders/show_prepass.wgsl @@ -10,8 +10,7 @@ struct ShowPrepassSettings { padding_1: u32, padding_2: u32, } -@group(1) @binding(0) -var settings: ShowPrepassSettings; +@group(1) @binding(0) var settings: ShowPrepassSettings; @fragment fn fragment( diff --git a/assets/shaders/texture_binding_array.wgsl b/assets/shaders/texture_binding_array.wgsl index a88b8bcf227dd..cd1045f8bf882 100644 --- a/assets/shaders/texture_binding_array.wgsl +++ b/assets/shaders/texture_binding_array.wgsl @@ -1,9 +1,7 @@ #import bevy_pbr::mesh_vertex_output MeshVertexOutput -@group(1) @binding(0) -var textures: binding_array>; -@group(1) @binding(1) -var nearest_sampler: sampler; +@group(1) @binding(0) var textures: binding_array>; +@group(1) @binding(1) var nearest_sampler: sampler; // We can also have array of samplers // var samplers: binding_array; diff --git a/crates/bevy_core_pipeline/src/blit/blit.wgsl b/crates/bevy_core_pipeline/src/blit/blit.wgsl index 23c0e4eacd7d8..5ee6c1a6f92cf 100644 --- a/crates/bevy_core_pipeline/src/blit/blit.wgsl +++ b/crates/bevy_core_pipeline/src/blit/blit.wgsl @@ -1,9 +1,7 @@ #import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput -@group(0) @binding(0) -var in_texture: texture_2d; -@group(0) @binding(1) -var in_sampler: sampler; +@group(0) @binding(0) var in_texture: texture_2d; +@group(0) @binding(1) var in_sampler: sampler; @fragment fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4 { diff --git a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl index 12eaf84dda9fd..3666f105b1180 100644 --- a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl +++ b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl @@ -14,13 +14,10 @@ struct BloomUniforms { aspect: f32, }; -@group(0) @binding(0) -var input_texture: texture_2d; -@group(0) @binding(1) -var s: sampler; +@group(0) @binding(0) var input_texture: texture_2d; +@group(0) @binding(1) var s: sampler; -@group(0) @binding(2) -var uniforms: BloomUniforms; +@group(0) @binding(2) var uniforms: BloomUniforms; #ifdef FIRST_DOWNSAMPLE // https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl index b857333c997d9..68e299cfa1f9a 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl @@ -23,12 +23,9 @@ struct CASUniforms { sharpness: f32, }; -@group(0) @binding(0) -var screenTexture: texture_2d; -@group(0) @binding(1) -var samp: sampler; -@group(0) @binding(2) -var uniforms: CASUniforms; +@group(0) @binding(0) var screenTexture: texture_2d; +@group(0) @binding(1) var samp: sampler; +@group(0) @binding(2) var uniforms: CASUniforms; // This is set at the limit of providing unnatural results for sharpening. const FSR_RCAS_LIMIT = 0.1875; diff --git a/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl b/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl index d02aad0ff2a95..d2302a2267b25 100644 --- a/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl +++ b/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl @@ -8,10 +8,8 @@ #import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput -@group(0) @binding(0) -var screenTexture: texture_2d; -@group(0) @binding(1) -var samp: sampler; +@group(0) @binding(0) var screenTexture: texture_2d; +@group(0) @binding(1) var samp: sampler; // Trims the algorithm from processing darks. #ifdef EDGE_THRESH_MIN_LOW diff --git a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl index 5f31b753072ea..d3bc9c5faa571 100644 --- a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl +++ b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl @@ -1,11 +1,8 @@ #import bevy_render::view View -@group(0) @binding(0) -var skybox: texture_cube; -@group(0) @binding(1) -var skybox_sampler: sampler; -@group(0) @binding(2) -var view: View; +@group(0) @binding(0) var skybox: texture_cube; +@group(0) @binding(1) var skybox_sampler: sampler; +@group(0) @binding(2) var view: View; struct VertexOutput { @builtin(position) clip_position: vec4, diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index 8bc5bf376c7c8..4c73a891c81eb 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -4,17 +4,12 @@ #import bevy_render::view View #import bevy_core_pipeline::tonemapping tone_mapping, powsafe, screen_space_dither -@group(0) @binding(0) -var view: View; - -@group(0) @binding(1) -var hdr_texture: texture_2d; -@group(0) @binding(2) -var hdr_sampler: sampler; -@group(0) @binding(3) -var dt_lut_texture: texture_3d; -@group(0) @binding(4) -var dt_lut_sampler: sampler; +@group(0) @binding(0) var view: View; + +@group(0) @binding(1) var hdr_texture: texture_2d; +@group(0) @binding(2) var hdr_sampler: sampler; +@group(0) @binding(3) var dt_lut_texture: texture_3d; +@group(0) @binding(4) var dt_lut_sampler: sampler; #import bevy_core_pipeline::tonemapping diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index a1e3571137721..b8ca8f0d68f81 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -4,15 +4,11 @@ // hack !! not sure what to do with this #ifdef TONEMAPPING_PASS - @group(0) @binding(3) - var dt_lut_texture: texture_3d; - @group(0) @binding(4) - var dt_lut_sampler: sampler; + @group(0) @binding(3) var dt_lut_texture: texture_3d; + @group(0) @binding(4) var dt_lut_sampler: sampler; #else - @group(0) @binding(15) - var dt_lut_texture: texture_3d; - @group(0) @binding(16) - var dt_lut_sampler: sampler; + @group(0) @binding(15) var dt_lut_texture: texture_3d; + @group(0) @binding(16) var dt_lut_sampler: sampler; #endif fn sample_current_lut(p: vec3) -> vec3 { diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index 2d0c341dcbb2f..377194f3f1c07 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -1,8 +1,7 @@ // TODO use common view binding #import bevy_render::view View -@group(0) @binding(0) -var view: View; +@group(0) @binding(0) var view: View; struct LineGizmoUniform { @@ -14,8 +13,7 @@ struct LineGizmoUniform { #endif } -@group(1) @binding(0) -var line_gizmo: LineGizmoUniform; +@group(1) @binding(0) var line_gizmo: LineGizmoUniform; struct VertexInput { @location(0) position_a: vec3, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 8f6c59ce0f7c3..2f71085504cbe 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -95,12 +95,9 @@ use std::marker::PhantomData; /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl -/// @group(1) @binding(0) -/// var color: vec4; -/// @group(1) @binding(1) -/// var color_texture: texture_2d; -/// @group(1) @binding(2) -/// var color_sampler: sampler; +/// @group(1) @binding(0) var color: vec4; +/// @group(1) @binding(1) var color_texture: texture_2d; +/// @group(1) @binding(2) var color_sampler: sampler; /// ``` pub trait Material: Asset + AsBindGroup + Clone + Sized { /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index c4d039d14d6d1..965af6a19425e 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -3,16 +3,12 @@ #import bevy_render::globals Globals #import bevy_pbr::mesh_types -@group(0) @binding(0) -var view: View; -@group(0) @binding(1) -var globals: Globals; +@group(0) @binding(0) var view: View; +@group(0) @binding(1) var globals: Globals; #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(2) -var previous_view_proj: mat4x4; +@group(0) @binding(2) var previous_view_proj: mat4x4; #endif // MOTION_VECTOR_PREPASS // Material bindings will be in @group(1) - #import bevy_pbr::mesh_bindings mesh diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a8127d0ce59ea..9661016e9a35f 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -445,11 +445,9 @@ pub struct MeshPipeline { /// Use code like this in custom shaders: /// ```wgsl /// ##ifdef PER_OBJECT_BUFFER_BATCH_SIZE - /// @group(2) @binding(0) - /// var mesh: array; + /// @group(2) @binding(0) var mesh: array; /// ##else - /// @group(2) @binding(0) - /// var mesh: array; + /// @group(2) @binding(0) var mesh: array; /// ##endif // PER_OBJECT_BUFFER_BATCH_SIZE /// ``` pub per_object_buffer_batch_size: Option, diff --git a/crates/bevy_pbr/src/render/mesh_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_bindings.wgsl index ed638bfbda60a..1d8cf31221350 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_bindings.wgsl @@ -5,21 +5,17 @@ #ifdef MESH_BINDGROUP_1 #ifdef PER_OBJECT_BUFFER_BATCH_SIZE -@group(1) @binding(0) -var mesh: array; +@group(1) @binding(0) var mesh: array; #else -@group(1) @binding(0) -var mesh: array; +@group(1) @binding(0) var mesh: array; #endif // PER_OBJECT_BUFFER_BATCH_SIZE #else // MESH_BINDGROUP_1 #ifdef PER_OBJECT_BUFFER_BATCH_SIZE -@group(2) @binding(0) -var mesh: array; +@group(2) @binding(0) var mesh: array; #else -@group(2) @binding(0) -var mesh: array; +@group(2) @binding(0) var mesh: array; #endif // PER_OBJECT_BUFFER_BATCH_SIZE #endif // MESH_BINDGROUP_1 diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 8c922f2152e9f..4ed5cc3da1a66 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -4,77 +4,49 @@ #import bevy_render::view View #import bevy_render::globals Globals -@group(0) @binding(0) -var view: View; -@group(0) @binding(1) -var lights: types::Lights; +@group(0) @binding(0) var view: View; +@group(0) @binding(1) var lights: types::Lights; #ifdef NO_ARRAY_TEXTURES_SUPPORT -@group(0) @binding(2) -var point_shadow_textures: texture_depth_cube; +@group(0) @binding(2) var point_shadow_textures: texture_depth_cube; #else -@group(0) @binding(2) -var point_shadow_textures: texture_depth_cube_array; +@group(0) @binding(2) var point_shadow_textures: texture_depth_cube_array; #endif -@group(0) @binding(3) -var point_shadow_textures_sampler: sampler_comparison; +@group(0) @binding(3) var point_shadow_textures_sampler: sampler_comparison; #ifdef NO_ARRAY_TEXTURES_SUPPORT -@group(0) @binding(4) -var directional_shadow_textures: texture_depth_2d; +@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d; #else -@group(0) @binding(4) -var directional_shadow_textures: texture_depth_2d_array; +@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d_array; #endif -@group(0) @binding(5) -var directional_shadow_textures_sampler: sampler_comparison; +@group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 -@group(0) @binding(6) -var point_lights: types::PointLights; -@group(0) @binding(7) -var cluster_light_index_lists: types::ClusterLightIndexLists; -@group(0) @binding(8) -var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; +@group(0) @binding(6) var point_lights: types::PointLights; +@group(0) @binding(7) var cluster_light_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #else -@group(0) @binding(6) -var point_lights: types::PointLights; -@group(0) @binding(7) -var cluster_light_index_lists: types::ClusterLightIndexLists; -@group(0) @binding(8) -var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; +@group(0) @binding(6) var point_lights: types::PointLights; +@group(0) @binding(7) var cluster_light_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #endif -@group(0) @binding(9) -var globals: Globals; -@group(0) @binding(10) -var fog: types::Fog; +@group(0) @binding(9) var globals: Globals; +@group(0) @binding(10) var fog: types::Fog; -@group(0) @binding(11) -var screen_space_ambient_occlusion_texture: texture_2d; +@group(0) @binding(11) var screen_space_ambient_occlusion_texture: texture_2d; -@group(0) @binding(12) -var environment_map_diffuse: texture_cube; -@group(0) @binding(13) -var environment_map_specular: texture_cube; -@group(0) @binding(14) -var environment_map_sampler: sampler; +@group(0) @binding(12) var environment_map_diffuse: texture_cube; +@group(0) @binding(13) var environment_map_specular: texture_cube; +@group(0) @binding(14) var environment_map_sampler: sampler; -@group(0) @binding(15) -var dt_lut_texture: texture_3d; -@group(0) @binding(16) -var dt_lut_sampler: sampler; +@group(0) @binding(15) var dt_lut_texture: texture_3d; +@group(0) @binding(16) var dt_lut_sampler: sampler; #ifdef MULTISAMPLED -@group(0) @binding(17) -var depth_prepass_texture: texture_depth_multisampled_2d; -@group(0) @binding(18) -var normal_prepass_texture: texture_multisampled_2d; -@group(0) @binding(19) -var motion_vector_prepass_texture: texture_multisampled_2d; +@group(0) @binding(17) var depth_prepass_texture: texture_depth_multisampled_2d; +@group(0) @binding(18) var normal_prepass_texture: texture_multisampled_2d; +@group(0) @binding(19) var motion_vector_prepass_texture: texture_multisampled_2d; #else -@group(0) @binding(17) -var depth_prepass_texture: texture_depth_2d; -@group(0) @binding(18) -var normal_prepass_texture: texture_2d; -@group(0) @binding(19) -var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(17) var depth_prepass_texture: texture_depth_2d; +@group(0) @binding(18) var normal_prepass_texture: texture_2d; +@group(0) @binding(19) var motion_vector_prepass_texture: texture_2d; #endif diff --git a/crates/bevy_pbr/src/render/morph.wgsl b/crates/bevy_pbr/src/render/morph.wgsl index 6cfc6d9602868..291b3efb5841a 100644 --- a/crates/bevy_pbr/src/render/morph.wgsl +++ b/crates/bevy_pbr/src/render/morph.wgsl @@ -13,17 +13,13 @@ #ifdef MESH_BINDGROUP_1 -@group(1) @binding(2) -var morph_weights: MorphWeights; -@group(1) @binding(3) -var morph_targets: texture_3d; +@group(1) @binding(2) var morph_weights: MorphWeights; +@group(1) @binding(3) var morph_targets: texture_3d; #else -@group(2) @binding(2) -var morph_weights: MorphWeights; -@group(2) @binding(3) -var morph_targets: texture_3d; +@group(2) @binding(2) var morph_weights: MorphWeights; +@group(2) @binding(3) var morph_targets: texture_3d; #endif diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index e6664b21310b4..73a414f0c77d1 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -2,29 +2,16 @@ #import bevy_pbr::pbr_types StandardMaterial -@group(1) @binding(0) -var material: StandardMaterial; -@group(1) @binding(1) -var base_color_texture: texture_2d; -@group(1) @binding(2) -var base_color_sampler: sampler; -@group(1) @binding(3) -var emissive_texture: texture_2d; -@group(1) @binding(4) -var emissive_sampler: sampler; -@group(1) @binding(5) -var metallic_roughness_texture: texture_2d; -@group(1) @binding(6) -var metallic_roughness_sampler: sampler; -@group(1) @binding(7) -var occlusion_texture: texture_2d; -@group(1) @binding(8) -var occlusion_sampler: sampler; -@group(1) @binding(9) -var normal_map_texture: texture_2d; -@group(1) @binding(10) -var normal_map_sampler: sampler; -@group(1) @binding(11) -var depth_map_texture: texture_2d; -@group(1) @binding(12) -var depth_map_sampler: sampler; +@group(1) @binding(0) var material: StandardMaterial; +@group(1) @binding(1) var base_color_texture: texture_2d; +@group(1) @binding(2) var base_color_sampler: sampler; +@group(1) @binding(3) var emissive_texture: texture_2d; +@group(1) @binding(4) var emissive_sampler: sampler; +@group(1) @binding(5) var metallic_roughness_texture: texture_2d; +@group(1) @binding(6) var metallic_roughness_sampler: sampler; +@group(1) @binding(7) var occlusion_texture: texture_2d; +@group(1) @binding(8) var occlusion_sampler: sampler; +@group(1) @binding(9) var normal_map_texture: texture_2d; +@group(1) @binding(10) var normal_map_sampler: sampler; +@group(1) @binding(11) var depth_map_texture: texture_2d; +@group(1) @binding(12) var depth_map_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index ff2269893e4b6..24678619a34c1 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -5,15 +5,9 @@ #ifdef SKINNED #ifdef MESH_BINDGROUP_1 - - @group(1) @binding(1) - var joint_matrices: SkinnedMesh; - + @group(1) @binding(1) var joint_matrices: SkinnedMesh; #else - - @group(2) @binding(1) - var joint_matrices: SkinnedMesh; - + @group(2) @binding(1) var joint_matrices: SkinnedMesh; #endif diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 47c893da28551..213c39782679c 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -92,14 +92,10 @@ impl Deref for BindGroup { /// In WGSL shaders, the binding would look like this: /// /// ```wgsl -/// @group(1) @binding(0) -/// var color: vec4; -/// @group(1) @binding(1) -/// var color_texture: texture_2d; -/// @group(1) @binding(2) -/// var color_sampler: sampler; -/// @group(1) @binding(3) -/// var values: array; +/// @group(1) @binding(0) var color: vec4; +/// @group(1) @binding(1) var color_texture: texture_2d; +/// @group(1) @binding(2) var color_sampler: sampler; +/// @group(1) @binding(3) var values: array; /// ``` /// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups /// are generally bound to group 1. @@ -194,8 +190,7 @@ impl Deref for BindGroup { /// roughness: f32, /// }; /// -/// @group(1) @binding(0) -/// var material: CoolMaterial; +/// @group(1) @binding(0) var material: CoolMaterial; /// ``` /// /// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes: diff --git a/crates/bevy_sprite/src/mesh2d/color_material.wgsl b/crates/bevy_sprite/src/mesh2d/color_material.wgsl index 97e5cb73f689b..8c6212d5da804 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.wgsl +++ b/crates/bevy_sprite/src/mesh2d/color_material.wgsl @@ -13,12 +13,9 @@ struct ColorMaterial { }; const COLOR_MATERIAL_FLAGS_TEXTURE_BIT: u32 = 1u; -@group(1) @binding(0) -var material: ColorMaterial; -@group(1) @binding(1) -var texture: texture_2d; -@group(1) @binding(2) -var texture_sampler: sampler; +@group(1) @binding(0) var material: ColorMaterial; +@group(1) @binding(1) var texture: texture_2d; +@group(1) @binding(2) var texture_sampler: sampler; @fragment fn fragment( diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 220931e6453c3..9412365cdeaba 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -100,12 +100,9 @@ use crate::{ /// color: vec4, /// } /// -/// @group(1) @binding(0) -/// var material: CustomMaterial; -/// @group(1) @binding(1) -/// var color_texture: texture_2d; -/// @group(1) @binding(2) -/// var color_sampler: sampler; +/// @group(1) @binding(0) var material: CustomMaterial; +/// @group(1) @binding(1) var color_texture: texture_2d; +/// @group(1) @binding(2) var color_sampler: sampler; /// ``` pub trait Material2d: AsBindGroup + Asset + Clone + Sized { /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl index 6d51f963e083f..521ccfa846e43 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl @@ -2,5 +2,4 @@ #import bevy_sprite::mesh2d_types -@group(2) @binding(0) -var mesh: bevy_sprite::mesh2d_types::Mesh2d; +@group(2) @binding(0) var mesh: bevy_sprite::mesh2d_types::Mesh2d; diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl index e3ac8c30da337..55eb7b964c23a 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl @@ -3,8 +3,6 @@ #import bevy_render::view View #import bevy_render::globals Globals -@group(0) @binding(0) -var view: View; +@group(0) @binding(0) var view: View; -@group(0) @binding(1) -var globals: Globals; +@group(0) @binding(1) var globals: Globals; diff --git a/crates/bevy_sprite/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl index 174ccc98803b0..536971e5cb6e6 100644 --- a/crates/bevy_sprite/src/render/sprite.wgsl +++ b/crates/bevy_sprite/src/render/sprite.wgsl @@ -5,8 +5,7 @@ #import bevy_render::maths affine_to_square #import bevy_render::view View -@group(0) @binding(0) -var view: View; +@group(0) @binding(0) var view: View; struct VertexInput { @builtin(vertex_index) index: u32, @@ -47,10 +46,8 @@ fn vertex(in: VertexInput) -> VertexOutput { return out; } -@group(1) @binding(0) -var sprite_texture: texture_2d; -@group(1) @binding(1) -var sprite_sampler: sampler; +@group(1) @binding(0) var sprite_texture: texture_2d; +@group(1) @binding(1) var sprite_sampler: sampler; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 36e18f201604a..5014c4da4baec 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -2,8 +2,7 @@ const TEXTURED_QUAD: u32 = 0u; -@group(0) @binding(0) -var view: View; +@group(0) @binding(0) var view: View; struct VertexOutput { @location(0) uv: vec2, @@ -27,10 +26,8 @@ fn vertex( return out; } -@group(1) @binding(0) -var sprite_texture: texture_2d; -@group(1) @binding(1) -var sprite_sampler: sampler; +@group(1) @binding(0) var sprite_texture: texture_2d; +@group(1) @binding(1) var sprite_sampler: sampler; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 5a9e0547751b8..780b56f9920fe 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -215,8 +215,7 @@ const COLORED_MESH2D_SHADER: &str = r" #import bevy_sprite::mesh2d_types as MeshTypes #import bevy_sprite::mesh2d_functions as MeshFunctions -@group(1) @binding(0) -var mesh: MeshTypes::Mesh2d; +@group(1) @binding(0) var mesh: MeshTypes::Mesh2d; // The structure of the vertex buffer is as specified in `specialize()` struct Vertex { From 87f7d013c0d4f45a93a4c29e5d48237f4b512ca2 Mon Sep 17 00:00:00 2001 From: Joseph <21144246+JoJoJet@users.noreply.github.com> Date: Tue, 19 Sep 2023 21:44:56 -0700 Subject: [PATCH 36/72] Fix a typo in `DirectionalLightBundle` (#9861) # Objective Fix a typo introduced by #9497. While drafting the PR, the type was originally called `VisibleInHierarchy` before I renamed it to `InheritedVisibility`, but this field got left behind due to a typo. --- crates/bevy_pbr/src/bundle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs index b77ee5b071d5a..851f87bcd7466 100644 --- a/crates/bevy_pbr/src/bundle.rs +++ b/crates/bevy_pbr/src/bundle.rs @@ -123,7 +123,7 @@ pub struct DirectionalLightBundle { /// Enables or disables the light pub visibility: Visibility, /// Inherited visibility of an entity. - pub visible_in_hieararchy: InheritedVisibility, + pub inherited_visibility: InheritedVisibility, /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering pub view_visibility: ViewVisibility, } From 038d11329c1e53d44b35b78a5e7f6ac496d3c420 Mon Sep 17 00:00:00 2001 From: James McNulty Date: Wed, 20 Sep 2023 08:10:56 -0400 Subject: [PATCH 37/72] Wslg docs (#9842) # Objective - WSL documentation was out-of-date and potentially misleading. The release of WSLg makes a lot of stuff easier ## Solution - Just updating docs for now ## NB I haven't been able to get a full end-to-end GPU on WSL test going yet, but plan to update this documentation again once I have more of a grasp on that --- docs/linux_dependencies.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index 47bdbda21d91c..b123c9525f076 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -21,10 +21,17 @@ Depending on your graphics card, you may have to install one of the following: Compiling with clang is also possible - replace the `g++` package with `clang`. -### Windows Subsystem for Linux (WSL 2) +## Windows Subsystem for Linux (WSL 2) -Graphics and audio need to be configured for them to work with WSL 2 backend. -Please see the ubuntu [WSL documentation](https://wiki.ubuntu.com/WSL) on how to set up graphics and audio. +Up-to-date WSL Installs for Windows 10 & 11 include WSLg, which provides +necessary servers for passing graphics and audio between Windows and the WSL instance. +With WSLg, a user's WSL instance can use X11 as well as Wayland. +For more information, see WSLg [documentation](https://github.com/microsoft/wslg#wslg-architecture-overview). + +Prior to the release of [WSL Gui (WSLg)](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSLg) +around 4/20/2021, users had to [manually set up servers](https://wiki.ubuntu.com/WSL#Advanced_Topics) on windows for graphic and audio. +Make note of the date for documentation found across the internet. +Following advice from before WSLg's release can lead to additional conflicts. ## [Fedora](https://getfedora.org/) From 354a5b79335e2624783fe1bc1974e353b2f76a93 Mon Sep 17 00:00:00 2001 From: floppyhammer Date: Thu, 21 Sep 2023 01:40:00 +0800 Subject: [PATCH 38/72] Handle empty morph weights when loading gltf (#9867) # Objective Fixes https://github.com/bevyengine/bevy/issues/9863. ## Solution Spawn `MorphWeights` after we handle `MeshMorphWeights` for the children. --- crates/bevy_gltf/src/loader.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 069e1e6ed513f..c00edd77eabeb 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -810,18 +810,7 @@ fn load_node( // Map node index to entity node_index_to_entity_map.insert(gltf_node.index(), node.id()); - if let Some(mesh) = gltf_node.mesh() { - if let Some(weights) = mesh.weights() { - let first_mesh = if let Some(primitive) = mesh.primitives().next() { - let primitive_label = primitive_label(&mesh, &primitive); - let handle: Handle = load_context.get_label_handle(&primitive_label); - Some(handle) - } else { - None - }; - node.insert(MorphWeights::new(weights.to_vec(), first_mesh)?); - } - }; + let mut morph_weights = None; node.with_children(|parent| { if let Some(mesh) = gltf_node.mesh() { @@ -853,6 +842,11 @@ fn load_node( Some(weights) => weights.to_vec(), None => vec![0.0; target_count], }; + + if morph_weights.is_none() { + morph_weights = Some(weights.clone()); + } + // unwrap: the parent's call to `MeshMorphWeights::new` // means this code doesn't run if it returns an `Err`. // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets @@ -972,6 +966,13 @@ fn load_node( } } }); + + if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { + let primitive_label = mesh.primitives().next().map(|p| primitive_label(&mesh, &p)); + let first_mesh = primitive_label.map(|label| load_context.get_label_handle(label)); + node.insert(MorphWeights::new(weights, first_mesh)?); + } + if let Some(err) = gltf_error { Err(err) } else { From dd7f800b25a5574174e85377df8122cd3b15f2ca Mon Sep 17 00:00:00 2001 From: Nathan Stocks Date: Wed, 20 Sep 2023 12:22:34 -0600 Subject: [PATCH 39/72] Only run some workflows on the bevy repo (not forks) (#9872) # Objective Eliminate unnecessary Actions CI builds on forks, such as: - Daily builds, which are a waste of compute on forks, even if they succeed (although the Android build fails) - Administrative builds that attempt to deploy something In both the cases above, forks get CI failures that need to be ignored. It looks like this: image image ## Solution - [Only run some jobs when they are in the `bevyengine/bevy` repo.](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-only-run-job-for-specific-repository) - Leave the rest of the workflows alone (you still get a full set of CI for pull requests, for example) --- --- .github/workflows/daily.yml | 5 +++++ .github/workflows/docs.yml | 1 + .github/workflows/post-release.yml | 1 + .github/workflows/release.yml | 1 + 4 files changed, 8 insertions(+) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index a21d00dfc6e72..79df092add62a 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -11,6 +11,7 @@ env: jobs: build-for-iOS: + if: github.repository == 'bevyengine/bevy' runs-on: macos-latest timeout-minutes: 30 steps: @@ -38,6 +39,7 @@ jobs: -F "custom_id=$GITHUB_RUN_ID" build-for-Android: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -65,6 +67,7 @@ jobs: -F "custom_id=$GITHUB_RUN_ID" nonce: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest timeout-minutes: 30 outputs: @@ -74,6 +77,7 @@ jobs: run: echo "result=${{ github.run_id }}-$(date +%s)" >> $GITHUB_OUTPUT run: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest timeout-minutes: 30 needs: [nonce, build-for-iOS, build-for-Android] @@ -112,6 +116,7 @@ jobs: path: .github/start-mobile-example/*.png check-result: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest timeout-minutes: 30 needs: [run] diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ac2ba5c3e0632..e66554022c2c4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -46,6 +46,7 @@ jobs: touch target/doc/.nojekyll - name: Deploy + if: github.repository == 'bevyengine/bevy' uses: JamesIves/github-pages-deploy-action@v4 with: branch: gh-pages diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 01a8467f998a4..a7cce2e0380e7 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -9,6 +9,7 @@ env: jobs: ci: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14729c1c19035..20c3ffa04d59f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,7 @@ env: jobs: ci: + if: github.repository == 'bevyengine/bevy' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From cd1260585b8b62108a49b602e9daf623f9b0141d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Maita?= <47983254+mnmaita@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:19:47 +0200 Subject: [PATCH 40/72] Revert "Update defaults for OrthographicProjection (#9537)" (#9878) # Objective - Fixes #9876 ## Solution - Reverted commit `5012a0fd57748ab6f146776368b4cf988bba1eaa` to restore the previous default values for `OrthographicProjection`. --- ## Migration Guide - Migration guide steps from #9537 should be removed for next release. --- crates/bevy_render/src/camera/projection.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index a824d886c80aa..7b62385cac792 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -197,8 +197,6 @@ pub enum ScalingMode { /// /// Note that the scale of the projection and the apparent size of objects are inversely proportional. /// As the size of the projection increases, the size of objects decreases. -/// -/// Note also that the view frustum is centered at the origin. #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component, Default)] pub struct OrthographicProjection { @@ -206,7 +204,7 @@ pub struct OrthographicProjection { /// /// Objects closer than this will not be rendered. /// - /// Defaults to `-1000.0` + /// Defaults to `0.0` pub near: f32, /// The distance of the far clipping plane in world units. /// @@ -317,7 +315,7 @@ impl Default for OrthographicProjection { fn default() -> Self { OrthographicProjection { scale: 1.0, - near: -1000.0, + near: 0.0, far: 1000.0, viewport_origin: Vec2::new(0.5, 0.5), scaling_mode: ScalingMode::WindowSize(1.0), From 3ee9edf280c530f35a51049ec92fcfa552998614 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Wed, 20 Sep 2023 15:34:30 -0400 Subject: [PATCH 41/72] add try_insert to entity commands (#9844) # Objective - I spoke with some users in the ECS channel of bevy discord today and they suggested that I implement a fallible form of .insert for components. - In my opinion, it would be nice to have a fallible .insert like .try_insert (or to just make insert be fallible!) because it was causing a lot of panics in my game. In my game, I am spawning terrain chunks and despawning them in the Update loop. However, this was causing bevy_xpbd to panic because it was trying to .insert some physics components on my chunks and a race condition meant that its check to see if the entity exists would pass but then the next execution step it would not exist and would do an .insert and then panic. This means that there is no way to avoid a panic with conditionals. Luckily, bevy_xpbd does not care about inserting these components if the entity is being deleted and so if there were a .try_insert, like this PR provides it could use that instead in order to NOT panic. ( My interim solution for my own game has been to run the entity despawn events in the Last schedule but really this is just a hack and I should not be expected to manage the scheduling of despawns like this - it should just be easy and simple. IF it just so happened that bevy_xpbd ran .inserts in the Last schedule also, this would be an untenable soln overall ) ## Solution - Describe the solution used to achieve the objective above. Add a new command named TryInsert (entitycommands.try_insert) which functions exactly like .insert except if the entity does not exist it will not panic. Instead, it will log to info. This way, crates that are attaching components in ways which they do not mind that the entity no longer exists can just use try_insert instead of insert. --- ## Changelog ## Additional Thoughts In my opinion, NOT panicing should really be the default and having an .insert that does panic should be the odd edgecase but removing the panic! from .insert seems a bit above my paygrade -- although i would love to see it. My other thought is it would be good for .insert to return an Option AND not panic but it seems it uses an event bus right now so that seems to be impossible w the current architecture. --- crates/bevy_ecs/src/system/commands/mod.rs | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 512dadff88017..bf9945c347e23 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -680,6 +680,8 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { /// /// The command will panic when applied if the associated entity does not exist. /// + /// To avoid a panic in this case, use the command [`Self::try_insert`] instead. + /// /// # Example /// /// ``` @@ -729,6 +731,62 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self } + /// Tries to add a [`Bundle`] of components to the entity. + /// + /// This will overwrite any previous value(s) of the same component type. + /// + /// # Note + /// + /// Unlike [`Self::insert`], this will not panic if the associated entity does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct PlayerEntity { entity: Entity } + /// #[derive(Component)] + /// struct Health(u32); + /// #[derive(Component)] + /// struct Strength(u32); + /// #[derive(Component)] + /// struct Defense(u32); + /// + /// #[derive(Bundle)] + /// struct CombatBundle { + /// health: Health, + /// strength: Strength, + /// } + /// + /// fn add_combat_stats_system(mut commands: Commands, player: Res) { + /// commands.entity(player.entity) + /// // You can try_insert individual components: + /// .try_insert(Defense(10)) + /// + /// // You can also insert tuples of components: + /// .try_insert(CombatBundle { + /// health: Health(100), + /// strength: Strength(40), + /// }); + /// + /// // Suppose this occurs in a parallel adjacent system or process + /// commands.entity(player.entity) + /// .despawn(); + /// + /// commands.entity(player.entity) + /// // This will not panic nor will it add the component + /// .try_insert(Defense(5)); + /// } + /// # bevy_ecs::system::assert_is_system(add_combat_stats_system); + /// ``` + pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { + self.commands.add(TryInsert { + entity: self.entity, + bundle, + }); + self + } + /// Removes a [`Bundle`] of components from the entity. /// /// See [`EntityWorldMut::remove`](crate::world::EntityWorldMut::remove) for more @@ -966,6 +1024,25 @@ where } } +/// A [`Command`] that attempts to add the components in a [`Bundle`] to an entity. +pub struct TryInsert { + /// The entity to which the components will be added. + pub entity: Entity, + /// The [`Bundle`] containing the components that will be added to the entity. + pub bundle: T, +} + +impl Command for TryInsert +where + T: Bundle + 'static, +{ + fn apply(self, world: &mut World) { + if let Some(mut entity) = world.get_entity_mut(self.entity) { + entity.insert(self.bundle); + } + } +} + /// A [`Command`] that removes components from an entity. /// For a [`Bundle`] type `T`, this will remove any components in the bundle. /// Any components in the bundle that aren't found on the entity will be ignored. From e07c427dea7151afc99b35598304bf233a41e6ec Mon Sep 17 00:00:00 2001 From: Sludge <96552222+SludgePhD@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:35:53 +0200 Subject: [PATCH 42/72] `#[derive(Clone)]` on `Component{Info,Descriptor}` (#9812) # Objective Occasionally, it is useful to pull `ComponentInfo` or `ComponentDescriptor` out of the `Components` collection so that they can be inspected without borrowing the whole `World`. ## Solution Make `ComponentInfo` and `ComponentDescriptor` `Clone`, so that reflection-heavy code can store them in a side table. --- ## Changelog - Implement `Clone` for `ComponentInfo` and `ComponentDescriptor` --- crates/bevy_ecs/src/component.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index d3b4970f21f6a..82bf894246b2f 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -202,7 +202,7 @@ pub enum StorageType { } /// Stores metadata for a type of component or resource stored in a specific [`World`]. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ComponentInfo { id: ComponentId, descriptor: ComponentDescriptor, @@ -319,6 +319,7 @@ impl SparseSetIndex for ComponentId { } /// A value describing a component or resource, which may or may not correspond to a Rust type. +#[derive(Clone)] pub struct ComponentDescriptor { name: Cow<'static, str>, // SAFETY: This must remain private. It must match the statically known StorageType of the From 9873c9745bcf7094ec52ca0c942b975a7310b878 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 20 Sep 2023 20:44:50 +0100 Subject: [PATCH 43/72] Rename `num_font_atlases` to `len`. (#9879) # Objective Rename the `num_font_atlases` method of `FontAtlasSet` to `len`. All the function does is return the number of entries in its hashmap and the unnatural naming only makes it harder to discover. --- ## Changelog * Renamed the `num_font_atlases` method of `FontAtlasSet` to `len`. ## Migration Guide The `num_font_atlases` method of `FontAtlasSet` has been renamed to `len`. --- crates/bevy_text/src/font_atlas_set.rs | 8 +++++++- crates/bevy_text/src/glyph_brush.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index d4c2c9f5073ad..f0ca798d8934b 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -153,7 +153,13 @@ impl FontAtlasSet { }) } - pub fn num_font_atlases(&self) -> usize { + /// Returns the number of font atlases in this set + pub fn len(&self) -> usize { self.font_atlases.len() } + + /// Returns `true` if the font atlas set contains no elements + pub fn is_empty(&self) -> bool { + self.font_atlases.is_empty() + } } diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 61a837303a26c..fe1e2d7203a1b 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -114,7 +114,7 @@ impl GlyphBrush { if !text_settings.allow_dynamic_font_size && !font_atlas_warning.warned - && font_atlas_set.num_font_atlases() > text_settings.max_font_atlases.get() + && font_atlas_set.len() > text_settings.max_font_atlases.get() { warn!("warning[B0005]: Number of font atlases has exceeded the maximum of {}. Performance and memory usage may suffer.", text_settings.max_font_atlases.get()); font_atlas_warning.warned = true; From 47d87e49daad3ab81dd1635e04571849a6e4ef4b Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 20 Sep 2023 22:18:55 +0200 Subject: [PATCH 44/72] Refactor rendering systems to use `let-else` (#9870) # Objective Some rendering system did heavy use of `if let`, and could be improved by using `let else`. ## Solution - Reduce rightward drift by using let-else over if-let - Extract value-to-key mappings to their own functions so that the system is less bloated, easier to understand - Use a `let` binding instead of untupling in closure argument to reduce indentation ## Note to reviewers Enable the "no white space diff" for easier viewing. In the "Files changed" view, click on the little cog right of the "Jump to" text, on the row where the "Review changes" button is. then enable the "Hide whitespace" checkbox and click reload. --- crates/bevy_pbr/src/material.rs | 201 +++++++++++----------- crates/bevy_pbr/src/render/light.rs | 158 +++++++++-------- crates/bevy_pbr/src/wireframe.rs | 55 +++--- crates/bevy_sprite/src/mesh2d/material.rs | 114 ++++++------ 4 files changed, 260 insertions(+), 268 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 2f71085504cbe..20873ed0f341d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -365,6 +365,33 @@ impl RenderCommand

for SetMaterial } } +const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { + match alpha_mode { + // Premultiplied and Add share the same pipeline key + // They're made distinct in the PBR shader, via `premultiply_alpha()` + AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA, + AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA, + AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY, + AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD, + _ => MeshPipelineKey::NONE, + } +} + +const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey { + match tonemapping { + Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, + Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, + Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, + Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, + Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, + Tonemapping::SomewhatBoringDisplayTransform => { + MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM + } + Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, + Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, + } +} + #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( opaque_draw_functions: Res>, @@ -418,131 +445,97 @@ pub fn queue_material_meshes( if normal_prepass.is_some() { view_key |= MeshPipelineKey::NORMAL_PREPASS; } - if taa_settings.is_some() { view_key |= MeshPipelineKey::TAA; } + let environment_map_loaded = environment_map.is_some_and(|map| map.is_loaded(&images)); - let environment_map_loaded = match environment_map { - Some(environment_map) => environment_map.is_loaded(&images), - None => false, - }; if environment_map_loaded { view_key |= MeshPipelineKey::ENVIRONMENT_MAP; } - if !view.hdr { if let Some(tonemapping) = tonemapping { view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= match tonemapping { - Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, - Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, - Tonemapping::ReinhardLuminance => { - MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE - } - Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, - Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, - Tonemapping::SomewhatBoringDisplayTransform => { - MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM - } - Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, - Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, - }; + view_key |= tonemapping_pipeline_key(*tonemapping); } if let Some(DebandDither::Enabled) = dither { view_key |= MeshPipelineKey::DEBAND_DITHER; } } - if ssao.is_some() { view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; } - let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - if let Ok((material_handle, mesh_handle, mesh_transforms)) = + let Ok((material_handle, mesh_handle, mesh_transforms)) = material_meshes.get(*visible_entity) - { - if let (Some(mesh), Some(material)) = ( - render_meshes.get(mesh_handle), - render_materials.get(&material_handle.id()), - ) { - let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) - | view_key; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - match material.properties.alpha_mode { - AlphaMode::Blend => { - mesh_key |= MeshPipelineKey::BLEND_ALPHA; - } - AlphaMode::Premultiplied | AlphaMode::Add => { - // Premultiplied and Add share the same pipeline key - // They're made distinct in the PBR shader, via `premultiply_alpha()` - mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA; - } - AlphaMode::Multiply => { - mesh_key |= MeshPipelineKey::BLEND_MULTIPLY; - } - AlphaMode::Mask(_) => { - mesh_key |= MeshPipelineKey::MAY_DISCARD; - } - _ => (), - } - - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &material_pipeline, - MaterialPipelineKey { - mesh_key, - bind_group_data: material.key.clone(), - }, - &mesh.layout, - ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - let distance = rangefinder - .distance_translation(&mesh_transforms.transform.translation) - + material.properties.depth_bias; - match material.properties.alpha_mode { - AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { - entity: *visible_entity, - draw_function: draw_opaque_pbr, - pipeline: pipeline_id, - distance, - batch_size: 1, - }); - } - AlphaMode::Mask(_) => { - alpha_mask_phase.add(AlphaMask3d { - entity: *visible_entity, - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - distance, - batch_size: 1, - }); - } - AlphaMode::Blend - | AlphaMode::Premultiplied - | AlphaMode::Add - | AlphaMode::Multiply => { - transparent_phase.add(Transparent3d { - entity: *visible_entity, - draw_function: draw_transparent_pbr, - pipeline: pipeline_id, - distance, - batch_size: 1, - }); - } - } + else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_handle) else { + continue; + }; + let Some(material) = render_materials.get(&material_handle.id()) else { + continue; + }; + let mut mesh_key = view_key; + + mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + + if mesh.morph_targets.is_some() { + mesh_key |= MeshPipelineKey::MORPH_TARGETS; + } + mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); + + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &material_pipeline, + MaterialPipelineKey { + mesh_key, + bind_group_data: material.key.clone(), + }, + &mesh.layout, + ); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + let distance = rangefinder.distance_translation(&mesh_transforms.transform.translation) + + material.properties.depth_bias; + match material.properties.alpha_mode { + AlphaMode::Opaque => { + opaque_phase.add(Opaque3d { + entity: *visible_entity, + draw_function: draw_opaque_pbr, + pipeline: pipeline_id, + distance, + batch_size: 1, + }); + } + AlphaMode::Mask(_) => { + alpha_mask_phase.add(AlphaMask3d { + entity: *visible_entity, + draw_function: draw_alpha_mask_pbr, + pipeline: pipeline_id, + distance, + batch_size: 1, + }); + } + AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + | AlphaMode::Multiply => { + transparent_phase.add(Transparent3d { + entity: *visible_entity, + draw_function: draw_transparent_pbr, + pipeline: pipeline_id, + distance, + batch_size: 1, + }); } } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index a9d0cef47a7b6..6c5087e721b9a 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -346,39 +346,38 @@ pub fn extract_lights( let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len); for entity in global_point_lights.iter().copied() { - if let Ok((point_light, cubemap_visible_entities, transform, view_visibility)) = + let Ok((point_light, cubemap_visible_entities, transform, view_visibility)) = point_lights.get(entity) - { - if !view_visibility.get() { - continue; - } - // TODO: This is very much not ideal. We should be able to re-use the vector memory. - // However, since exclusive access to the main world in extract is ill-advised, we just clone here. - let render_cubemap_visible_entities = cubemap_visible_entities.clone(); - point_lights_values.push(( - entity, - ( - ExtractedPointLight { - color: point_light.color, - // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian - // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower - // for details. - intensity: point_light.intensity / (4.0 * std::f32::consts::PI), - range: point_light.range, - radius: point_light.radius, - transform: *transform, - shadows_enabled: point_light.shadows_enabled, - shadow_depth_bias: point_light.shadow_depth_bias, - // The factor of SQRT_2 is for the worst-case diagonal offset - shadow_normal_bias: point_light.shadow_normal_bias - * point_light_texel_size - * std::f32::consts::SQRT_2, - spot_light_angles: None, - }, - render_cubemap_visible_entities, - ), - )); + else { + continue; + }; + if !view_visibility.get() { + continue; } + // TODO: This is very much not ideal. We should be able to re-use the vector memory. + // However, since exclusive access to the main world in extract is ill-advised, we just clone here. + let render_cubemap_visible_entities = cubemap_visible_entities.clone(); + let extracted_point_light = ExtractedPointLight { + color: point_light.color, + // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian + // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower + // for details. + intensity: point_light.intensity / (4.0 * std::f32::consts::PI), + range: point_light.range, + radius: point_light.radius, + transform: *transform, + shadows_enabled: point_light.shadows_enabled, + shadow_depth_bias: point_light.shadow_depth_bias, + // The factor of SQRT_2 is for the worst-case diagonal offset + shadow_normal_bias: point_light.shadow_normal_bias + * point_light_texel_size + * std::f32::consts::SQRT_2, + spot_light_angles: None, + }; + point_lights_values.push(( + entity, + (extracted_point_light, render_cubemap_visible_entities), + )); } *previous_point_lights_len = point_lights_values.len(); commands.insert_or_spawn_batch(point_lights_values); @@ -1594,56 +1593,55 @@ pub fn queue_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued for entity in visible_entities.iter().copied() { - if let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) { - if let (Some(mesh), Some(material)) = ( - render_meshes.get(mesh_handle), - render_materials.get(&material_handle.id()), - ) { - let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) - | MeshPipelineKey::DEPTH_PREPASS; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - if is_directional_light { - mesh_key |= MeshPipelineKey::DEPTH_CLAMP_ORTHO; - } - let alpha_mode = material.properties.alpha_mode; - match alpha_mode { - AlphaMode::Mask(_) - | AlphaMode::Blend - | AlphaMode::Premultiplied - | AlphaMode::Add => { - mesh_key |= MeshPipelineKey::MAY_DISCARD; - } - _ => {} - } - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &prepass_pipeline, - MaterialPipelineKey { - mesh_key, - bind_group_data: material.key.clone(), - }, - &mesh.layout, - ); - - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - shadow_phase.add(Shadow { - draw_function: draw_shadow_mesh, - pipeline: pipeline_id, - entity, - distance: 0.0, // TODO: sort front-to-back - }); - } + let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_handle) else { + continue; + }; + let Some(material) = render_materials.get(&material_handle.id()) else { + continue; + }; + let mut mesh_key = + MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | MeshPipelineKey::DEPTH_PREPASS; + if mesh.morph_targets.is_some() { + mesh_key |= MeshPipelineKey::MORPH_TARGETS; } + if is_directional_light { + mesh_key |= MeshPipelineKey::DEPTH_CLAMP_ORTHO; + } + mesh_key |= match material.properties.alpha_mode { + AlphaMode::Mask(_) + | AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add => MeshPipelineKey::MAY_DISCARD, + _ => MeshPipelineKey::NONE, + }; + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &prepass_pipeline, + MaterialPipelineKey { + mesh_key, + bind_group_data: material.key.clone(), + }, + &mesh.layout, + ); + + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + shadow_phase.add(Shadow { + draw_function: draw_shadow_mesh, + pipeline: pipeline_id, + entity, + distance: 0.0, // TODO: sort front-to-back + }); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index e62ac1ae857d5..128609071a398 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -127,37 +127,34 @@ fn queue_wireframes( let rangefinder = view.rangefinder3d(); let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); - let add_render_phase = - |(entity, mesh_handle, mesh_transforms): (Entity, &Handle, &MeshTransforms)| { - if let Some(mesh) = render_meshes.get(mesh_handle) { - let mut key = view_key - | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - if mesh.morph_targets.is_some() { - key |= MeshPipelineKey::MORPH_TARGETS; - } - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &wireframe_pipeline, - key, - &mesh.layout, - ); - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - return; - } - }; - opaque_phase.add(Opaque3d { - entity, - pipeline: pipeline_id, - draw_function: draw_custom, - distance: rangefinder - .distance_translation(&mesh_transforms.transform.translation), - batch_size: 1, - }); + let add_render_phase = |phase_item: (Entity, &Handle, &MeshTransforms)| { + let (entity, mesh_handle, mesh_transforms) = phase_item; + + let Some(mesh) = render_meshes.get(mesh_handle) else { + return; + }; + let mut key = + view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + if mesh.morph_targets.is_some() { + key |= MeshPipelineKey::MORPH_TARGETS; + } + let pipeline_id = + pipelines.specialize(&pipeline_cache, &wireframe_pipeline, key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + return; } }; + opaque_phase.add(Opaque3d { + entity, + pipeline: pipeline_id, + draw_function: draw_custom, + distance: rangefinder.distance_translation(&mesh_transforms.transform.translation), + batch_size: 1, + }); + }; if wireframe_config.global { let query = material_meshes.p0(); diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9412365cdeaba..605115d2403f8 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -319,6 +319,21 @@ impl RenderCommand

} } +const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelineKey { + match tonemapping { + Tonemapping::None => Mesh2dPipelineKey::TONEMAP_METHOD_NONE, + Tonemapping::Reinhard => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD, + Tonemapping::ReinhardLuminance => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, + Tonemapping::AcesFitted => Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED, + Tonemapping::AgX => Mesh2dPipelineKey::TONEMAP_METHOD_AGX, + Tonemapping::SomewhatBoringDisplayTransform => { + Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM + } + Tonemapping::TonyMcMapface => Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, + Tonemapping::BlenderFilmic => Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, + } +} + #[allow(clippy::too_many_arguments)] pub fn queue_material2d_meshes( transparent_draw_functions: Res>, @@ -352,69 +367,58 @@ pub fn queue_material2d_meshes( if !view.hdr { if let Some(tonemapping) = tonemapping { view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; - view_key |= match tonemapping { - Tonemapping::None => Mesh2dPipelineKey::TONEMAP_METHOD_NONE, - Tonemapping::Reinhard => Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD, - Tonemapping::ReinhardLuminance => { - Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE - } - Tonemapping::AcesFitted => Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED, - Tonemapping::AgX => Mesh2dPipelineKey::TONEMAP_METHOD_AGX, - Tonemapping::SomewhatBoringDisplayTransform => { - Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM - } - Tonemapping::TonyMcMapface => Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, - Tonemapping::BlenderFilmic => Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, - }; + view_key |= tonemapping_pipeline_key(*tonemapping); } if let Some(DebandDither::Enabled) = dither { view_key |= Mesh2dPipelineKey::DEBAND_DITHER; } } - for visible_entity in &visible_entities.entities { - if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = + let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = material2d_meshes.get(*visible_entity) - { - if let Some(material2d) = render_materials.get(&material2d_handle.id()) { - if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { - let mesh_key = view_key - | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); - - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &material2d_pipeline, - Material2dKey { - mesh_key, - bind_group_data: material2d.key.clone(), - }, - &mesh.layout, - ); - - let pipeline_id = match pipeline_id { - Ok(id) => id, - Err(err) => { - error!("{}", err); - continue; - } - }; - - let mesh_z = mesh2d_uniform.transform.w_axis.z; - transparent_phase.add(Transparent2d { - entity: *visible_entity, - draw_function: draw_transparent_pbr, - pipeline: pipeline_id, - // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the - // lowest sort key and getting closer should increase. As we have - // -z in front of the camera, the largest distance is -far with values increasing toward the - // camera. As such we can just use mesh_z as the distance - sort_key: FloatOrd(mesh_z), - // This material is not batched - batch_size: 1, - }); - } + else { + continue; + }; + let Some(material2d) = render_materials.get(&material2d_handle.id()) else { + continue; + }; + let Some(mesh) = render_meshes.get(&mesh2d_handle.0) else { + continue; + }; + let mesh_key = + view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + + let pipeline_id = pipelines.specialize( + &pipeline_cache, + &material2d_pipeline, + Material2dKey { + mesh_key, + bind_group_data: material2d.key.clone(), + }, + &mesh.layout, + ); + + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; } - } + }; + + let mesh_z = mesh2d_uniform.transform.w_axis.z; + transparent_phase.add(Transparent2d { + entity: *visible_entity, + draw_function: draw_transparent_pbr, + pipeline: pipeline_id, + // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the + // lowest sort key and getting closer should increase. As we have + // -z in front of the camera, the largest distance is -far with values increasing toward the + // camera. As such we can just use mesh_z as the distance + sort_key: FloatOrd(mesh_z), + // This material is not batched + batch_size: 1, + }); } } } From 1116207f7dbcc04fbcfd10eecc09e3988a4c726a Mon Sep 17 00:00:00 2001 From: James Liu Date: Wed, 20 Sep 2023 15:34:28 -0700 Subject: [PATCH 45/72] Remove dependecies from bevy_tasks' README (#9881) # Objective Noticed that bevy_tasks' README mentions its dependency tree, which is very outdated at this point. ## Solution Remove it. --- crates/bevy_tasks/README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/bevy_tasks/README.md b/crates/bevy_tasks/README.md index 881d7b802a50b..233d6794c7d32 100644 --- a/crates/bevy_tasks/README.md +++ b/crates/bevy_tasks/README.md @@ -31,19 +31,3 @@ The determining factor for what kind of work should go in each pool is latency r [bevy]: https://bevyengine.org [rayon]: https://github.com/rayon-rs/rayon [async-executor]: https://github.com/stjepang/async-executor - -## Dependencies - -A very small dependency list is a key feature of this module - -```text -├── async-executor -│ ├── async-task -│ ├── concurrent-queue -│ │ └── cache-padded -│ └── fastrand -├── num_cpus -│ └── libc -├── parking -└── futures-lite -``` From bdb063497d802b9180d1b608bf54339e5a726b70 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Thu, 21 Sep 2023 10:53:20 -0700 Subject: [PATCH 46/72] Use radsort for Transparent2d PhaseItem sorting (#9882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Fix a performance regression in the "[bevy vs pixi](https://github.com/SUPERCILEX/bevy-vs-pixi)" benchmark. This benchmark seems to have a slightly pathological distribution of `z` values -- Sprites are spawned with a random `z` value with a child sprite at `f32::EPSILON` relative to the parent. See discussion here: https://github.com/bevyengine/bevy/issues/8100#issuecomment-1726978633 ## Solution Use `radsort` for sorting `Transparent2d` `PhaseItem`s. Use random `z` values in bevymark to stress the phase sort. Add an `--ordered-z` option to `bevymark` that uses the old behavior. ## Benchmarks mac m1 max | benchmark | fps before | fps after | diff | | - | - | - | - | | bevymark --waves 120 --per-wave 1000 --random-z | 42.16 | 47.06 | 🟩 +11.6% | | bevymark --waves 120 --per-wave 1000 | 52.50 | 52.29 | 🟥 -0.4% | | bevymark --waves 120 --per-wave 1000 --mode mesh2d --random-z | 9.64 | 10.24 | 🟩 +6.2% | | bevymark --waves 120 --per-wave 1000 --mode mesh2d | 15.83 | 15.59 | 🟥 -1.5% | | bevy-vs-pixi | 39.71 | 59.88 | 🟩 +50.1% | ## Discussion It's possible that `TransparentUi` should also change. We could probably use `slice::sort_unstable_by_key` with the current sort key though, as its items are always sorted and unique. I'd prefer to follow up later to look into that. Here's a survey of sorts used by other `PhaseItem`s #### slice::sort_by_key `Transparent2d`, `TransparentUi` #### radsort `Opaque3d`, `AlphaMask3d`, `Transparent3d`, `Opaque3dPrepass`, `AlphaMask3dPrepass`, `Shadow` I also tried `slice::sort_unstable_by_key` with a compound sort key including `Entity`, but it didn't seem as promising and I didn't test it as thoroughly. --------- Co-authored-by: Alice Cecile Co-authored-by: Robert Swain --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 ++- crates/bevy_sprite/Cargo.toml | 1 + examples/stress_tests/bevymark.rs | 20 ++++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 49f4260b203fc..084205ee24752 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -106,7 +106,8 @@ impl PhaseItem for Transparent2d { #[inline] fn sort(items: &mut [Self]) { - items.sort_by_key(|item| item.sort_key()); + // radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`. + radsort::sort_by_key(items, |item| item.sort_key().0); } #[inline] diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 37db1b9eb2a20..649dc42345036 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -31,3 +31,4 @@ guillotiere = "0.6.0" thiserror = "1.0" rectangle-pack = "0.4" bitflags = "2.3" +radsort = "0.1" diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 4e2670ecabd9d..cd8c1ad77f3c2 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -60,6 +60,10 @@ struct Args { /// the number of different textures from which to randomly select the material color. 0 means no textures. #[argh(option, default = "1")] material_texture_count: usize, + + /// generate z values in increasing order rather than randomly + #[argh(switch)] + ordered_z: bool, } #[derive(Default, Clone)] @@ -163,6 +167,7 @@ struct BirdResources { color_rng: StdRng, material_rng: StdRng, velocity_rng: StdRng, + transform_rng: StdRng, } #[derive(Component)] @@ -204,6 +209,7 @@ fn setup( color_rng: StdRng::seed_from_u64(42), material_rng: StdRng::seed_from_u64(42), velocity_rng: StdRng::seed_from_u64(42), + transform_rng: StdRng::seed_from_u64(42), }; let text_section = move |color, value: &str| { @@ -360,7 +366,12 @@ fn spawn_birds( Mode::Sprite => { let batch = (0..spawn_count) .map(|count| { - let bird_z = (current_count + count) as f32 * 0.00001; + let bird_z = if args.ordered_z { + (current_count + count) as f32 * 0.00001 + } else { + bird_resources.transform_rng.gen::() + }; + let (transform, velocity) = bird_velocity_transform( half_extents, Vec3::new(bird_x, bird_y, bird_z), @@ -398,7 +409,12 @@ fn spawn_birds( Mode::Mesh2d => { let batch = (0..spawn_count) .map(|count| { - let bird_z = (current_count + count) as f32 * 0.00001; + let bird_z = if args.ordered_z { + (current_count + count) as f32 * 0.00001 + } else { + bird_resources.transform_rng.gen::() + }; + let (transform, velocity) = bird_velocity_transform( half_extents, Vec3::new(bird_x, bird_y, bird_z), From e60249e59d0e8f62eb3fff2222e5cf6c3bc1ba33 Mon Sep 17 00:00:00 2001 From: Joseph <21144246+JoJoJet@users.noreply.github.com> Date: Thu, 21 Sep 2023 13:57:06 -0700 Subject: [PATCH 47/72] Improve codegen for world validation (#9464) # Objective Improve code-gen for `QueryState::validate_world` and `SystemState::validate_world`. ## Solution * Move panics into separate, non-inlined functions, to reduce the code size of the outer methods. * Mark the panicking functions with `#[cold]` to help the compiler optimize for the happy path. * Mark the functions with `#[track_caller]` to make debugging easier. --------- Co-authored-by: James Liu --- crates/bevy_ecs/src/query/state.rs | 16 +++++++++++----- crates/bevy_ecs/src/system/function_system.rs | 12 +++++++++++- crates/bevy_ecs/src/system/mod.rs | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 45115c2d540f5..e64c257ec04b5 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -224,12 +224,18 @@ impl QueryState { /// Many unsafe query methods require the world to match for soundness. This function is the easiest /// way of ensuring that it matches. #[inline] + #[track_caller] pub fn validate_world(&self, world_id: WorldId) { - assert!( - world_id == self.world_id, - "Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.", - std::any::type_name::(), - ); + #[inline(never)] + #[track_caller] + #[cold] + fn panic_mismatched(this: WorldId, other: WorldId) -> ! { + panic!("Encountered a mismatched World. This QueryState was created from {this:?}, but a method was called using {other:?}."); + } + + if self.world_id != world_id { + panic_mismatched(self.world_id, world_id); + } } /// Update the current [`QueryState`] with information from the provided [`Archetype`] diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index aa11b1a03f3cb..9e5fd21513dc9 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -232,8 +232,18 @@ impl SystemState { /// Asserts that the [`SystemState`] matches the provided world. #[inline] + #[track_caller] fn validate_world(&self, world_id: WorldId) { - assert!(self.matches_world(world_id), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with."); + #[inline(never)] + #[track_caller] + #[cold] + fn panic_mismatched(this: WorldId, other: WorldId) -> ! { + panic!("Encountered a mismatched World. This SystemState was created from {this:?}, but a method was called using {other:?}."); + } + + if !self.matches_world(world_id) { + panic_mismatched(self.world_id, world_id); + } } /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before fetching the parameters, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index ac73d676c9d6e..9079d3a9d4745 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1775,7 +1775,7 @@ mod tests { } #[test] - #[should_panic = "Attempted to use bevy_ecs::query::state::QueryState<()> with a mismatched World."] + #[should_panic = "Encountered a mismatched World."] fn query_validates_world_id() { let mut world1 = World::new(); let world2 = World::new(); From 5c884c5a151e178f6ad98a74edab473f12c344dd Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Fri, 22 Sep 2023 00:12:34 +0200 Subject: [PATCH 48/72] Automatic batching/instancing of draw commands (#9685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Implement the foundations of automatic batching/instancing of draw commands as the next step from #89 - NOTE: More performance improvements will come when more data is managed and bound in ways that do not require rebinding such as mesh, material, and texture data. ## Solution - The core idea for batching of draw commands is to check whether any of the information that has to be passed when encoding a draw command changes between two things that are being drawn according to the sorted render phase order. These should be things like the pipeline, bind groups and their dynamic offsets, index/vertex buffers, and so on. - The following assumptions have been made: - Only entities with prepared assets (pipelines, materials, meshes) are queued to phases - View bindings are constant across a phase for a given draw function as phases are per-view - `batch_and_prepare_render_phase` is the only system that performs this batching and has sole responsibility for preparing the per-object data. As such the mesh binding and dynamic offsets are assumed to only vary as a result of the `batch_and_prepare_render_phase` system, e.g. due to having to split data across separate uniform bindings within the same buffer due to the maximum uniform buffer binding size. - Implement `GpuArrayBuffer` for `Mesh2dUniform` to store Mesh2dUniform in arrays in GPU buffers rather than each one being at a dynamic offset in a uniform buffer. This is the same optimisation that was made for 3D not long ago. - Change batch size for a range in `PhaseItem`, adding API for getting or mutating the range. This is more flexible than a size as the length of the range can be used in place of the size, but the start and end can be otherwise whatever is needed. - Add an optional mesh bind group dynamic offset to `PhaseItem`. This avoids having to do a massive table move just to insert `GpuArrayBufferIndex` components. ## Benchmarks All tests have been run on an M1 Max on AC power. `bevymark` and `many_cubes` were modified to use 1920x1080 with a scale factor of 1. I run a script that runs a separate Tracy capture process, and then runs the bevy example with `--features bevy_ci_testing,trace_tracy` and `CI_TESTING_CONFIG=../benchmark.ron` with the contents of `../benchmark.ron`: ```rust ( exit_after: Some(1500) ) ``` ...in order to run each test for 1500 frames. The recent changes to `many_cubes` and `bevymark` added reproducible random number generation so that with the same settings, the same rng will occur. They also added benchmark modes that use a fixed delta time for animations. Combined this means that the same frames should be rendered both on main and on the branch. The graphs compare main (yellow) to this PR (red). ### 3D Mesh `many_cubes --benchmark` Screenshot 2023-09-03 at 23 42 10 The mesh and material are the same for all instances. This is basically the best case for the initial batching implementation as it results in 1 draw for the ~11.7k visible meshes. It gives a ~30% reduction in median frame time. The 1000th frame is identical using the flip tool: ![flip many_cubes-main-mesh3d many_cubes-batching-mesh3d 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/2511f37a-6df8-481a-932f-706ca4de7643) ``` Mean: 0.000000 Weighted median: 0.000000 1st weighted quartile: 0.000000 3rd weighted quartile: 0.000000 Min: 0.000000 Max: 0.000000 Evaluation time: 0.4615 seconds ``` ### 3D Mesh `many_cubes --benchmark --material-texture-count 10` Screenshot 2023-09-03 at 23 45 18 This run uses 10 different materials by varying their textures. The materials are randomly selected, and there is no sorting by material bind group for opaque 3D so any batching is 'random'. The PR produces a ~5% reduction in median frame time. If we were to sort the opaque phase by the material bind group, then this should be a lot faster. This produces about 10.5k draws for the 11.7k visible entities. This makes sense as randomly selecting from 10 materials gives a chance that two adjacent entities randomly select the same material and can be batched. The 1000th frame is identical in flip: ![flip many_cubes-main-mesh3d-mtc10 many_cubes-batching-mesh3d-mtc10 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/2b3a8614-9466-4ed8-b50c-d4aa71615dbb) ``` Mean: 0.000000 Weighted median: 0.000000 1st weighted quartile: 0.000000 3rd weighted quartile: 0.000000 Min: 0.000000 Max: 0.000000 Evaluation time: 0.4537 seconds ``` ### 3D Mesh `many_cubes --benchmark --vary-per-instance` Screenshot 2023-09-03 at 23 48 44 This run varies the material data per instance by randomly-generating its colour. This is the worst case for batching and that it performs about the same as `main` is a good thing as it demonstrates that the batching has minimal overhead when dealing with ~11k visible mesh entities. The 1000th frame is identical according to flip: ![flip many_cubes-main-mesh3d-vpi many_cubes-batching-mesh3d-vpi 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/ac5f5c14-9bda-4d1a-8219-7577d4aac68c) ``` Mean: 0.000000 Weighted median: 0.000000 1st weighted quartile: 0.000000 3rd weighted quartile: 0.000000 Min: 0.000000 Max: 0.000000 Evaluation time: 0.4568 seconds ``` ### 2D Mesh `bevymark --benchmark --waves 160 --per-wave 1000 --mode mesh2d` Screenshot 2023-09-03 at 23 59 56 This spawns 160 waves of 1000 quad meshes that are shaded with ColorMaterial. Each wave has a different material so 160 waves currently should result in 160 batches. This results in a 50% reduction in median frame time. Capturing a screenshot of the 1000th frame main vs PR gives: ![flip bevymark-main-mesh2d bevymark-batching-mesh2d 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/80102728-1217-4059-87af-14d05044df40) ``` Mean: 0.001222 Weighted median: 0.750432 1st weighted quartile: 0.453494 3rd weighted quartile: 0.969758 Min: 0.000000 Max: 0.990296 Evaluation time: 0.4255 seconds ``` So they seem to produce the same results. I also double-checked the number of draws. `main` does 160000 draws, and the PR does 160, as expected. ### 2D Mesh `bevymark --benchmark --waves 160 --per-wave 1000 --mode mesh2d --material-texture-count 10` Screenshot 2023-09-04 at 00 09 22 This generates 10 textures and generates materials for each of those and then selects one material per wave. The median frame time is reduced by 50%. Similar to the plain run above, this produces 160 draws on the PR and 160000 on `main` and the 1000th frame is identical (ignoring the fps counter text overlay). ![flip bevymark-main-mesh2d-mtc10 bevymark-batching-mesh2d-mtc10 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/ebed2822-dce7-426a-858b-b77dc45b986f) ``` Mean: 0.002877 Weighted median: 0.964980 1st weighted quartile: 0.668871 3rd weighted quartile: 0.982749 Min: 0.000000 Max: 0.992377 Evaluation time: 0.4301 seconds ``` ### 2D Mesh `bevymark --benchmark --waves 160 --per-wave 1000 --mode mesh2d --vary-per-instance` Screenshot 2023-09-04 at 00 13 53 This creates unique materials per instance by randomly-generating the material's colour. This is the worst case for 2D batching. Somehow, this PR manages a 7% reduction in median frame time. Both main and this PR issue 160000 draws. The 1000th frame is the same: ![flip bevymark-main-mesh2d-vpi bevymark-batching-mesh2d-vpi 67ppd ldr](https://github.com/bevyengine/bevy/assets/302146/a2ec471c-f576-4a36-a23b-b24b22578b97) ``` Mean: 0.001214 Weighted median: 0.937499 1st weighted quartile: 0.635467 3rd weighted quartile: 0.979085 Min: 0.000000 Max: 0.988971 Evaluation time: 0.4462 seconds ``` ### 2D Sprite `bevymark --benchmark --waves 160 --per-wave 1000 --mode sprite` Screenshot 2023-09-04 at 12 21 12 This just spawns 160 waves of 1000 sprites. There should be and is no notable difference between main and the PR. ### 2D Sprite `bevymark --benchmark --waves 160 --per-wave 1000 --mode sprite --material-texture-count 10` Screenshot 2023-09-04 at 12 36 08 This spawns the sprites selecting a texture at random per instance from the 10 generated textures. This has no significant change vs main and shouldn't. ### 2D Sprite `bevymark --benchmark --waves 160 --per-wave 1000 --mode sprite --vary-per-instance` Screenshot 2023-09-04 at 12 29 52 This sets the sprite colour as being unique per instance. This can still all be drawn using one batch. There should be no difference but the PR produces median frame times that are 4% higher. Investigation showed no clear sources of cost, rather a mix of give and take that should not happen. It seems like noise in the results. ### Summary | Benchmark | % change in median frame time | | ------------- | ------------- | | many_cubes | 🟩 -30% | | many_cubes 10 materials | 🟩 -5% | | many_cubes unique materials | 🟩 ~0% | | bevymark mesh2d | 🟩 -50% | | bevymark mesh2d 10 materials | 🟩 -50% | | bevymark mesh2d unique materials | 🟩 -7% | | bevymark sprite | 🟥 2% | | bevymark sprite 10 materials | 🟥 0.6% | | bevymark sprite unique materials | 🟥 4.1% | --- ## Changelog - Added: 2D and 3D mesh entities that share the same mesh and material (same textures, same data) are now batched into the same draw command for better performance. --------- Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com> Co-authored-by: Nicola Papale --- assets/shaders/custom_gltf_2d.wgsl | 6 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 26 ++- crates/bevy_core_pipeline/src/core_3d/mod.rs | 70 +++++- crates/bevy_core_pipeline/src/prepass/mod.rs | 48 ++++- crates/bevy_gizmos/src/pipeline_2d.rs | 3 +- crates/bevy_gizmos/src/pipeline_3d.rs | 3 +- crates/bevy_math/src/affine3.rs | 32 ++- crates/bevy_pbr/src/material.rs | 68 ++++-- crates/bevy_pbr/src/prepass/mod.rs | 12 +- crates/bevy_pbr/src/render/light.rs | 27 ++- crates/bevy_pbr/src/render/mesh.rs | 199 ++++++------------ crates/bevy_pbr/src/render/morph.rs | 5 +- crates/bevy_pbr/src/wireframe.rs | 3 +- crates/bevy_render/src/batching/mod.rs | 123 +++++++++++ crates/bevy_render/src/lib.rs | 1 + crates/bevy_render/src/render_phase/mod.rs | 54 ++--- .../render_resource/batched_uniform_buffer.rs | 5 +- .../src/render_resource/gpu_array_buffer.rs | 9 +- crates/bevy_sprite/src/mesh2d/material.rs | 71 +++++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 170 +++++++++++---- crates/bevy_sprite/src/mesh2d/mesh2d.wgsl | 8 +- .../src/mesh2d/mesh2d_bindings.wgsl | 20 +- .../src/mesh2d/mesh2d_functions.wgsl | 15 +- .../bevy_sprite/src/mesh2d/mesh2d_types.wgsl | 12 +- crates/bevy_sprite/src/render/mod.rs | 14 +- crates/bevy_ui/src/render/mod.rs | 8 +- crates/bevy_ui/src/render/render_pass.rs | 26 ++- crates/bevy_utils/Cargo.toml | 1 + crates/bevy_utils/src/lib.rs | 5 + examples/2d/mesh2d_manual.rs | 28 ++- examples/shader/shader_instancing.rs | 3 +- 31 files changed, 772 insertions(+), 303 deletions(-) create mode 100644 crates/bevy_render/src/batching/mod.rs diff --git a/assets/shaders/custom_gltf_2d.wgsl b/assets/shaders/custom_gltf_2d.wgsl index 58058d9501033..5e0a908c875d4 100644 --- a/assets/shaders/custom_gltf_2d.wgsl +++ b/assets/shaders/custom_gltf_2d.wgsl @@ -1,8 +1,9 @@ #import bevy_sprite::mesh2d_view_bindings globals #import bevy_sprite::mesh2d_bindings mesh -#import bevy_sprite::mesh2d_functions mesh2d_position_local_to_clip +#import bevy_sprite::mesh2d_functions get_model_matrix, mesh2d_position_local_to_clip struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, @location(1) color: vec4, @location(2) barycentric: vec3, @@ -17,7 +18,8 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - out.clip_position = mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + let model = get_model_matrix(vertex.instance_index); + out.clip_position = mesh2d_position_local_to_clip(model, vec4(vertex.position, 1.0)); out.color = vertex.color; out.barycentric = vertex.barycentric; return out; diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 084205ee24752..530d48cde38a5 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -19,6 +19,8 @@ pub mod graph { } pub const CORE_2D: &str = graph::NAME; +use std::ops::Range; + pub use camera_2d::*; pub use main_pass_2d_node::*; @@ -35,7 +37,7 @@ use bevy_render::{ render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; @@ -83,7 +85,8 @@ pub struct Transparent2d { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Transparent2d { @@ -111,8 +114,23 @@ impl PhaseItem for Transparent2d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 04f4f0973998f..e30b20b8d5e49 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -24,7 +24,7 @@ pub mod graph { } pub const CORE_3D: &str = graph::NAME; -use std::cmp::Reverse; +use std::{cmp::Reverse, ops::Range}; pub use camera_3d::*; pub use main_opaque_pass_3d_node::*; @@ -50,7 +50,7 @@ use bevy_render::{ view::ViewDepthTexture, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::{FloatOrd, HashMap}; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd, HashMap}; use crate::{ prepass::{ @@ -135,7 +135,8 @@ pub struct Opaque3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Opaque3d { @@ -164,8 +165,23 @@ impl PhaseItem for Opaque3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } @@ -181,7 +197,8 @@ pub struct AlphaMask3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3d { @@ -210,8 +227,23 @@ impl PhaseItem for AlphaMask3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } @@ -227,7 +259,8 @@ pub struct Transparent3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Transparent3d { @@ -255,8 +288,23 @@ impl PhaseItem for Transparent3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 38c71050a194b..f408a168e7c7c 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -27,7 +27,7 @@ pub mod node; -use std::cmp::Reverse; +use std::{cmp::Reverse, ops::Range}; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; @@ -36,7 +36,7 @@ use bevy_render::{ render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat}, texture::CachedTexture, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; pub const DEPTH_PREPASS_FORMAT: TextureFormat = TextureFormat::Depth32Float; pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; @@ -83,6 +83,8 @@ pub struct Opaque3dPrepass { pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Opaque3dPrepass { @@ -109,6 +111,26 @@ impl PhaseItem for Opaque3dPrepass { // Key negated to match reversed SortKey ordering radsort::sort_by_key(items, |item| -item.distance); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { @@ -128,6 +150,8 @@ pub struct AlphaMask3dPrepass { pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3dPrepass { @@ -154,6 +178,26 @@ impl PhaseItem for AlphaMask3dPrepass { // Key negated to match reversed SortKey ordering radsort::sort_by_key(items, |item| -item.distance); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass { diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 2ec777e8ca52b..5b64598eb403f 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -178,7 +178,8 @@ fn queue_line_gizmos_2d( draw_function, pipeline, sort_key: FloatOrd(f32::INFINITY), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index acb827dff3346..c9a465f595549 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -192,7 +192,8 @@ fn queue_line_gizmos_3d( draw_function, pipeline, distance: 0., - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/crates/bevy_math/src/affine3.rs b/crates/bevy_math/src/affine3.rs index 51598e4beae12..a03f12dd57e65 100644 --- a/crates/bevy_math/src/affine3.rs +++ b/crates/bevy_math/src/affine3.rs @@ -1,4 +1,4 @@ -use glam::{Affine3A, Mat3, Vec3}; +use glam::{Affine3A, Mat3, Vec3, Vec3Swizzles, Vec4}; /// Reduced-size version of `glam::Affine3A` for use when storage has /// significant performance impact. Convert to `glam::Affine3A` to do @@ -10,6 +10,36 @@ pub struct Affine3 { pub translation: Vec3, } +impl Affine3 { + /// Calculates the transpose of the affine 4x3 matrix to a 3x4 and formats it for packing into GPU buffers + #[inline] + pub fn to_transpose(&self) -> [Vec4; 3] { + let transpose_3x3 = self.matrix3.transpose(); + [ + transpose_3x3.x_axis.extend(self.translation.x), + transpose_3x3.y_axis.extend(self.translation.y), + transpose_3x3.z_axis.extend(self.translation.z), + ] + } + + /// Calculates the inverse transpose of the 3x3 matrix and formats it for packing into GPU buffers + #[inline] + pub fn inverse_transpose_3x3(&self) -> ([Vec4; 2], f32) { + let inverse_transpose_3x3 = Affine3A::from(self).inverse().matrix3.transpose(); + ( + [ + (inverse_transpose_3x3.x_axis, inverse_transpose_3x3.y_axis.x).into(), + ( + inverse_transpose_3x3.y_axis.yz(), + inverse_transpose_3x3.z_axis.xy(), + ) + .into(), + ], + inverse_transpose_3x3.z_axis.z, + ) + } +} + impl From<&Affine3A> for Affine3 { fn from(affine: &Affine3A) -> Self { Self { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 20873ed0f341d..d9c835abcaffa 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -20,7 +20,6 @@ use bevy_ecs::{ }, }; use bevy_render::{ - extract_component::ExtractComponentPlugin, mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, @@ -29,13 +28,13 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, - PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, texture::FallbackImage, - view::{ExtractedView, Msaa, VisibleEntities}, + view::{ExtractedView, Msaa, ViewVisibility, VisibleEntities}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap, HashSet}; @@ -180,8 +179,7 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); + app.init_asset::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -193,7 +191,10 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_materials::) + .add_systems( + ExtractSchedule, + (extract_materials::, extract_material_meshes::), + ) .add_systems( Render, ( @@ -225,6 +226,26 @@ where } } +fn extract_material_meshes( + mut commands: Commands, + mut previous_len: Local, + query: Extract)>>, +) { + let mut values = Vec::with_capacity(*previous_len); + for (entity, view_visibility, material) in &query { + if view_visibility.get() { + // NOTE: MaterialBindGroupId is inserted here to avoid a table move. Upcoming changes + // to use SparseSet for render world entity storage will do this automatically. + values.push(( + entity, + (material.clone_weak(), MaterialBindGroupId::default()), + )); + } + } + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); +} + /// A key uniquely identifying a specialized [`MaterialPipeline`]. pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, @@ -403,7 +424,12 @@ pub fn queue_material_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material_meshes: Query<(&Handle, &Handle, &MeshTransforms)>, + mut material_meshes: Query<( + &Handle, + &mut MaterialBindGroupId, + &Handle, + &MeshTransforms, + )>, images: Res>, mut views: Query<( &ExtractedView, @@ -467,8 +493,8 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - let Ok((material_handle, mesh_handle, mesh_transforms)) = - material_meshes.get(*visible_entity) + let Ok((material_handle, mut material_bind_group_id, mesh_handle, mesh_transforms)) = + material_meshes.get_mut(*visible_entity) else { continue; }; @@ -504,6 +530,8 @@ pub fn queue_material_meshes( } }; + *material_bind_group_id = material.get_bind_group_id(); + let distance = rangefinder.distance_translation(&mesh_transforms.transform.translation) + material.properties.depth_bias; match material.properties.alpha_mode { @@ -513,7 +541,8 @@ pub fn queue_material_meshes( draw_function: draw_opaque_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Mask(_) => { @@ -522,7 +551,8 @@ pub fn queue_material_meshes( draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Blend @@ -534,7 +564,8 @@ pub fn queue_material_meshes( draw_function: draw_transparent_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } @@ -560,6 +591,15 @@ pub struct PreparedMaterial { pub properties: MaterialProperties, } +#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] +pub struct MaterialBindGroupId(Option); + +impl PreparedMaterial { + pub fn get_bind_group_id(&self) -> MaterialBindGroupId { + MaterialBindGroupId(Some(self.bind_group.id())) + } +} + #[derive(Resource)] pub struct ExtractedMaterials { extracted: Vec<(AssetId, M)>, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 3c9bdd7ddb4ec..ca1caf5c44720 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -17,6 +17,7 @@ use bevy_ecs::{ }; use bevy_math::{Affine3A, Mat4}; use bevy_render::{ + batching::batch_and_prepare_render_phase, globals::{GlobalsBuffer, GlobalsUniform}, mesh::MeshVertexBufferLayout, prelude::{Camera, Mesh}, @@ -158,7 +159,12 @@ where .add_systems(ExtractSchedule, extract_camera_previous_view_projection) .add_systems( Render, - prepare_previous_view_projection_uniforms.in_set(RenderSet::PrepareResources), + ( + prepare_previous_view_projection_uniforms, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + ) + .in_set(RenderSet::PrepareResources), ); } @@ -849,6 +855,8 @@ pub fn queue_prepass_material_meshes( draw_function: opaque_draw_prepass, pipeline_id, distance, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Mask(_) => { @@ -857,6 +865,8 @@ pub fn queue_prepass_material_meshes( draw_function: alpha_mask_draw_prepass, pipeline_id, distance, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6c5087e721b9a..bad686b92f2f9 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -27,10 +27,11 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::{ + nonmax::NonMaxU32, tracing::{error, warn}, HashMap, }; -use std::{hash::Hash, num::NonZeroU64}; +use std::{hash::Hash, num::NonZeroU64, ops::Range}; #[derive(Component)] pub struct ExtractedPointLight { @@ -1641,6 +1642,8 @@ pub fn queue_shadows( pipeline: pipeline_id, entity, distance: 0.0, // TODO: sort front-to-back + batch_range: 0..1, + dynamic_offset: None, }); } } @@ -1652,6 +1655,8 @@ pub struct Shadow { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Shadow { @@ -1679,6 +1684,26 @@ impl PhaseItem for Shadow { // better than rebinding everything at a high rate. radsort::sort_by_key(items, |item| item.sort_key()); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for Shadow { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9661016e9a35f..995c8bfa59f2c 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,8 +1,8 @@ use crate::{ environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights, - GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform, - ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers, ViewClusterBindings, - ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, + GpuPointLights, LightMeta, MaterialBindGroupId, NotShadowCaster, NotShadowReceiver, + PreviousGlobalTransform, ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers, + ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; use bevy_app::Plugin; @@ -16,11 +16,15 @@ use bevy_core_pipeline::{ }; use bevy_ecs::{ prelude::*, - query::ROQueryItem, + query::{QueryItem, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Affine3, Affine3A, Mat4, Vec2, Vec3Swizzles, Vec4}; +use bevy_math::{Affine3, Mat4, Vec2, Vec4}; use bevy_render::{ + batching::{ + batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + NoAutomaticBatching, + }, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, @@ -29,7 +33,7 @@ use bevy_render::{ }, prelude::Msaa, render_asset::RenderAssets, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, TrackedRenderPass}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -41,7 +45,6 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::{tracing::error, HashMap, Hashed}; -use fixedbitset::FixedBitSet; use crate::render::{ morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, @@ -119,7 +122,15 @@ impl Plugin for MeshRenderPlugin { .add_systems( Render, ( - prepare_mesh_uniforms.in_set(RenderSet::PrepareResources), + ( + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + ) + .in_set(RenderSet::PrepareResources), + write_batched_instance_buffer:: + .in_set(RenderSet::PrepareResourcesFlush), prepare_skinned_meshes.in_set(RenderSet::PrepareResources), prepare_morphs.in_set(RenderSet::PrepareResources), prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups), @@ -184,48 +195,13 @@ pub struct MeshUniform { impl From<&MeshTransforms> for MeshUniform { fn from(mesh_transforms: &MeshTransforms) -> Self { - let transpose_model_3x3 = mesh_transforms.transform.matrix3.transpose(); - let transpose_previous_model_3x3 = mesh_transforms.previous_transform.matrix3.transpose(); - let inverse_transpose_model_3x3 = Affine3A::from(&mesh_transforms.transform) - .inverse() - .matrix3 - .transpose(); + let (inverse_transpose_model_a, inverse_transpose_model_b) = + mesh_transforms.transform.inverse_transpose_3x3(); Self { - transform: [ - transpose_model_3x3 - .x_axis - .extend(mesh_transforms.transform.translation.x), - transpose_model_3x3 - .y_axis - .extend(mesh_transforms.transform.translation.y), - transpose_model_3x3 - .z_axis - .extend(mesh_transforms.transform.translation.z), - ], - previous_transform: [ - transpose_previous_model_3x3 - .x_axis - .extend(mesh_transforms.previous_transform.translation.x), - transpose_previous_model_3x3 - .y_axis - .extend(mesh_transforms.previous_transform.translation.y), - transpose_previous_model_3x3 - .z_axis - .extend(mesh_transforms.previous_transform.translation.z), - ], - inverse_transpose_model_a: [ - ( - inverse_transpose_model_3x3.x_axis, - inverse_transpose_model_3x3.y_axis.x, - ) - .into(), - ( - inverse_transpose_model_3x3.y_axis.yz(), - inverse_transpose_model_3x3.z_axis.xy(), - ) - .into(), - ], - inverse_transpose_model_b: inverse_transpose_model_3x3.z_axis.z, + transform: mesh_transforms.transform.to_transpose(), + previous_transform: mesh_transforms.previous_transform.to_transpose(), + inverse_transpose_model_a, + inverse_transpose_model_b, flags: mesh_transforms.flags, } } @@ -234,7 +210,7 @@ impl From<&MeshTransforms> for MeshUniform { // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_types.wgsl! bitflags::bitflags! { #[repr(transparent)] - struct MeshFlags: u32 { + pub struct MeshFlags: u32 { const SHADOW_RECEIVER = (1 << 0); // Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive, // then the flag should be set, else it should not be set. @@ -361,7 +337,12 @@ pub fn extract_skinned_meshes( SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer) { last_start = last_start.max(skinned_joints.index as usize); - values.push((entity, skinned_joints.to_buffer_index())); + // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per + // entity and so cannot currently be batched. + values.push(( + entity, + (skinned_joints.to_buffer_index(), NoAutomaticBatching), + )); } } @@ -374,63 +355,6 @@ pub fn extract_skinned_meshes( commands.insert_or_spawn_batch(values); } -#[allow(clippy::too_many_arguments)] -pub fn prepare_mesh_uniforms( - mut seen: Local, - mut commands: Commands, - mut previous_len: Local, - render_device: Res, - render_queue: Res, - mut gpu_array_buffer: ResMut>, - views: Query<( - &RenderPhase, - &RenderPhase, - &RenderPhase, - )>, - shadow_views: Query<&RenderPhase>, - meshes: Query<(Entity, &MeshTransforms)>, -) { - gpu_array_buffer.clear(); - seen.clear(); - - let mut indices = Vec::with_capacity(*previous_len); - let mut push_indices = |(mesh, mesh_uniform): (Entity, &MeshTransforms)| { - let index = mesh.index() as usize; - if !seen.contains(index) { - if index >= seen.len() { - seen.grow(index + 1); - } - seen.insert(index); - indices.push((mesh, gpu_array_buffer.push(mesh_uniform.into()))); - } - }; - - for (opaque_phase, transparent_phase, alpha_phase) in &views { - meshes - .iter_many(opaque_phase.iter_entities()) - .for_each(&mut push_indices); - - meshes - .iter_many(transparent_phase.iter_entities()) - .for_each(&mut push_indices); - - meshes - .iter_many(alpha_phase.iter_entities()) - .for_each(&mut push_indices); - } - - for shadow_phase in &shadow_views { - meshes - .iter_many(shadow_phase.iter_entities()) - .for_each(&mut push_indices); - } - - *previous_len = indices.len(); - commands.insert_or_spawn_batch(indices); - - gpu_array_buffer.write_buffer(&render_device, &render_queue); -} - #[derive(Resource, Clone)] pub struct MeshPipeline { pub view_layout: BindGroupLayout, @@ -713,6 +637,26 @@ impl MeshPipeline { } } +impl GetBatchData for MeshPipeline { + type Query = ( + Option<&'static MaterialBindGroupId>, + &'static Handle, + &'static MeshTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = MeshUniform; + + fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { + mesh_transforms.into() + } + + fn get_compare_data( + &(material_bind_group_id, mesh_handle, ..): &QueryItem, + ) -> Self::CompareData { + (material_bind_group_id.copied(), mesh_handle.id()) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -1364,16 +1308,15 @@ impl RenderCommand

for SetMeshBindGroup { type ViewWorldQuery = (); type ItemWorldQuery = ( Read>, - Read>, Option>, Option>, ); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - (mesh, batch_indices, skin_index, morph_index): ROQueryItem, + (mesh, skin_index, morph_index): ROQueryItem, bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -1391,20 +1334,20 @@ impl RenderCommand

for SetMeshBindGroup { }; let mut dynamic_offsets: [u32; 3] = Default::default(); - let mut index_count = 0; - if let Some(mesh_index) = batch_indices.dynamic_offset { - dynamic_offsets[index_count] = mesh_index; - index_count += 1; + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; } if let Some(skin_index) = skin_index { - dynamic_offsets[index_count] = skin_index.index; - index_count += 1; + dynamic_offsets[offset_count] = skin_index.index; + offset_count += 1; } if let Some(morph_index) = morph_index { - dynamic_offsets[index_count] = morph_index.index; - index_count += 1; + dynamic_offsets[offset_count] = morph_index.index; + offset_count += 1; } - pass.set_bind_group(I, bind_group, &dynamic_offsets[0..index_count]); + pass.set_bind_group(I, bind_group, &dynamic_offsets[0..offset_count]); RenderCommandResult::Success } @@ -1414,22 +1357,23 @@ pub struct DrawMesh; impl RenderCommand

for DrawMesh { type Param = SRes>; type ViewWorldQuery = (); - type ItemWorldQuery = (Read>, Read>); + type ItemWorldQuery = Read>; #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - (batch_indices, mesh_handle): ROQueryItem<'_, Self::ItemWorldQuery>, + mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { + let batch_range = item.batch_range(); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); #[cfg(all(feature = "webgl", target_arch = "wasm32"))] pass.set_push_constants( ShaderStages::VERTEX, 0, - &(batch_indices.index as i32).to_le_bytes(), + &(batch_range.start as i32).to_le_bytes(), ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { @@ -1438,13 +1382,10 @@ impl RenderCommand

for DrawMesh { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, batch_indices.index..batch_indices.index + 1); + pass.draw_indexed(0..*count, 0, batch_range.clone()); } GpuBufferInfo::NonIndexed => { - pass.draw( - 0..gpu_mesh.vertex_count, - batch_indices.index..batch_indices.index + 1, - ); + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 753c366726934..5b98de2ad84d9 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -2,6 +2,7 @@ use std::{iter, mem}; use bevy_ecs::prelude::*; use bevy_render::{ + batching::NoAutomaticBatching, mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}, render_resource::{BufferUsages, BufferVec}, renderer::{RenderDevice, RenderQueue}, @@ -89,7 +90,9 @@ pub fn extract_morphs( add_to_alignment::(&mut uniform.buffer); let index = (start * mem::size_of::()) as u32; - values.push((entity, MorphIndex { index })); + // NOTE: Because morph targets require per-morph target texture bindings, they cannot + // currently be batched. + values.push((entity, (MorphIndex { index }, NoAutomaticBatching))); } *previous_len = values.len(); commands.insert_or_spawn_batch(values); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 128609071a398..b1be7a2ef5ccb 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -152,7 +152,8 @@ fn queue_wireframes( pipeline: pipeline_id, draw_function: draw_custom, distance: rangefinder.distance_translation(&mesh_transforms.transform.translation), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); }; diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs new file mode 100644 index 0000000000000..715402b2b4b16 --- /dev/null +++ b/crates/bevy_render/src/batching/mod.rs @@ -0,0 +1,123 @@ +use bevy_ecs::{ + component::Component, + prelude::Res, + query::{Has, QueryItem, ReadOnlyWorldQuery}, + system::{Query, ResMut}, +}; +use bevy_utils::nonmax::NonMaxU32; + +use crate::{ + render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, + render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable}, + renderer::{RenderDevice, RenderQueue}, +}; + +/// Add this component to mesh entities to disable automatic batching +#[derive(Component)] +pub struct NoAutomaticBatching; + +/// Data necessary to be equal for two draw commands to be mergeable +/// +/// This is based on the following assumptions: +/// - Only entities with prepared assets (pipelines, materials, meshes) are +/// queued to phases +/// - View bindings are constant across a phase for a given draw function as +/// phases are per-view +/// - `batch_and_prepare_render_phase` is the only system that performs this +/// batching and has sole responsibility for preparing the per-object data. +/// As such the mesh binding and dynamic offsets are assumed to only be +/// variable as a result of the `batch_and_prepare_render_phase` system, e.g. +/// due to having to split data across separate uniform bindings within the +/// same buffer due to the maximum uniform buffer binding size. +#[derive(PartialEq)] +struct BatchMeta { + /// The pipeline id encompasses all pipeline configuration including vertex + /// buffers and layouts, shaders and their specializations, bind group + /// layouts, etc. + pipeline_id: CachedRenderPipelineId, + /// The draw function id defines the RenderCommands that are called to + /// set the pipeline and bindings, and make the draw command + draw_function_id: DrawFunctionId, + dynamic_offset: Option, + user_data: T, +} + +impl BatchMeta { + fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self { + BatchMeta { + pipeline_id: item.cached_pipeline(), + draw_function_id: item.draw_function(), + dynamic_offset: item.dynamic_offset(), + user_data, + } + } +} + +/// A trait to support getting data used for batching draw commands via phase +/// items. +pub trait GetBatchData { + type Query: ReadOnlyWorldQuery; + /// Data used for comparison between phase items. If the pipeline id, draw + /// function id, per-instance data buffer dynamic offset and this data + /// matches, the draws can be batched. + type CompareData: PartialEq; + /// The per-instance data to be inserted into the [`GpuArrayBuffer`] + /// containing these data for all instances. + type BufferData: GpuArrayBufferable + Sync + Send + 'static; + /// Get the per-instance data to be inserted into the [`GpuArrayBuffer`]. + fn get_buffer_data(query_item: &QueryItem) -> Self::BufferData; + /// Get the data used for comparison when deciding whether draws can be + /// batched. + fn get_compare_data(query_item: &QueryItem) -> Self::CompareData; +} + +/// Batch the items in a render phase. This means comparing metadata needed to draw each phase item +/// and trying to combine the draws into a batch. +pub fn batch_and_prepare_render_phase( + gpu_array_buffer: ResMut>, + mut views: Query<&mut RenderPhase>, + query: Query<(Has, F::Query)>, +) { + let gpu_array_buffer = gpu_array_buffer.into_inner(); + + let mut process_item = |item: &mut I| { + let (no_auto_batching, batch_query_item) = query.get(item.entity()).ok()?; + + let buffer_data = F::get_buffer_data(&batch_query_item); + let buffer_index = gpu_array_buffer.push(buffer_data); + + let index = buffer_index.index.get(); + *item.batch_range_mut() = index..index + 1; + *item.dynamic_offset_mut() = buffer_index.dynamic_offset; + + (!no_auto_batching).then(|| { + let compare_data = F::get_compare_data(&batch_query_item); + BatchMeta::new(item, compare_data) + }) + }; + + for mut phase in &mut views { + let items = phase.items.iter_mut().map(|item| { + let batch_data = process_item(item); + (item.batch_range_mut(), batch_data) + }); + items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| { + if batch_meta.is_some() && prev_batch_meta == batch_meta { + start_range.end = range.end; + (start_range, prev_batch_meta) + } else { + (range, batch_meta) + } + }); + } +} + +pub fn write_batched_instance_buffer( + render_device: Res, + render_queue: Res, + gpu_array_buffer: ResMut>, +) { + let gpu_array_buffer = gpu_array_buffer.into_inner(); + gpu_array_buffer.write_buffer(&render_device, &render_queue); + gpu_array_buffer.clear(); +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index cc62f3fcead84..c72353df47fd6 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -5,6 +5,7 @@ compile_error!("bevy_render cannot compile for a 16-bit platform."); extern crate core; +pub mod batching; pub mod camera; pub mod color; pub mod extract_component; diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 54870cfc260b7..6230d2e1d9cfa 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -29,6 +29,7 @@ mod draw; mod draw_state; mod rangefinder; +use bevy_utils::nonmax::NonMaxU32; pub use draw::*; pub use draw_state::*; pub use rangefinder::*; @@ -38,7 +39,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; -use std::ops::Range; +use std::{ops::Range, slice::SliceIndex}; /// A collection of all rendering instructions, that will be executed by the GPU, for a /// single render phase for a single view. @@ -86,22 +87,7 @@ impl RenderPhase { world: &'w World, view: Entity, ) { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - - let mut index = 0; - while index < self.items.len() { - let item = &self.items[index]; - let batch_size = item.batch_size(); - if batch_size > 0 { - let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); - draw_function.draw(world, render_pass, view, item); - index += batch_size; - } else { - index += 1; - } - } + self.render_range(render_pass, world, view, ..); } /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions. @@ -110,27 +96,27 @@ impl RenderPhase { render_pass: &mut TrackedRenderPass<'w>, world: &'w World, view: Entity, - range: Range, + range: impl SliceIndex<[I], Output = [I]>, ) { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - let items = self .items .get(range) .expect("`Range` provided to `render_range()` is out of bounds"); + let draw_functions = world.resource::>(); + let mut draw_functions = draw_functions.write(); + draw_functions.prepare(world); + let mut index = 0; while index < items.len() { let item = &items[index]; - let batch_size = item.batch_size(); - if batch_size > 0 { + let batch_range = item.batch_range(); + if batch_range.is_empty() { + index += 1; + } else { let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); draw_function.draw(world, render_pass, view, item); - index += batch_size; - } else { - index += 1; + index += batch_range.len(); } } } @@ -182,12 +168,14 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { items.sort_unstable_by_key(|item| item.sort_key()); } - /// The number of items to skip after rendering this [`PhaseItem`]. - /// - /// Items with a `batch_size` of 0 will not be rendered. - fn batch_size(&self) -> usize { - 1 - } + /// The range of instances that the batch covers. After doing a batched draw, batch range + /// length phase items will be skipped. This design is to avoid having to restructure the + /// render phase unnecessarily. + fn batch_range(&self) -> &Range; + fn batch_range_mut(&mut self) -> &mut Range; + + fn dynamic_offset(&self) -> Option; + fn dynamic_offset_mut(&mut self) -> &mut Option; } /// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, diff --git a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs index a9fba2ac7fb42..08c29a8664856 100644 --- a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -3,6 +3,7 @@ use crate::{ render_resource::DynamicUniformBuffer, renderer::{RenderDevice, RenderQueue}, }; +use bevy_utils::nonmax::NonMaxU32; use encase::{ private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer}, ShaderType, @@ -76,8 +77,8 @@ impl BatchedUniformBuffer { pub fn push(&mut self, component: T) -> GpuArrayBufferIndex { let result = GpuArrayBufferIndex { - index: self.temp.0.len() as u32, - dynamic_offset: Some(self.current_offset), + index: NonMaxU32::new(self.temp.0.len() as u32).unwrap(), + dynamic_offset: NonMaxU32::new(self.current_offset), element_type: PhantomData, }; self.temp.0.push(component); diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs index 45eaba4f73246..13694439ba5dc 100644 --- a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -4,6 +4,7 @@ use crate::{ renderer::{RenderDevice, RenderQueue}, }; use bevy_ecs::{prelude::Component, system::Resource}; +use bevy_utils::nonmax::NonMaxU32; use encase::{private::WriteInto, ShaderSize, ShaderType}; use std::{marker::PhantomData, mem}; use wgpu::{BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ShaderStages}; @@ -52,7 +53,7 @@ impl GpuArrayBuffer { match self { GpuArrayBuffer::Uniform(buffer) => buffer.push(value), GpuArrayBuffer::Storage((_, buffer)) => { - let index = buffer.len() as u32; + let index = NonMaxU32::new(buffer.len() as u32).unwrap(); buffer.push(value); GpuArrayBufferIndex { index, @@ -118,12 +119,12 @@ impl GpuArrayBuffer { } /// An index into a [`GpuArrayBuffer`] for a given element. -#[derive(Component)] +#[derive(Component, Clone)] pub struct GpuArrayBufferIndex { /// The index to use in a shader into the array. - pub index: u32, + pub index: NonMaxU32, /// The dynamic offset to use when setting the bind group in a pass. /// Only used on platforms that don't support storage buffers. - pub dynamic_offset: Option, + pub dynamic_offset: Option, pub element_type: PhantomData, } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 605115d2403f8..4b496c7242ac4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -15,7 +15,6 @@ use bevy_ecs::{ }; use bevy_log::error; use bevy_render::{ - extract_component::ExtractComponentPlugin, mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, @@ -24,9 +23,9 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, - PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, texture::FallbackImage, @@ -39,8 +38,8 @@ use std::hash::Hash; use std::marker::PhantomData; use crate::{ - DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, SetMesh2dBindGroup, - SetMesh2dViewBindGroup, + DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, }; /// Materials are used alongside [`Material2dPlugin`] and [`MaterialMesh2dBundle`] @@ -144,8 +143,7 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); + app.init_asset::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -153,7 +151,10 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_materials_2d::) + .add_systems( + ExtractSchedule, + (extract_materials_2d::, extract_material_meshes_2d::), + ) .add_systems( Render, ( @@ -175,6 +176,26 @@ where } } +fn extract_material_meshes_2d( + mut commands: Commands, + mut previous_len: Local, + query: Extract)>>, +) { + let mut values = Vec::with_capacity(*previous_len); + for (entity, view_visibility, material) in &query { + if view_visibility.get() { + // NOTE: Material2dBindGroupId is inserted here to avoid a table move. Upcoming changes + // to use SparseSet for render world entity storage will do this automatically. + values.push(( + entity, + (material.clone_weak(), Material2dBindGroupId::default()), + )); + } + } + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); +} + /// Render pipeline data for a given [`Material2d`] #[derive(Resource)] pub struct Material2dPipeline { @@ -343,7 +364,12 @@ pub fn queue_material2d_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material2d_meshes: Query<(&Handle, &Mesh2dHandle, &Mesh2dUniform)>, + mut material2d_meshes: Query<( + &Handle, + &mut Material2dBindGroupId, + &Mesh2dHandle, + &Mesh2dTransforms, + )>, mut views: Query<( &ExtractedView, &VisibleEntities, @@ -374,8 +400,12 @@ pub fn queue_material2d_meshes( } } for visible_entity in &visible_entities.entities { - let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = - material2d_meshes.get(*visible_entity) + let Ok(( + material2d_handle, + mut material2d_bind_group_id, + mesh2d_handle, + mesh2d_uniform, + )) = material2d_meshes.get_mut(*visible_entity) else { continue; }; @@ -406,7 +436,8 @@ pub fn queue_material2d_meshes( } }; - let mesh_z = mesh2d_uniform.transform.w_axis.z; + *material2d_bind_group_id = material2d.get_bind_group_id(); + let mesh_z = mesh2d_uniform.transform.translation.z; transparent_phase.add(Transparent2d { entity: *visible_entity, draw_function: draw_transparent_pbr, @@ -416,13 +447,17 @@ pub fn queue_material2d_meshes( // -z in front of the camera, the largest distance is -far with values increasing toward the // camera. As such we can just use mesh_z as the distance sort_key: FloatOrd(mesh_z), - // This material is not batched - batch_size: 1, + // Batching is done in batch_and_prepare_render_phase + batch_range: 0..1, + dynamic_offset: None, }); } } } +#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] +pub struct Material2dBindGroupId(Option); + /// Data prepared for a [`Material2d`] instance. pub struct PreparedMaterial2d { pub bindings: Vec, @@ -430,6 +465,12 @@ pub struct PreparedMaterial2d { pub key: T::Data, } +impl PreparedMaterial2d { + pub fn get_bind_group_id(&self) -> Material2dBindGroupId { + Material2dBindGroupId(Some(self.bind_group.id())) + } +} + #[derive(Resource)] pub struct ExtractedMaterials2d { extracted: Vec<(AssetId, M)>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 09b69b296a664..2717acd394d4e 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,15 +1,16 @@ use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, AssetId, Handle}; +use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::{ prelude::*, - query::ROQueryItem, + query::{QueryItem, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Mat4, Vec2}; +use bevy_math::{Affine3, Vec2, Vec4}; use bevy_reflect::Reflect; use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, + batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, @@ -26,10 +27,12 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; +use crate::Material2dBindGroupId; + /// Component for rendering with meshes in the 2d pipeline, usually with a [2d material](crate::Material2d) such as [`ColorMaterial`](crate::ColorMaterial). /// /// It wraps a [`Handle`] to differentiate from the 3d pipelines which use the handles directly as components -#[derive(Default, Clone, Component, Debug, Reflect)] +#[derive(Default, Clone, Component, Debug, Reflect, PartialEq, Eq)] #[reflect(Component)] pub struct Mesh2dHandle(pub Handle); @@ -76,12 +79,6 @@ impl Plugin for Mesh2dRenderPlugin { "mesh2d_types.wgsl", Shader::from_wgsl ); - load_internal_asset!( - app, - MESH2D_BINDINGS_HANDLE, - "mesh2d_bindings.wgsl", - Shader::from_wgsl - ); load_internal_asset!( app, MESH2D_FUNCTIONS_HANDLE, @@ -90,8 +87,6 @@ impl Plugin for Mesh2dRenderPlugin { ); load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); - app.add_plugins(UniformComponentPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() @@ -99,6 +94,10 @@ impl Plugin for Mesh2dRenderPlugin { .add_systems( Render, ( + batch_and_prepare_render_phase:: + .in_set(RenderSet::PrepareResources), + write_batched_instance_buffer:: + .in_set(RenderSet::PrepareResourcesFlush), prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), ), @@ -107,19 +106,69 @@ impl Plugin for Mesh2dRenderPlugin { } fn finish(&self, app: &mut bevy_app::App) { + let mut mesh_bindings_shader_defs = Vec::with_capacity(1); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::(); + if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size( + render_app.world.resource::(), + ) { + mesh_bindings_shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + per_object_buffer_batch_size, + )); + } + + render_app + .insert_resource(GpuArrayBuffer::::new( + render_app.world.resource::(), + )) + .init_resource::(); } + + // Load the mesh_bindings shader module here as it depends on runtime information about + // whether storage buffers are supported, or the maximum uniform buffer binding size. + load_internal_asset!( + app, + MESH2D_BINDINGS_HANDLE, + "mesh2d_bindings.wgsl", + Shader::from_wgsl_with_defs, + mesh_bindings_shader_defs + ); } } -#[derive(Component, ShaderType, Clone)] +#[derive(Component)] +pub struct Mesh2dTransforms { + pub transform: Affine3, + pub flags: u32, +} + +#[derive(ShaderType, Clone)] pub struct Mesh2dUniform { - pub transform: Mat4, - pub inverse_transpose_model: Mat4, + // Affine 4x3 matrix transposed to 3x4 + pub transform: [Vec4; 3], + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + pub inverse_transpose_model_a: [Vec4; 2], + pub inverse_transpose_model_b: f32, pub flags: u32, } +impl From<&Mesh2dTransforms> for Mesh2dUniform { + fn from(mesh_transforms: &Mesh2dTransforms) -> Self { + let (inverse_transpose_model_a, inverse_transpose_model_b) = + mesh_transforms.transform.inverse_transpose_3x3(); + Self { + transform: mesh_transforms.transform.to_transpose(), + inverse_transpose_model_a, + inverse_transpose_model_b, + flags: mesh_transforms.flags, + } + } +} + // NOTE: These must match the bit flags in bevy_sprite/src/mesh2d/mesh2d.wgsl! bitflags::bitflags! { #[repr(transparent)] @@ -139,15 +188,13 @@ pub fn extract_mesh2d( if !view_visibility.get() { continue; } - let transform = transform.compute_matrix(); values.push(( entity, ( Mesh2dHandle(handle.0.clone_weak()), - Mesh2dUniform { + Mesh2dTransforms { + transform: (&transform.affine()).into(), flags: MeshFlags::empty().bits(), - transform, - inverse_transpose_model: transform.inverse().transpose(), }, ), )); @@ -162,13 +209,18 @@ pub struct Mesh2dPipeline { pub mesh_layout: BindGroupLayout, // This dummy white texture is to be used in place of optional textures pub dummy_white_gpu_image: GpuImage, + pub per_object_buffer_batch_size: Option, } impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { - let mut system_state: SystemState<(Res, Res)> = - SystemState::new(world); - let (render_device, default_sampler) = system_state.get_mut(world); + let mut system_state: SystemState<( + Res, + Res, + Res, + )> = SystemState::new(world); + let (render_device, render_queue, default_sampler) = system_state.get_mut(world); + let render_device = render_device.into_inner(); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -197,16 +249,11 @@ impl FromWorld for Mesh2dPipeline { }); let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Mesh2dUniform::min_size()), - }, - count: None, - }], + entries: &[GpuArrayBuffer::::binding_layout( + 0, + ShaderStages::VERTEX_FRAGMENT, + render_device, + )], label: Some("mesh2d_layout"), }); // A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures @@ -219,7 +266,6 @@ impl FromWorld for Mesh2dPipeline { }; let format_size = image.texture_descriptor.format.pixel_size(); - let render_queue = world.resource_mut::(); render_queue.write_texture( ImageCopyTexture { texture: &texture, @@ -253,6 +299,9 @@ impl FromWorld for Mesh2dPipeline { view_layout, mesh_layout, dummy_white_gpu_image, + per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( + render_device, + ), } } } @@ -275,6 +324,26 @@ impl Mesh2dPipeline { } } +impl GetBatchData for Mesh2dPipeline { + type Query = ( + Option<&'static Material2dBindGroupId>, + &'static Mesh2dHandle, + &'static Mesh2dTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = Mesh2dUniform; + + fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { + mesh_transforms.into() + } + + fn get_compare_data( + &(material_bind_group_id, mesh_handle, ..): &QueryItem, + ) -> Self::CompareData { + (material_bind_group_id.copied(), mesh_handle.0.id()) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -477,9 +546,9 @@ pub fn prepare_mesh2d_bind_group( mut commands: Commands, mesh2d_pipeline: Res, render_device: Res, - mesh2d_uniforms: Res>, + mesh2d_uniforms: Res>, ) { - if let Some(binding) = mesh2d_uniforms.uniforms().binding() { + if let Some(binding) = mesh2d_uniforms.binding() { commands.insert_resource(Mesh2dBindGroup { value: render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { @@ -557,20 +626,26 @@ pub struct SetMesh2dBindGroup; impl RenderCommand

for SetMesh2dBindGroup { type Param = SRes; type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - mesh2d_index: &'_ DynamicUniformIndex, + _item_query: (), mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let mut dynamic_offsets: [u32; 1] = Default::default(); + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; + } pass.set_bind_group( I, &mesh2d_bind_group.into_inner().value, - &[mesh2d_index.index()], + &dynamic_offsets[..offset_count], ); RenderCommandResult::Success } @@ -584,14 +659,21 @@ impl RenderCommand

for DrawMesh2d { #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), mesh_handle: ROQueryItem<'w, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let batch_range = item.batch_range(); if let Some(gpu_mesh) = meshes.into_inner().get(&mesh_handle.0) { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + pass.set_push_constants( + ShaderStages::VERTEX, + 0, + &(batch_range.start as i32).to_le_bytes(), + ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, @@ -599,10 +681,10 @@ impl RenderCommand

for DrawMesh2d { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, 0..1); + pass.draw_indexed(0..*count, 0, batch_range.clone()); } GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, 0..1); + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl index 2b99639836d31..003f7dda13af9 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl @@ -8,6 +8,7 @@ #endif struct Vertex { + @builtin(instance_index) instance_index: u32, #ifdef VERTEX_POSITIONS @location(0) position: vec3, #endif @@ -33,20 +34,21 @@ fn vertex(vertex: Vertex) -> MeshVertexOutput { #endif #ifdef VERTEX_POSITIONS + var model = mesh_functions::get_model_matrix(vertex.instance_index); out.world_position = mesh_functions::mesh2d_position_local_to_world( - mesh.model, + model, vec4(vertex.position, 1.0) ); out.position = mesh_functions::mesh2d_position_world_to_clip(out.world_position); #endif #ifdef VERTEX_NORMALS - out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal); + out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal, vertex.instance_index); #endif #ifdef VERTEX_TANGENTS out.world_tangent = mesh_functions::mesh2d_tangent_local_to_world( - mesh.model, + model, vertex.tangent ); #endif diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl index 521ccfa846e43..e673ef23f06b6 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl @@ -1,5 +1,21 @@ #define_import_path bevy_sprite::mesh2d_bindings -#import bevy_sprite::mesh2d_types +#import bevy_sprite::mesh2d_types Mesh2d -@group(2) @binding(0) var mesh: bevy_sprite::mesh2d_types::Mesh2d; +#ifdef MESH_BINDGROUP_1 + +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE +@group(1) @binding(0) var mesh: array; +#else +@group(1) @binding(0) var mesh: array; +#endif // PER_OBJECT_BUFFER_BATCH_SIZE + +#else // MESH_BINDGROUP_1 + +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE +@group(2) @binding(0) var mesh: array; +#else +@group(2) @binding(0) var mesh: array; +#endif // PER_OBJECT_BUFFER_BATCH_SIZE + +#endif // MESH_BINDGROUP_1 diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl index cf8d6e2522068..b936cad10f66f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl @@ -2,6 +2,12 @@ #import bevy_sprite::mesh2d_view_bindings view #import bevy_sprite::mesh2d_bindings mesh +#import bevy_render::instance_index get_instance_index +#import bevy_render::maths affine_to_square, mat2x4_f32_to_mat3x3_unpack + +fn get_model_matrix(instance_index: u32) -> mat4x4 { + return affine_to_square(mesh[get_instance_index(instance_index)].model); +} fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; @@ -19,11 +25,10 @@ fn mesh2d_position_local_to_clip(model: mat4x4, vertex_position: vec4) return mesh2d_position_world_to_clip(world_position); } -fn mesh2d_normal_local_to_world(vertex_normal: vec3) -> vec3 { - return mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz +fn mesh2d_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> vec3 { + return mat2x4_f32_to_mat3x3_unpack( + mesh[instance_index].inverse_transpose_model_a, + mesh[instance_index].inverse_transpose_model_b, ) * vertex_normal; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl index 1de0218112a47..f855707790001 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl @@ -1,8 +1,16 @@ #define_import_path bevy_sprite::mesh2d_types struct Mesh2d { - model: mat4x4, - inverse_transpose_model: mat4x4, + // Affine 4x3 matrix transposed to 3x4 + // Use bevy_render::maths::affine_to_square to unpack + model: mat3x4, + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + // Use bevy_render::maths::mat2x4_f32_to_mat3x3_unpack to unpack + inverse_transpose_model_a: mat2x4, + inverse_transpose_model_b: f32, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, }; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a0a16ea612796..2d5343a867adc 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -565,8 +565,9 @@ pub fn queue_sprites( pipeline: colored_pipeline, entity: *entity, sort_key, - // batch_size will be calculated in prepare_sprites - batch_size: 0, + // batch_range and dynamic_offset will be calculated in prepare_sprites + batch_range: 0..0, + dynamic_offset: None, }); } else { transparent_phase.add(Transparent2d { @@ -574,8 +575,9 @@ pub fn queue_sprites( pipeline, entity: *entity, sort_key, - // batch_size will be calculated in prepare_sprites - batch_size: 0, + // batch_range and dynamic_offset will be calculated in prepare_sprites + batch_range: 0..0, + dynamic_offset: None, }); } } @@ -739,7 +741,9 @@ pub fn prepare_sprites( )); } - transparent_phase.items[batch_item_index].batch_size += 1; + transparent_phase.items[batch_item_index] + .batch_range_mut() + .end += 1; batches.last_mut().unwrap().1.range.end += 1; index += 1; } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c4195a6f41200..d146e53beb79d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -4,6 +4,7 @@ mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_ecs::storage::SparseSet; use bevy_hierarchy::Parent; +use bevy_render::render_phase::PhaseItem; use bevy_render::view::ViewVisibility; use bevy_render::{ExtractSchedule, Render}; use bevy_window::{PrimaryWindow, Window}; @@ -665,8 +666,9 @@ pub fn queue_uinodes( pipeline, entity: *entity, sort_key: FloatOrd(extracted_uinode.stack_index as f32), - // batch_size will be calculated in prepare_uinodes - batch_size: 0, + // batch_range will be calculated in prepare_uinodes + batch_range: 0..0, + dynamic_offset: None, }); } } @@ -892,7 +894,7 @@ pub fn prepare_uinodes( } index += QUAD_INDICES.len() as u32; existing_batch.unwrap().1.range.end = index; - ui_phase.items[batch_item_index].batch_size += 1; + ui_phase.items[batch_item_index].batch_range_mut().end += 1; } else { batch_image_handle = AssetId::invalid(); } diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 1eb3836b8c634..f483c8cf0bd90 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use super::{UiBatch, UiImageBindGroups, UiMeta}; use crate::{prelude::UiCameraConfig, DefaultCameraView}; use bevy_ecs::{ @@ -11,7 +13,7 @@ use bevy_render::{ renderer::*, view::*, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; pub struct UiPassNode { ui_view_query: QueryState< @@ -90,7 +92,8 @@ pub struct TransparentUi { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for TransparentUi { @@ -117,8 +120,23 @@ impl PhaseItem for TransparentUi { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index ba887e8057220..20a4cb32bb99c 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -20,6 +20,7 @@ hashbrown = { version = "0.14", features = ["serde"] } bevy_utils_proc_macros = {version = "0.12.0-dev", path = "macros"} petgraph = "0.6" thiserror = "1.0" +nonmax = "0.5" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 7916caf769476..52f33f31d7dc6 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -34,6 +34,11 @@ pub use thiserror; pub use tracing; pub use uuid::Uuid; +#[allow(missing_docs)] +pub mod nonmax { + pub use nonmax::*; +} + use hashbrown::hash_map::RawEntryMut; use std::{ fmt::Debug, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 780b56f9920fe..f1047a9fb88d4 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -21,7 +21,7 @@ use bevy::{ Extract, Render, RenderApp, RenderSet, }, sprite::{ - DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, + DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, SetMesh2dBindGroup, SetMesh2dViewBindGroup, }, utils::FloatOrd, @@ -148,19 +148,24 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { false => TextureFormat::bevy_default(), }; + // Meshes typically live in bind group 2. Because we are using bind group 1 + // we need to add the MESH_BINDGROUP_1 shader def so that the bindings are correctly + // linked in the shader. + let shader_defs = vec!["MESH_BINDGROUP_1".into()]; + RenderPipelineDescriptor { vertex: VertexState { // Use our custom shader shader: COLORED_MESH2D_SHADER_HANDLE, entry_point: "vertex".into(), - shader_defs: Vec::new(), + shader_defs: shader_defs.clone(), // Use our custom vertex buffer buffers: vec![vertex_layout], }, fragment: Some(FragmentState { // Use our custom shader shader: COLORED_MESH2D_SHADER_HANDLE, - shader_defs: Vec::new(), + shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format, @@ -212,13 +217,12 @@ type DrawColoredMesh2d = ( // using `include_str!()`, or loaded like any other asset with `asset_server.load()`. const COLORED_MESH2D_SHADER: &str = r" // Import the standard 2d mesh uniforms and set their bind groups -#import bevy_sprite::mesh2d_types as MeshTypes +#import bevy_sprite::mesh2d_bindings mesh #import bevy_sprite::mesh2d_functions as MeshFunctions -@group(1) @binding(0) var mesh: MeshTypes::Mesh2d; - // The structure of the vertex buffer is as specified in `specialize()` struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, @location(1) color: u32, }; @@ -235,7 +239,8 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; // Project the world position of the mesh into screen position - out.clip_position = MeshFunctions::mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + let model = MeshFunctions::get_model_matrix(vertex.instance_index); + out.clip_position = MeshFunctions::mesh2d_position_local_to_clip(model, vec4(vertex.position, 1.0)); // Unpack the `u32` from the vertex buffer into the `vec4` used by the fragment shader out.color = vec4((vec4(vertex.color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; return out; @@ -315,7 +320,7 @@ pub fn queue_colored_mesh2d( pipeline_cache: Res, msaa: Res, render_meshes: Res>, - colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With>, + colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dTransforms), With>, mut views: Query<( &VisibleEntities, &mut RenderPhase, @@ -334,7 +339,7 @@ pub fn queue_colored_mesh2d( // Queue all entities visible to that view for visible_entity in &visible_entities.entities { - if let Ok((mesh2d_handle, mesh2d_uniform)) = colored_mesh2d.get(*visible_entity) { + if let Ok((mesh2d_handle, mesh2d_transforms)) = colored_mesh2d.get(*visible_entity) { // Get our specialized pipeline let mut mesh2d_key = mesh_key; if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { @@ -345,7 +350,7 @@ pub fn queue_colored_mesh2d( let pipeline_id = pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key); - let mesh_z = mesh2d_uniform.transform.w_axis.z; + let mesh_z = mesh2d_transforms.transform.translation.z; transparent_phase.add(Transparent2d { entity: *visible_entity, draw_function: draw_colored_mesh2d, @@ -354,7 +359,8 @@ pub fn queue_colored_mesh2d( // in order to get correct transparency sort_key: FloatOrd(mesh_z), // This material is not batched - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 326183d917a0b..d5e751ae0fa1d 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -136,7 +136,8 @@ fn queue_custom( draw_function: draw_custom, distance: rangefinder .distance_translation(&mesh_transforms.transform.translation), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } From 0181d40d83c7e12a3e54846c60b7bff3142cbe0a Mon Sep 17 00:00:00 2001 From: iiYese <83026177+iiYese@users.noreply.github.com> Date: Fri, 22 Sep 2023 07:27:58 +0100 Subject: [PATCH 49/72] Add as_slice to parent (#9871) # Objective - Make it possible to write APIs that require a type or homogenous storage for both `Children` & `Parent` that is agnostic to edge direction. ## Solution - Add a way to get the `Entity` from `Parent` as a slice. --------- Co-authored-by: Alice Cecile Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com> --- crates/bevy_hierarchy/src/components/parent.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 7bccf33ed6e1e..68ba8c7c91ed0 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -23,6 +23,16 @@ impl Parent { pub fn get(&self) -> Entity { self.0 } + + /// Gets the parent [`Entity`] as a slice of length 1. + /// + /// Useful for making APIs that require a type or homogenous storage + /// for both [`Children`] & [`Parent`] that is agnostic to edge direction. + /// + /// [`Children`]: super::children::Children + pub fn as_slice(&self) -> &[Entity] { + std::slice::from_ref(&self.0) + } } // TODO: We need to impl either FromWorld or Default so Parent can be registered as Reflect. From b416d181a7e489df9a2b35f98e9e8038699586f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 23 Sep 2023 08:28:49 +0200 Subject: [PATCH 50/72] don't create windows on winit StartCause::Init event (#9684) # Objective - https://github.com/bevyengine/bevy/pull/7609 broke Android support ``` 8721 8770 I event crates/bevy_winit/src/system.rs:55: Creating new window "App" (0v0) 8721 8769 I RustStdoutStderr: thread '' panicked at 'Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.', winit-0.28.6/src/platform_impl/android/mod.rs:1058:13 ``` ## Solution - Don't create windows on `StartCause::Init` as it's too early --- crates/bevy_winit/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index d6ea22e368388..92e6674674b83 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -366,7 +366,7 @@ pub fn winit_runner(mut app: App) { match event { event::Event::NewEvents(start_cause) => match start_cause { StartCause::Init => { - #[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] + #[cfg(any(target_os = "ios", target_os = "macos"))] { #[cfg(not(target_arch = "wasm32"))] let ( From 22dfa9ee96d70e550aa1b120f63cbad99b7d98f3 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 24 Sep 2023 00:11:59 +0200 Subject: [PATCH 51/72] skybox.wgsl: Fix precision issues (#9909) # Objective - Fixes #9707 ## Solution - At large translations (a few thousand units), the precision of calculating the ray direction from the fragment world position and camera world position seems to break down. Sampling the cubemap only needs the ray direction. As such we can use the view space fragment position, normalise it, rotate it to world space, and use that. --- ## Changelog - Fixed: Jittery skybox at large translations. --- .../bevy_core_pipeline/src/skybox/skybox.wgsl | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl index d3bc9c5faa571..856cde7d96eaf 100644 --- a/crates/bevy_core_pipeline/src/skybox/skybox.wgsl +++ b/crates/bevy_core_pipeline/src/skybox/skybox.wgsl @@ -1,12 +1,36 @@ #import bevy_render::view View +#import bevy_pbr::utils coords_to_viewport_uv @group(0) @binding(0) var skybox: texture_cube; @group(0) @binding(1) var skybox_sampler: sampler; @group(0) @binding(2) var view: View; +fn coords_to_ray_direction(position: vec2, viewport: vec4) -> vec3 { + // Using world positions of the fragment and camera to calculate a ray direction + // break down at large translations. This code only needs to know the ray direction. + // The ray direction is along the direction from the camera to the fragment position. + // In view space, the camera is at the origin, so the view space ray direction is + // along the direction of the fragment position - (0,0,0) which is just the + // fragment position. + // Use the position on the near clipping plane to avoid -inf world position + // because the far plane of an infinite reverse projection is at infinity. + let view_position_homogeneous = view.inverse_projection * vec4( + coords_to_viewport_uv(position, viewport) * vec2(2.0, -2.0) + vec2(-1.0, 1.0), + 1.0, + 1.0, + ); + let view_ray_direction = view_position_homogeneous.xyz / view_position_homogeneous.w; + // Transforming the view space ray direction by the view matrix, transforms the + // direction to world space. Note that the w element is set to 0.0, as this is a + // vector direction, not a position, That causes the matrix multiplication to ignore + // the translations from the view matrix. + let ray_direction = (view.view * vec4(view_ray_direction, 0.0)).xyz; + + return normalize(ray_direction); +} + struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) world_position: vec3, + @builtin(position) position: vec4, }; // 3 | 2. @@ -29,21 +53,14 @@ fn skybox_vertex(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { 0.25, 0.5 ) * 4.0 - vec4(1.0); - // Use the position on the near clipping plane to avoid -inf world position - // because the far plane of an infinite reverse projection is at infinity. - // NOTE: The clip position has a w component equal to 1.0 so we don't need - // to apply a perspective divide to it before inverse-projecting it. - let world_position_homogeneous = view.inverse_view_proj * vec4(clip_position.xy, 1.0, 1.0); - let world_position = world_position_homogeneous.xyz / world_position_homogeneous.w; - return VertexOutput(clip_position, world_position); + return VertexOutput(clip_position); } @fragment fn skybox_fragment(in: VertexOutput) -> @location(0) vec4 { - // The skybox cubemap is sampled along the direction from the camera world - // position, to the fragment world position on the near clipping plane - let ray_direction = in.world_position - view.world_position; - // cube maps are left-handed so we negate the z coordinate + let ray_direction = coords_to_ray_direction(in.position.xy, view.viewport); + + // Cube maps are left-handed so we negate the z coordinate. return textureSample(skybox, skybox_sampler, ray_direction * vec3(1.0, 1.0, -1.0)); } From 8ace2ff9e361dd7ef1bc620b84674def0cb56454 Mon Sep 17 00:00:00 2001 From: James Liu Date: Sat, 23 Sep 2023 17:16:33 -0700 Subject: [PATCH 52/72] Only run event systems if they have tangible work to do (#7728) # Objective Scheduling low cost systems has significant overhead due to task pool contention and the extra machinery to schedule and run them. Event update systems are the prime example of a low cost system, requiring a guaranteed O(1) operation, and there are a *lot* of them. ## Solution Add a run condition to every event system so they only run when there is an event in either of it's two internal Vecs. --- ## Changelog Changed: Event update systems will not run if there are no events to process. ## Migration Guide `Events::update_system` has been split off from the the type and can be found at `bevy_ecs::event::event_update_system`. --------- Co-authored-by: IceSentry --- crates/bevy_app/src/app.rs | 11 ++++++++--- crates/bevy_ecs/examples/events.rs | 2 +- crates/bevy_ecs/src/event.rs | 18 ++++++++++++------ crates/bevy_ecs/src/schedule/condition.rs | 2 +- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 566de2686c9ca..195bc71d5dd4e 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -424,7 +424,7 @@ impl App { /// Setup the application to manage events of type `T`. /// /// This is done by adding a [`Resource`] of type [`Events::`], - /// and inserting an [`update_system`](Events::update_system) into [`First`]. + /// and inserting an [`event_update_system`] into [`First`]. /// /// See [`Events`] for defining events. /// @@ -440,13 +440,18 @@ impl App { /// # /// app.add_event::(); /// ``` + /// + /// [`event_update_system`]: bevy_ecs::event::event_update_system pub fn add_event(&mut self) -> &mut Self where T: Event, { if !self.world.contains_resource::>() { - self.init_resource::>() - .add_systems(First, Events::::update_system); + self.init_resource::>().add_systems( + First, + bevy_ecs::event::event_update_system:: + .run_if(bevy_ecs::event::event_update_condition::), + ); } self } diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index af342f1fca8f0..7be6795880775 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -16,7 +16,7 @@ fn main() { #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct FlushEvents; - schedule.add_systems(Events::::update_system.in_set(FlushEvents)); + schedule.add_systems(bevy_ecs::event::event_update_system::.in_set(FlushEvents)); // Add systems sending and receiving events after the events are flushed. schedule.add_systems(( diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 0494555b04e65..781bbde448cd0 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -103,7 +103,7 @@ struct EventInstance { /// This collection is meant to be paired with a system that calls /// [`Events::update`] exactly once per update/frame. /// -/// [`Events::update_system`] is a system that does this, typically initialized automatically using +/// [`event_update_system`] is a system that does this, typically initialized automatically using /// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event). /// [`EventReader`]s are expected to read events from this collection at least once per loop/frame. /// Events will persist across a single frame boundary and so ordering of event producers and @@ -251,11 +251,6 @@ impl Events { iter.map(|e| e.event) } - /// A system that calls [`Events::update`] once per frame. - pub fn update_system(mut events: ResMut) { - events.update(); - } - #[inline] fn reset_start_event_count(&mut self) { self.events_a.start_event_count = self.event_count; @@ -754,6 +749,17 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> { } } +/// A system that calls [`Events::update`] once per frame. +pub fn event_update_system(mut events: ResMut>) { + events.update(); +} + +/// A run condition that checks if the event's [`event_update_system`] +/// needs to run or not. +pub fn event_update_condition(events: Res>) -> bool { + !events.events_a.is_empty() || !events.events_b.is_empty() +} + #[cfg(test)] mod tests { use crate::system::assert_is_read_only_system; diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 021964311e294..1327d2ea52d17 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -867,7 +867,7 @@ pub mod common_conditions { /// # let mut world = World::new(); /// # world.init_resource::(); /// # world.init_resource::>(); - /// # app.add_systems(Events::::update_system.before(my_system)); + /// # app.add_systems(bevy_ecs::event::event_update_system::.before(my_system)); /// /// app.add_systems( /// my_system.run_if(on_event::()), From ae95ba5278dbb4348ca00152ed4ffe58e7a388f8 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Tue, 26 Sep 2023 01:35:46 +0700 Subject: [PATCH 53/72] Fix typos. (#9922) # Objective - Have docs with fewer typos.1 ## Solution - Fix typos as they are found. --- crates/bevy_ecs/src/reflect/component.rs | 2 +- crates/bevy_pbr/src/pbr_material.rs | 2 +- crates/bevy_render/src/render_asset.rs | 2 +- crates/bevy_ui/src/layout/mod.rs | 4 ++-- crates/bevy_utils/src/label.rs | 2 +- crates/bevy_window/src/window.rs | 2 +- crates/bevy_winit/src/lib.rs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index f91ac8e900225..c86f34b22b3d6 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -279,7 +279,7 @@ impl FromType for ReflectComponent { }, reflect_unchecked_mut: |entity| { // SAFETY: reflect_unchecked_mut is an unsafe function pointer used by - // `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the the component `C` on the `entity` + // `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the component `C` on the `entity` unsafe { entity.get_mut::().map(|c| Mut { value: c.value as &mut dyn Reflect, diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index f979765c5d471..47c861c155789 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -254,7 +254,7 @@ pub struct StandardMaterial { /// - It will look weird on bent/non-planar surfaces. /// - The depth of the pixel does not reflect its visual position, resulting /// in artifacts for depth-dependent features such as fog or SSAO. - /// - For the same reason, the the geometry silhouette will always be + /// - For the same reason, the geometry silhouette will always be /// the one of the actual geometry, not the parallaxed version, resulting /// in awkward looks on intersecting parallaxed surfaces. /// diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 266a3e148c2d4..7dc222e1e701e 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -156,7 +156,7 @@ impl RenderAssets { } } -/// This system extracts all crated or modified assets of the corresponding [`RenderAsset`] type +/// This system extracts all created or modified assets of the corresponding [`RenderAsset`] type /// into the "render world". fn extract_render_asset( mut commands: Commands, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 90603577b1d65..fbf7a986381d6 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -551,8 +551,8 @@ mod tests { world.despawn(ui_entity); - // `ui_layout_system` will recieve a `RemovedComponents` event for `ui_entity` - // and remove `ui_entity` from `ui_node` from the internal layout tree + // `ui_layout_system` will receive a `RemovedComponents` event for `ui_entity` + // and remove `ui_entity` from `ui_node` from the internal layout tree ui_schedule.run(&mut world); let ui_surface = world.resource::(); diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 019965804e58f..9d993c5993809 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -73,7 +73,7 @@ macro_rules! define_boxed_label { ($label_trait_name:ident) => { /// A strongly-typed label. pub trait $label_trait_name: 'static + Send + Sync + ::std::fmt::Debug { - /// Return's the [`TypeId`] of this label, or the the ID of the + /// Return's the [`TypeId`] of this label, or the ID of the /// wrapped label type for `Box` diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 10bdd8fe8b5ca..b77b633dae62b 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1054,7 +1054,7 @@ pub struct EnabledButtons { /// /// macOS note: When [`Window`] `resizable` member is set to `false` /// the maximize button will be disabled regardless of this value. - /// Additionaly, when `resizable` is set to `true` the window will + /// Additionally, when `resizable` is set to `true` the window will /// be maximized when its bar is double-clicked regardless of whether /// the maximize button is enabled or not. pub maximize: bool, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 92e6674674b83..a13c158709bfe 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -67,7 +67,7 @@ pub static ANDROID_APP: std::sync::OnceLock = std::sync::OnceLock::n /// events. /// /// This plugin will add systems and resources that sync with the `winit` backend and also -/// replace the exising [`App`] runner with one that constructs an [event loop](EventLoop) to +/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to /// receive window and input events from the OS. #[derive(Default)] pub struct WinitPlugin; From db1e3d36bc8ebdcdf85b17eecca2ecf022dabd78 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Mon, 25 Sep 2023 20:40:22 +0200 Subject: [PATCH 54/72] Move skin code to a separate module (#9899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective mesh.rs is infamously large. We could split off unrelated code. ## Solution Morph targets are very similar to skinning and have their own module. We move skinned meshes to an independent module like morph targets and give the systems similar names. ### Open questions Should the skinning systems and structs stay public? --- ## Migration Guide Renamed skinning systems, resources and components: - extract_skinned_meshes -> extract_skins - prepare_skinned_meshes -> prepare_skins - SkinnedMeshUniform -> SkinUniform - SkinnedMeshJoints -> SkinIndex --------- Co-authored-by: François Co-authored-by: vero --- crates/bevy_pbr/src/render/mesh.rs | 148 ++------------------ crates/bevy_pbr/src/render/mesh_bindings.rs | 12 +- crates/bevy_pbr/src/render/mod.rs | 2 + crates/bevy_pbr/src/render/morph.rs | 12 +- crates/bevy_pbr/src/render/skin.rs | 139 ++++++++++++++++++ 5 files changed, 166 insertions(+), 147 deletions(-) create mode 100644 crates/bevy_pbr/src/render/skin.rs diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 995c8bfa59f2c..992ae4cf3fa7d 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -6,7 +6,7 @@ use crate::{ CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, AssetId, Assets, Handle}; +use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, prepass::ViewPrepassTextures, @@ -19,15 +19,11 @@ use bevy_ecs::{ query::{QueryItem, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Affine3, Mat4, Vec2, Vec4}; +use bevy_math::{Affine3, Vec2, Vec4}; use bevy_render::{ - batching::{ - batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, - NoAutomaticBatching, - }, + batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{ - skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout, VertexAttributeDescriptor, }, @@ -48,17 +44,13 @@ use bevy_utils::{tracing::error, HashMap, Hashed}; use crate::render::{ morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, + skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform}, MeshLayouts, }; #[derive(Default)] pub struct MeshRenderPlugin; -/// Maximum number of joints supported for skinned meshes. -pub const MAX_JOINTS: usize = 256; -const JOINT_SIZE: usize = std::mem::size_of::(); -pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE; - pub const MESH_VERTEX_OUTPUT: Handle = Handle::weak_from_u128(2645551199423808407); pub const MESH_VIEW_TYPES_HANDLE: Handle = Handle::weak_from_u128(8140454348013264787); pub const MESH_VIEW_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(9076678235888822571); @@ -112,12 +104,12 @@ impl Plugin for MeshRenderPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app - .init_resource::() .init_resource::() + .init_resource::() .init_resource::() .add_systems( ExtractSchedule, - (extract_meshes, extract_skinned_meshes, extract_morphs), + (extract_meshes, extract_skins, extract_morphs), ) .add_systems( Render, @@ -131,7 +123,7 @@ impl Plugin for MeshRenderPlugin { .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: .in_set(RenderSet::PrepareResourcesFlush), - prepare_skinned_meshes.in_set(RenderSet::PrepareResources), + prepare_skins.in_set(RenderSet::PrepareResources), prepare_morphs.in_set(RenderSet::PrepareResources), prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh_view_bind_groups.in_set(RenderSet::PrepareBindGroups), @@ -270,91 +262,6 @@ pub fn extract_meshes( commands.insert_or_spawn_batch(not_caster_commands); } -#[derive(Component)] -pub struct SkinnedMeshJoints { - pub index: u32, -} - -impl SkinnedMeshJoints { - #[inline] - pub fn build( - skin: &SkinnedMesh, - inverse_bindposes: &Assets, - joints: &Query<&GlobalTransform>, - buffer: &mut BufferVec, - ) -> Option { - let inverse_bindposes = inverse_bindposes.get(&skin.inverse_bindposes)?; - let start = buffer.len(); - let target = start + skin.joints.len().min(MAX_JOINTS); - buffer.extend( - joints - .iter_many(&skin.joints) - .zip(inverse_bindposes.iter()) - .take(MAX_JOINTS) - .map(|(joint, bindpose)| joint.affine() * *bindpose), - ); - // iter_many will skip any failed fetches. This will cause it to assign the wrong bones, - // so just bail by truncating to the start. - if buffer.len() != target { - buffer.truncate(start); - return None; - } - - // Pad to 256 byte alignment - while buffer.len() % 4 != 0 { - buffer.push(Mat4::ZERO); - } - Some(Self { - index: start as u32, - }) - } - - /// Updated index to be in address space based on [`SkinnedMeshUniform`] size. - pub fn to_buffer_index(mut self) -> Self { - self.index *= std::mem::size_of::() as u32; - self - } -} - -pub fn extract_skinned_meshes( - mut commands: Commands, - mut previous_len: Local, - mut uniform: ResMut, - query: Extract>, - inverse_bindposes: Extract>>, - joint_query: Extract>, -) { - uniform.buffer.clear(); - let mut values = Vec::with_capacity(*previous_len); - let mut last_start = 0; - - for (entity, view_visibility, skin) in &query { - if !view_visibility.get() { - continue; - } - // PERF: This can be expensive, can we move this to prepare? - if let Some(skinned_joints) = - SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer) - { - last_start = last_start.max(skinned_joints.index as usize); - // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per - // entity and so cannot currently be batched. - values.push(( - entity, - (skinned_joints.to_buffer_index(), NoAutomaticBatching), - )); - } - } - - // Pad out the buffer to ensure that there's enough space for bindings - while uniform.buffer.len() - last_start < MAX_JOINTS { - uniform.buffer.push(Mat4::ZERO); - } - - *previous_len = values.len(); - commands.insert_or_spawn_batch(values); -} - #[derive(Resource, Clone)] pub struct MeshPipeline { pub view_layout: BindGroupLayout, @@ -1043,7 +950,7 @@ pub fn prepare_mesh_bind_group( mesh_pipeline: Res, render_device: Res, mesh_uniforms: Res>, - skinned_mesh_uniform: Res, + skins_uniform: Res, weights_uniform: Res, ) { groups.reset(); @@ -1053,7 +960,7 @@ pub fn prepare_mesh_bind_group( }; groups.model_only = Some(layouts.model_only(&render_device, &model)); - let skin = skinned_mesh_uniform.buffer.buffer(); + let skin = skins_uniform.buffer.buffer(); if let Some(skin) = skin { groups.skinned = Some(layouts.skinned(&render_device, &model, skin)); } @@ -1072,41 +979,6 @@ pub fn prepare_mesh_bind_group( } } -// NOTE: This is using BufferVec because it is using a trick to allow a fixed-size array -// in a uniform buffer to be used like a variable-sized array by only writing the valid data -// into the buffer, knowing the number of valid items starting from the dynamic offset, and -// ignoring the rest, whether they're valid for other dynamic offsets or not. This trick may -// be supported later in encase, and then we should make use of it. - -#[derive(Resource)] -pub struct SkinnedMeshUniform { - pub buffer: BufferVec, -} - -impl Default for SkinnedMeshUniform { - fn default() -> Self { - Self { - buffer: BufferVec::new(BufferUsages::UNIFORM), - } - } -} - -pub fn prepare_skinned_meshes( - render_device: Res, - render_queue: Res, - mut skinned_mesh_uniform: ResMut, -) { - if skinned_mesh_uniform.buffer.is_empty() { - return; - } - - let len = skinned_mesh_uniform.buffer.len(); - skinned_mesh_uniform.buffer.reserve(len, &render_device); - skinned_mesh_uniform - .buffer - .write_buffer(&render_device, &render_queue); -} - #[derive(Component)] pub struct MeshViewBindGroup { pub value: BindGroup, @@ -1308,7 +1180,7 @@ impl RenderCommand

for SetMeshBindGroup { type ViewWorldQuery = (); type ItemWorldQuery = ( Read>, - Option>, + Option>, Option>, ); diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index e46af242fdf85..dcc01e1aa4c8b 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -1,5 +1,6 @@ //! Bind group layout related definitions for the mesh pipeline. +use bevy_math::Mat4; use bevy_render::{ mesh::morph::MAX_MORPH_WEIGHTS, render_resource::{ @@ -9,13 +10,17 @@ use bevy_render::{ renderer::RenderDevice, }; +use crate::render::skin::MAX_JOINTS; + const MORPH_WEIGHT_SIZE: usize = std::mem::size_of::(); pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE; +const JOINT_SIZE: usize = std::mem::size_of::(); +pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE; + /// Individual layout entries. mod layout_entry { - use super::MORPH_BUFFER_SIZE; - use crate::render::mesh::JOINT_BUFFER_SIZE; + use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; use crate::MeshUniform; use bevy_render::{ render_resource::{ @@ -66,8 +71,7 @@ mod layout_entry { /// Individual [`BindGroupEntry`](bevy_render::render_resource::BindGroupEntry) /// for bind groups. mod entry { - use super::MORPH_BUFFER_SIZE; - use crate::render::mesh::JOINT_BUFFER_SIZE; + use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; use bevy_render::render_resource::{ BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView, }; diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 5448850d30723..b9d0d239c3874 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -3,8 +3,10 @@ mod light; pub(crate) mod mesh; mod mesh_bindings; mod morph; +mod skin; pub use fog::*; pub use light::*; pub use mesh::*; pub use mesh_bindings::MeshLayouts; +pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 5b98de2ad84d9..b39064c7f34ba 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -28,16 +28,16 @@ impl Default for MorphUniform { } pub fn prepare_morphs( - device: Res, - queue: Res, + render_device: Res, + render_queue: Res, mut uniform: ResMut, ) { if uniform.buffer.is_empty() { return; } - let buffer = &mut uniform.buffer; - buffer.reserve(buffer.len(), &device); - buffer.write_buffer(&device, &queue); + let len = uniform.buffer.len(); + uniform.buffer.reserve(len, &render_device); + uniform.buffer.write_buffer(&render_device, &render_queue); } const fn can_align(step: usize, target: usize) -> bool { @@ -69,6 +69,8 @@ fn add_to_alignment(buffer: &mut BufferVec) { buffer.extend(iter::repeat_with(T::default).take(ts_to_add)); } +// Notes on implementation: see comment on top of the extract_skins system in skin module. +// This works similarly, but for `f32` instead of `Mat4` pub fn extract_morphs( mut commands: Commands, mut previous_len: Local, diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs new file mode 100644 index 0000000000000..871f504d3ebe2 --- /dev/null +++ b/crates/bevy_pbr/src/render/skin.rs @@ -0,0 +1,139 @@ +use bevy_asset::Assets; +use bevy_ecs::prelude::*; +use bevy_math::Mat4; +use bevy_render::{ + batching::NoAutomaticBatching, + mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, + render_resource::{BufferUsages, BufferVec}, + renderer::{RenderDevice, RenderQueue}, + view::ViewVisibility, + Extract, +}; +use bevy_transform::prelude::GlobalTransform; + +/// Maximum number of joints supported for skinned meshes. +pub const MAX_JOINTS: usize = 256; + +#[derive(Component)] +pub struct SkinIndex { + pub index: u32, +} +impl SkinIndex { + /// Index to be in address space based on [`SkinUniform`] size. + const fn new(start: usize) -> Self { + SkinIndex { + index: (start * std::mem::size_of::()) as u32, + } + } +} + +// Notes on implementation: see comment on top of the `extract_skins` system. +#[derive(Resource)] +pub struct SkinUniform { + pub buffer: BufferVec, +} +impl Default for SkinUniform { + fn default() -> Self { + Self { + buffer: BufferVec::new(BufferUsages::UNIFORM), + } + } +} + +pub fn prepare_skins( + render_device: Res, + render_queue: Res, + mut uniform: ResMut, +) { + if uniform.buffer.is_empty() { + return; + } + + let len = uniform.buffer.len(); + uniform.buffer.reserve(len, &render_device); + uniform.buffer.write_buffer(&render_device, &render_queue); +} + +// Notes on implementation: +// We define the uniform binding as an array, N> in the shader, +// where N is the maximum number of Mat4s we can fit in the uniform binding, +// which may be as little as 16kB or 64kB. But, we may not need all N. +// We may only need, for example, 10. +// +// If we used uniform buffers ‘normally’ then we would have to write a full +// binding of data for each dynamic offset binding, which is wasteful, makes +// the buffer much larger than it needs to be, and uses more memory bandwidth +// to transfer the data, which then costs frame time So @superdump came up +// with this design: just bind data at the specified offset and interpret +// the data at that offset as an array regardless of what is there. +// +// So instead of writing N Mat4s when you only need 10, you write 10, and +// then pad up to the next dynamic offset alignment. Then write the next. +// And for the last dynamic offset binding, make sure there is a full binding +// of data after it so that the buffer is of size +// `last dynamic offset` + `array>`. +// +// Then when binding the first dynamic offset, the first 10 entries in the array +// are what you expect, but if you read the 11th you’re reading ‘invalid’ data +// which could be padding or could be from the next binding. +// +// In this way, we can pack ‘variable sized arrays’ into uniform buffer bindings +// which normally only support fixed size arrays. You just have to make sure +// in the shader that you only read the values that are valid for that binding. +pub fn extract_skins( + mut commands: Commands, + mut previous_len: Local, + mut uniform: ResMut, + query: Extract>, + inverse_bindposes: Extract>>, + joints: Extract>, +) { + uniform.buffer.clear(); + + let mut values = Vec::with_capacity(*previous_len); + let mut last_start = 0; + + // PERF: This can be expensive, can we move this to prepare? + for (entity, view_visibility, skin) in &query { + if !view_visibility.get() { + continue; + } + let buffer = &mut uniform.buffer; + let Some(inverse_bindposes) = inverse_bindposes.get(&skin.inverse_bindposes) else { + continue; + }; + let start = buffer.len(); + + let target = start + skin.joints.len().min(MAX_JOINTS); + buffer.extend( + joints + .iter_many(&skin.joints) + .zip(inverse_bindposes.iter()) + .take(MAX_JOINTS) + .map(|(joint, bindpose)| joint.affine() * *bindpose), + ); + // iter_many will skip any failed fetches. This will cause it to assign the wrong bones, + // so just bail by truncating to the start. + if buffer.len() != target { + buffer.truncate(start); + continue; + } + last_start = last_start.max(start); + + // Pad to 256 byte alignment + while buffer.len() % 4 != 0 { + buffer.push(Mat4::ZERO); + } + // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per + // entity and so cannot currently be batched. + values.push((entity, (SkinIndex::new(start), NoAutomaticBatching))); + } + + // Pad out the buffer to ensure that there's enough space for bindings + while uniform.buffer.len() - last_start < MAX_JOINTS { + uniform.buffer.push(Mat4::ZERO); + } + + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); +} From 35de5e608e9441a1d8e01a6fc381ff1b2e943b72 Mon Sep 17 00:00:00 2001 From: Ycy Date: Tue, 26 Sep 2023 02:59:29 +0800 Subject: [PATCH 55/72] register `TextLayoutInfo` and `TextFlags` type. (#9919) derive `Reflect` to `GlyphAtlasInfo`,`PositionedGlyph` and `TextLayoutInfo`. # Objective - I need reflection gets all components of the `TextBundle` and `clone_value` it ## Solution - registry it --- crates/bevy_text/src/font_atlas_set.rs | 3 ++- crates/bevy_text/src/glyph_brush.rs | 3 ++- crates/bevy_text/src/pipeline.rs | 6 +++++- crates/bevy_ui/src/lib.rs | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index f0ca798d8934b..730d978c1477c 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -4,6 +4,7 @@ use bevy_asset::{AssetEvent, AssetId}; use bevy_asset::{Assets, Handle}; use bevy_ecs::prelude::*; use bevy_math::Vec2; +use bevy_reflect::Reflect; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::FloatOrd; @@ -40,7 +41,7 @@ pub struct FontAtlasSet { font_atlases: HashMap>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflect)] pub struct GlyphAtlasInfo { pub texture_atlas: Handle, pub glyph_index: usize, diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index fe1e2d7203a1b..9aab58ee977f8 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -1,6 +1,7 @@ use ab_glyph::{Font as _, FontArc, Glyph, PxScaleFont, ScaleFont as _}; use bevy_asset::{AssetId, Assets}; use bevy_math::{Rect, Vec2}; +use bevy_reflect::Reflect; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::tracing::warn; @@ -158,7 +159,7 @@ impl GlyphBrush { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflect)] pub struct PositionedGlyph { pub position: Vec2, pub size: Vec2, diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 6a83a446479b6..a93aa546f913f 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -6,8 +6,11 @@ use crate::{ use ab_glyph::PxScale; use bevy_asset::{AssetId, Assets, Handle}; use bevy_ecs::component::Component; +use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::system::Resource; use bevy_math::Vec2; +use bevy_reflect::prelude::ReflectDefault; +use bevy_reflect::Reflect; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::HashMap; @@ -22,7 +25,8 @@ pub struct TextPipeline { /// Render information for a corresponding [`Text`](crate::Text) component. /// /// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`]. -#[derive(Component, Clone, Default, Debug)] +#[derive(Component, Clone, Default, Debug, Reflect)] +#[reflect(Component, Default)] pub struct TextLayoutInfo { pub glyphs: Vec, pub logical_size: Vec2, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index ae9f9f41885d9..1cee4a52a1c05 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -14,6 +14,8 @@ pub mod widget; use bevy_derive::{Deref, DerefMut}; use bevy_reflect::Reflect; #[cfg(feature = "bevy_text")] +use bevy_text::TextLayoutInfo; +#[cfg(feature = "bevy_text")] mod accessibility; mod focus; mod geometry; @@ -40,6 +42,8 @@ pub mod prelude { } use crate::prelude::UiCameraConfig; +#[cfg(feature = "bevy_text")] +use crate::widget::TextFlags; use bevy_app::prelude::*; use bevy_asset::Assets; use bevy_ecs::prelude::*; @@ -126,6 +130,10 @@ impl Plugin for UiPlugin { PreUpdate, ui_focus_system.in_set(UiSystem::Focus).after(InputSystem), ); + + #[cfg(feature = "bevy_text")] + app.register_type::() + .register_type::(); // add these systems to front because these must run before transform update systems #[cfg(feature = "bevy_text")] app.add_systems( From 12032cd29647b1c7b512dc7cbaf3bcfa76eb9aa4 Mon Sep 17 00:00:00 2001 From: James Liu Date: Mon, 25 Sep 2023 12:15:37 -0700 Subject: [PATCH 56/72] Directly copy data into uniform buffers (#9865) # Objective This is a minimally disruptive version of #8340. I attempted to update it, but failed due to the scope of the changes added in #8204. Fixes #8307. Partially addresses #4642. As seen in https://github.com/bevyengine/bevy/issues/8284, we're actually copying data twice in Prepare stage systems. Once into a CPU-side intermediate scratch buffer, and once again into a mapped buffer. This is inefficient and effectively doubles the time spent and memory allocated to run these systems. ## Solution Skip the scratch buffer entirely and use `wgpu::Queue::write_buffer_with` to directly write data into mapped buffers. Separately, this also directly uses `wgpu::Limits::min_uniform_buffer_offset_alignment` to set up the alignment when writing to the buffers. Partially addressing the issue raised in #4642. Storage buffers and the abstractions built on top of `DynamicUniformBuffer` will need to come in followup PRs. This may not have a noticeable performance difference in this PR, as the only first-party systems affected by this are view related, and likely are not going to be particularly heavy. --- ## Changelog Added: `DynamicUniformBuffer::get_writer`. Added: `DynamicUniformBufferWriter`. --- crates/bevy_pbr/src/prepass/mod.rs | 19 ++-- crates/bevy_pbr/src/render/fog.rs | 18 +-- crates/bevy_pbr/src/render/light.rs | 16 ++- crates/bevy_render/src/extract_component.rs | 19 ++-- .../src/render_resource/uniform_buffer.rs | 105 +++++++++++++++++- crates/bevy_render/src/view/mod.rs | 17 +-- 6 files changed, 154 insertions(+), 40 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index ca1caf5c44720..9169d1083fde1 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -667,9 +667,16 @@ pub fn prepare_previous_view_projection_uniforms( With, >, ) { - view_uniforms.uniforms.clear(); - - for (entity, camera, maybe_previous_view_proj) in &views { + let views_iter = views.iter(); + let view_count = views_iter.len(); + let Some(mut writer) = + view_uniforms + .uniforms + .get_writer(view_count, &render_device, &render_queue) + else { + return; + }; + for (entity, camera, maybe_previous_view_proj) in views_iter { let view_projection = match maybe_previous_view_proj { Some(previous_view) => previous_view.clone(), None => PreviousViewProjection { @@ -679,13 +686,9 @@ pub fn prepare_previous_view_projection_uniforms( commands .entity(entity) .insert(PreviousViewProjectionUniformOffset { - offset: view_uniforms.uniforms.push(view_projection), + offset: writer.write(&view_projection), }); } - - view_uniforms - .uniforms - .write_buffer(&render_device, &render_queue); } #[derive(Default, Resource)] diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 10fb61ff2bcc9..4df01418f1d30 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -52,9 +52,15 @@ pub fn prepare_fog( mut fog_meta: ResMut, views: Query<(Entity, Option<&FogSettings>), With>, ) { - fog_meta.gpu_fogs.clear(); - - for (entity, fog) in &views { + let views_iter = views.iter(); + let view_count = views_iter.len(); + let Some(mut writer) = fog_meta + .gpu_fogs + .get_writer(view_count, &render_device, &render_queue) + else { + return; + }; + for (entity, fog) in views_iter { let gpu_fog = if let Some(fog) = fog { match &fog.falloff { FogFalloff::Linear { start, end } => GpuFog { @@ -103,13 +109,9 @@ pub fn prepare_fog( // This is later read by `SetMeshViewBindGroup` commands.entity(entity).insert(ViewFogUniformOffset { - offset: fog_meta.gpu_fogs.push(gpu_fog), + offset: writer.write(&gpu_fog), }); } - - fog_meta - .gpu_fogs - .write_buffer(&render_device, &render_queue); } /// Inserted on each `Entity` with an `ExtractedView` to keep track of its offset diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index bad686b92f2f9..c476afc76e1f6 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -667,7 +667,15 @@ pub fn prepare_lights( point_lights: Query<(Entity, &ExtractedPointLight)>, directional_lights: Query<(Entity, &ExtractedDirectionalLight)>, ) { - light_meta.view_gpu_lights.clear(); + let views_iter = views.iter(); + let views_count = views_iter.len(); + let Some(mut view_gpu_lights_writer) = + light_meta + .view_gpu_lights + .get_writer(views_count, &render_device, &render_queue) + else { + return; + }; // Pre-calculate for PointLights let cube_face_projection = @@ -1198,14 +1206,10 @@ pub fn prepare_lights( lights: view_lights, }, ViewLightsUniformOffset { - offset: light_meta.view_gpu_lights.push(gpu_lights), + offset: view_gpu_lights_writer.write(&gpu_lights), }, )); } - - light_meta - .view_gpu_lights - .write_buffer(&render_device, &render_queue); } // this must match CLUSTER_COUNT_SIZE in pbr.wgsl diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index df555d392a96b..4c6e4f8553928 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -132,24 +132,27 @@ fn prepare_uniform_components( ) where C: ShaderType + WriteInto + Clone, { - component_uniforms.uniforms.clear(); - let entities = components - .iter() + let components_iter = components.iter(); + let count = components_iter.len(); + let Some(mut writer) = + component_uniforms + .uniforms + .get_writer(count, &render_device, &render_queue) + else { + return; + }; + let entities = components_iter .map(|(entity, component)| { ( entity, DynamicUniformIndex:: { - index: component_uniforms.uniforms.push(component.clone()), + index: writer.write(component), marker: PhantomData, }, ) }) .collect::>(); commands.insert_or_spawn_batch(entities); - - component_uniforms - .uniforms - .write_buffer(&render_device, &render_queue); } /// This plugin extracts the components into the "render world". diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index 4c1ad61b2aeb8..3876866b639d3 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -1,14 +1,17 @@ -use std::marker::PhantomData; +use std::{marker::PhantomData, num::NonZeroU64}; use crate::{ render_resource::Buffer, renderer::{RenderDevice, RenderQueue}, }; use encase::{ - internal::WriteInto, DynamicUniformBuffer as DynamicUniformBufferWrapper, ShaderType, + internal::{AlignmentValue, BufferMut, WriteInto}, + DynamicUniformBuffer as DynamicUniformBufferWrapper, ShaderType, UniformBuffer as UniformBufferWrapper, }; -use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsages}; +use wgpu::{ + util::BufferInitDescriptor, BindingResource, BufferBinding, BufferDescriptor, BufferUsages, +}; /// Stores data to be transferred to the GPU and made accessible to shaders as a uniform buffer. /// @@ -240,6 +243,67 @@ impl DynamicUniformBuffer { self.changed = true; } + /// Creates a writer that can be used to directly write elements into the target buffer. + /// + /// This method uses less memory and performs fewer memory copies using over [`push`] and [`write_buffer`]. + /// + /// `max_count` *must* be greater than or equal to the number of elements that are to be written to the buffer, or + /// the writer will panic while writing. Dropping the writer will schedule the buffer write into the provided + /// [`RenderQueue`](crate::renderer::RenderQueue). + /// + /// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously + /// allocated does not have enough capacity to hold `max_count` elements, a new GPU-side buffer is created. + /// + /// Returns `None` if there is no allocated GPU-side buffer, and `max_count` is 0. + /// + /// [`push`]: Self::push + /// [`write_buffer`]: Self::write_buffer + #[inline] + pub fn get_writer<'a>( + &'a mut self, + max_count: usize, + device: &RenderDevice, + queue: &'a RenderQueue, + ) -> Option> { + let alignment = + AlignmentValue::new(device.limits().min_uniform_buffer_offset_alignment as u64); + let mut capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0); + let size = alignment + .round_up(T::min_size().get()) + .checked_mul(max_count as u64) + .unwrap(); + + if capacity < size || self.changed { + let buffer = device.create_buffer(&BufferDescriptor { + label: self.label.as_deref(), + usage: self.buffer_usage, + size, + mapped_at_creation: false, + }); + capacity = buffer.size(); + self.buffer = Some(buffer); + self.changed = false; + } + + if let Some(buffer) = self.buffer.as_deref() { + let buffer_view = queue + .write_buffer_with(buffer, 0, NonZeroU64::new(buffer.size())?) + .unwrap(); + Some(DynamicUniformBufferWriter { + buffer: encase::DynamicUniformBuffer::new_with_alignment( + QueueWriteBufferViewWrapper { + capacity: capacity as usize, + buffer_view, + }, + alignment.get(), + ), + _marker: PhantomData, + }) + } else { + None + } + } + /// Queues writing of data from system RAM to VRAM using the [`RenderDevice`](crate::renderer::RenderDevice) /// and the provided [`RenderQueue`](crate::renderer::RenderQueue). /// @@ -268,3 +332,38 @@ impl DynamicUniformBuffer { self.scratch.set_offset(0); } } + +/// A writer that can be used to directly write elements into the target buffer. +/// +/// For more information, see [`DynamicUniformBuffer::get_writer`]. +pub struct DynamicUniformBufferWriter<'a, T> { + buffer: encase::DynamicUniformBuffer>, + _marker: PhantomData T>, +} + +impl<'a, T: ShaderType + WriteInto> DynamicUniformBufferWriter<'a, T> { + pub fn write(&mut self, value: &T) -> u32 { + self.buffer.write(value).unwrap() as u32 + } +} + +/// A wrapper to work around the orphan rule so that [`wgpu::QueueWriteBufferView`] can implement +/// [`encase::internal::BufferMut`]. +struct QueueWriteBufferViewWrapper<'a> { + buffer_view: wgpu::QueueWriteBufferView<'a>, + // Must be kept separately and cannot be retrieved from buffer_view, as the read-only access will + // invoke a panic. + capacity: usize, +} + +impl<'a> BufferMut for QueueWriteBufferViewWrapper<'a> { + #[inline] + fn capacity(&self) -> usize { + self.capacity + } + + #[inline] + fn write(&mut self, offset: usize, val: &[u8; N]) { + self.buffer_view.write(offset, val); + } +} diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 012a62f76d80c..9759684148324 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -356,8 +356,15 @@ pub fn prepare_view_uniforms( Option<&MipBias>, )>, ) { - view_uniforms.uniforms.clear(); - + let view_iter = views.iter(); + let view_count = view_iter.len(); + let Some(mut writer) = + view_uniforms + .uniforms + .get_writer(view_count, &render_device, &render_queue) + else { + return; + }; for (entity, camera, temporal_jitter, mip_bias) in &views { let viewport = camera.viewport.as_vec4(); let unjittered_projection = camera.projection; @@ -380,7 +387,7 @@ pub fn prepare_view_uniforms( }; let view_uniforms = ViewUniformOffset { - offset: view_uniforms.uniforms.push(ViewUniform { + offset: writer.write(&ViewUniform { view_proj, unjittered_view_proj: unjittered_projection * inverse_view, inverse_view_proj: view * inverse_projection, @@ -397,10 +404,6 @@ pub fn prepare_view_uniforms( commands.entity(entity).insert(view_uniforms); } - - view_uniforms - .uniforms - .write_buffer(&render_device, &render_queue); } #[derive(Clone)] From 503b861e3a76fc3bdc00570f7e0bb8dc1a825679 Mon Sep 17 00:00:00 2001 From: Pixelstorm Date: Mon, 25 Sep 2023 20:59:50 +0100 Subject: [PATCH 57/72] Allow using async_io::block_on in bevy_tasks (#9626) # Objective Fixes #9625 ## Solution Adds `async-io` as an optional dependency of `bevy_tasks`. When enabled, this causes calls to `futures_lite::future::block_on` to be replaced with calls to `async_io::block_on`. --- ## Changelog - Added a new `async-io` feature to `bevy_tasks`. When enabled, this causes `bevy_tasks` to use `async-io`'s implemention of `block_on` instead of `futures-lite`'s implementation. You should enable this if you use `async-io` in your application. --- Cargo.toml | 3 +++ crates/bevy_asset/src/processor/mod.rs | 4 ++-- crates/bevy_internal/Cargo.toml | 1 + crates/bevy_tasks/Cargo.toml | 1 + crates/bevy_tasks/src/lib.rs | 6 ++++++ crates/bevy_tasks/src/task_pool.rs | 9 +++++---- docs/cargo_features.md | 1 + examples/async_tasks/async_compute.rs | 4 ++-- 8 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d06f251e497ee..4c417d9eb70af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -199,6 +199,9 @@ serialize = ["bevy_internal/serialize"] # Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread. multi-threaded = ["bevy_internal/multi-threaded"] +# Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io. +async-io = ["bevy_internal/async-io"] + # Wayland display server support wayland = ["bevy_internal/wayland"] diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 2e67be23deca5..1228740bea578 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -166,7 +166,7 @@ impl AssetProcessor { let processor = _processor.clone(); std::thread::spawn(move || { processor.process_assets(); - futures_lite::future::block_on(processor.listen_for_source_change_events()); + bevy_tasks::block_on(processor.listen_for_source_change_events()); }); } } @@ -190,7 +190,7 @@ impl AssetProcessor { }); // This must happen _after_ the scope resolves or it will happen "too early" // Don't move this into the async scope above! process_assets is a blocking/sync function this is fine - futures_lite::future::block_on(self.finish_processing_assets()); + bevy_tasks::block_on(self.finish_processing_assets()); let end_time = std::time::Instant::now(); debug!("Processing finished in {:?}", end_time - start_time); } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 1a71d43819078..41b06c2311ae9 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -63,6 +63,7 @@ shader_format_spirv = ["bevy_render/shader_format_spirv"] serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene?/serialize"] multi-threaded = ["bevy_asset/multi-threaded", "bevy_ecs/multi-threaded", "bevy_tasks/multi-threaded"] +async-io = ["bevy_tasks/async-io"] # Display server protocol support (X11 is enabled by default) wayland = ["bevy_winit/wayland"] diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index d7d2f8fb6c8da..c4607fbcf87ce 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -15,6 +15,7 @@ multi-threaded = [] futures-lite = "1.4.0" async-executor = "1.3.0" async-channel = "1.4.2" +async-io = { version = "1.13.0", optional = true } async-task = "4.2.0" concurrent-queue = "2.0.0" diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index b97d2e9df466b..d4b68e2096bc6 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -28,6 +28,11 @@ mod thread_executor; #[cfg(all(not(target_arch = "wasm32"), feature = "multi-threaded"))] pub use thread_executor::{ThreadExecutor, ThreadExecutorTicker}; +#[cfg(feature = "async-io")] +pub use async_io::block_on; +#[cfg(not(feature = "async-io"))] +pub use futures_lite::future::block_on; + mod iter; pub use iter::ParallelIterator; @@ -35,6 +40,7 @@ pub use iter::ParallelIterator; pub mod prelude { #[doc(hidden)] pub use crate::{ + block_on, iter::ParallelIterator, slice::{ParallelSlice, ParallelSliceMut}, usages::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool}, diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 90afe4b4bc2e3..5562a5abc0a47 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -9,9 +9,10 @@ use std::{ use async_task::FallibleTask; use concurrent_queue::ConcurrentQueue; -use futures_lite::{future, FutureExt}; +use futures_lite::FutureExt; use crate::{ + block_on, thread_executor::{ThreadExecutor, ThreadExecutorTicker}, Task, }; @@ -176,7 +177,7 @@ impl TaskPool { local_executor.tick().await; } }; - future::block_on(ex.run(tick_forever.or(shutdown_rx.recv()))) + block_on(ex.run(tick_forever.or(shutdown_rx.recv()))) }); if let Ok(value) = res { // Use unwrap_err because we expect a Closed error @@ -379,7 +380,7 @@ impl TaskPool { if spawned.is_empty() { Vec::new() } else { - future::block_on(async move { + block_on(async move { let get_results = async { let mut results = Vec::with_capacity(spawned.len()); while let Ok(task) = spawned.pop() { @@ -661,7 +662,7 @@ where T: 'scope, { fn drop(&mut self) { - future::block_on(async { + block_on(async { while let Ok(task) = self.spawned.pop() { task.cancel().await; } diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 9c7ee1701fa5d..f3d618fb0da5e 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -43,6 +43,7 @@ The default feature set enables most of the expected features of a game engine, |feature name|description| |-|-| |accesskit_unix|Enable AccessKit on Unix backends (currently only works with experimental screen readers and forks.)| +|async-io|Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io.| |basis-universal|Basis Universal compressed texture support| |bevy_ci_testing|Enable systems that allow for automated testing on CI| |bevy_dynamic_plugin|Plugin for dynamic loading (using [libloading](https://crates.io/crates/libloading))| diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index 7ab793809776e..9fad4d30a9c2e 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -3,7 +3,7 @@ use bevy::{ prelude::*, - tasks::{AsyncComputeTaskPool, Task}, + tasks::{block_on, AsyncComputeTaskPool, Task}, }; use futures_lite::future; use rand::Rng; @@ -88,7 +88,7 @@ fn handle_tasks( box_material_handle: Res, ) { for (entity, mut task) in &mut transform_tasks { - if let Some(transform) = future::block_on(future::poll_once(&mut task.0)) { + if let Some(transform) = block_on(future::poll_once(&mut task.0)) { // Add our new PbrBundle of components to our tagged entity commands.entity(entity).insert(PbrBundle { mesh: box_mesh_handle.clone(), From bc1f33d50bd747245a1b7fbd09ae17d81fa7cfca Mon Sep 17 00:00:00 2001 From: jpsikstus <52893351+jpsikstus@users.noreply.github.com> Date: Mon, 25 Sep 2023 23:33:42 +0300 Subject: [PATCH 58/72] Add link to `Text2dBundle` in `TextBundle` docs. (#9900) # Objective Some beginners spend time trying to manually set the position of a `TextBundle`, without realizing that `Text2dBundle` exists. ## Solution Mention `Text2dBundle` in the documentation of `TextBundle`. --------- Co-authored-by: Rob Parrett --- crates/bevy_ui/src/node_bundles.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 7488d5dc41271..ac527d2172f80 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -159,6 +159,9 @@ pub struct AtlasImageBundle { #[cfg(feature = "bevy_text")] /// A UI node that is text +/// +/// The positioning of this node is controlled by the UI layout system. If you need manual control, +/// use [`Text2dBundle`](bevy_text::Text2dBundle). #[derive(Bundle, Debug)] pub struct TextBundle { /// Describes the logical size of the node From bc88f33e483d70878fe63db09fbe8a0301034d48 Mon Sep 17 00:00:00 2001 From: piper <107778087+awtterpip@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:35:08 -0500 Subject: [PATCH 59/72] Allow other plugins to create renderer resources (#9925) This is a duplicate of #9632, it was created since I forgot to make a new branch when I first made this PR, so I was having trouble resolving merge conflicts, meaning I had to rebuild my PR. # Objective - Allow other plugins to create the renderer resources. An example of where this would be required is my [OpenXR plugin](https://github.com/awtterpip/bevy_openxr) ## Solution - Changed the bevy RenderPlugin to optionally take precreated render resources instead of a configuration. ## Migration Guide The `RenderPlugin` now takes a `RenderCreation` enum instead of `WgpuSettings`. `RenderSettings::default()` returns `RenderSettings::Automatic(WgpuSettings::default())`. `RenderSettings` also implements `From`. ```rust // before RenderPlugin { wgpu_settings: WgpuSettings { ... }, } // now RenderPlugin { render_creation: RenderCreation::Automatic(WgpuSettings { ... }), } // or RenderPlugin { render_creation: WgpuSettings { ... }.into(), } ``` --------- Co-authored-by: Malek Co-authored-by: Robert Swain --- crates/bevy_render/src/lib.rs | 267 ++++++++++++++----------- crates/bevy_render/src/renderer/mod.rs | 4 +- crates/bevy_render/src/settings.rs | 42 ++++ examples/3d/wireframe.rs | 5 +- examples/app/no_renderer.rs | 15 +- 5 files changed, 205 insertions(+), 128 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index c72353df47fd6..1709c0ea048c5 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -45,7 +45,6 @@ use bevy_hierarchy::ValidParentCheckPlugin; use bevy_window::{PrimaryWindow, RawHandleWrapper}; use globals::GlobalsPlugin; use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; -use wgpu::Instance; use crate::{ camera::CameraPlugin, @@ -53,7 +52,7 @@ use crate::{ render_asset::prepare_assets, render_resource::{PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance}, - settings::WgpuSettings, + settings::RenderCreation, view::{ViewPlugin, WindowRenderPlugin}, }; use bevy_app::{App, AppLabel, Plugin, SubApp}; @@ -68,7 +67,7 @@ use std::{ /// Contains the default Bevy rendering backend based on wgpu. #[derive(Default)] pub struct RenderPlugin { - pub wgpu_settings: WgpuSettings, + pub render_creation: RenderCreation, } /// The labels of the default App rendering sets. @@ -221,7 +220,7 @@ struct FutureRendererResources( RenderQueue, RenderAdapterInfo, RenderAdapter, - Instance, + RenderInstance, )>, >, >, @@ -241,120 +240,84 @@ impl Plugin for RenderPlugin { app.init_asset::() .init_asset_loader::(); - if let Some(backends) = self.wgpu_settings.backends { - let future_renderer_resources_wrapper = Arc::new(Mutex::new(None)); - app.insert_resource(FutureRendererResources( - future_renderer_resources_wrapper.clone(), - )); - - let mut system_state: SystemState>> = - SystemState::new(&mut app.world); - let primary_window = system_state.get(&app.world).get_single().ok().cloned(); - - let settings = self.wgpu_settings.clone(); - let async_renderer = async move { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - dx12_shader_compiler: settings.dx12_shader_compiler.clone(), - }); - let surface = primary_window.map(|wrapper| unsafe { - // SAFETY: Plugins should be set up on the main thread. - let handle = wrapper.get_handle(); - instance - .create_surface(&handle) - .expect("Failed to create wgpu surface") - }); - - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: settings.power_preference, - compatible_surface: surface.as_ref(), - ..Default::default() - }; - - let (device, queue, adapter_info, render_adapter) = - renderer::initialize_renderer(&instance, &settings, &request_adapter_options) - .await; - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_renderer_resources_inner = - future_renderer_resources_wrapper.lock().unwrap(); - *future_renderer_resources_inner = - Some((device, queue, adapter_info, render_adapter, instance)); - }; - // In wasm, spawn a task and detach it for execution - #[cfg(target_arch = "wasm32")] - bevy_tasks::IoTaskPool::get() - .spawn_local(async_renderer) - .detach(); - // Otherwise, just block for it to complete - #[cfg(not(target_arch = "wasm32"))] - futures_lite::future::block_on(async_renderer); - - app.init_resource::(); - - let mut render_app = App::empty(); - render_app.main_schedule_label = Box::new(Render); - - let mut extract_schedule = Schedule::new(ExtractSchedule); - extract_schedule.set_apply_final_deferred(false); - - render_app - .add_schedule(extract_schedule) - .add_schedule(Render::base_schedule()) - .init_resource::() - .insert_resource(app.world.resource::().clone()) - .add_systems(ExtractSchedule, PipelineCache::extract_shaders) - .add_systems( - Render, - ( - // This set applies the commands from the extract schedule while the render schedule - // is running in parallel with the main app. - apply_extract_commands.in_set(RenderSet::ExtractCommands), - ( - PipelineCache::process_pipeline_queue_system.before(render_system), - render_system, - ) - .in_set(RenderSet::Render), - World::clear_entities.in_set(RenderSet::Cleanup), - ), - ); - - let (sender, receiver) = bevy_time::create_time_channels(); - app.insert_resource(receiver); - render_app.insert_resource(sender); - - app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| { - #[cfg(feature = "trace")] - let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered(); - { - #[cfg(feature = "trace")] - let _stage_span = - bevy_utils::tracing::info_span!("reserve_and_flush") - .entered(); - - // reserve all existing main world entities for use in render_app - // they can only be spawned using `get_or_spawn()` - let total_count = main_world.entities().total_count(); - - assert_eq!( - render_app.world.entities().len(), - 0, - "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", - ); - - // This is safe given the clear_entities call in the past frame and the assert above - unsafe { - render_app - .world - .entities_mut() - .flush_and_reserve_invalid_assuming_no_entities(total_count); - } + match &self.render_creation { + RenderCreation::Manual(device, queue, adapter_info, adapter, instance) => { + let future_renderer_resources_wrapper = Arc::new(Mutex::new(Some(( + device.clone(), + queue.clone(), + adapter_info.clone(), + adapter.clone(), + instance.clone(), + )))); + app.insert_resource(FutureRendererResources( + future_renderer_resources_wrapper.clone(), + )); + unsafe { initialize_render_app(app) }; + } + RenderCreation::Automatic(render_creation) => { + if let Some(backends) = render_creation.backends { + let future_renderer_resources_wrapper = Arc::new(Mutex::new(None)); + app.insert_resource(FutureRendererResources( + future_renderer_resources_wrapper.clone(), + )); + + let mut system_state: SystemState< + Query<&RawHandleWrapper, With>, + > = SystemState::new(&mut app.world); + let primary_window = system_state.get(&app.world).get_single().ok().cloned(); + + let settings = render_creation.clone(); + let async_renderer = async move { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends, + dx12_shader_compiler: settings.dx12_shader_compiler.clone(), + }); + let surface = primary_window.map(|wrapper| unsafe { + // SAFETY: Plugins should be set up on the main thread. + let handle = wrapper.get_handle(); + instance + .create_surface(&handle) + .expect("Failed to create wgpu surface") + }); + + let request_adapter_options = wgpu::RequestAdapterOptions { + power_preference: settings.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }; + + let (device, queue, adapter_info, render_adapter) = + renderer::initialize_renderer( + &instance, + &settings, + &request_adapter_options, + ) + .await; + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + let mut future_renderer_resources_inner = + future_renderer_resources_wrapper.lock().unwrap(); + *future_renderer_resources_inner = Some(( + device, + queue, + adapter_info, + render_adapter, + RenderInstance(Arc::new(instance)), + )); + }; + // In wasm, spawn a task and detach it for execution + #[cfg(target_arch = "wasm32")] + bevy_tasks::IoTaskPool::get() + .spawn_local(async_renderer) + .detach(); + // Otherwise, just block for it to complete + #[cfg(not(target_arch = "wasm32"))] + futures_lite::future::block_on(async_renderer); + + unsafe { initialize_render_app(app) }; } - - // run extract schedule - extract(main_world, render_app); - })); - } + } + }; app.add_plugins(( ValidParentCheckPlugin::::default(), @@ -406,7 +369,7 @@ impl Plugin for RenderPlugin { let render_app = app.sub_app_mut(RenderApp); render_app - .insert_resource(RenderInstance(instance)) + .insert_resource(instance) .insert_resource(PipelineCache::new(device.clone())) .insert_resource(device) .insert_resource(queue) @@ -437,6 +400,74 @@ fn extract(main_world: &mut World, render_app: &mut App) { main_world.insert_resource(ScratchMainWorld(scratch_world)); } +/// SAFETY: this function must be called from the main thread. +unsafe fn initialize_render_app(app: &mut App) { + app.init_resource::(); + + let mut render_app = App::empty(); + render_app.main_schedule_label = Box::new(Render); + + let mut extract_schedule = Schedule::new(ExtractSchedule); + extract_schedule.set_apply_final_deferred(false); + + render_app + .add_schedule(extract_schedule) + .add_schedule(Render::base_schedule()) + .init_resource::() + .insert_resource(app.world.resource::().clone()) + .add_systems(ExtractSchedule, PipelineCache::extract_shaders) + .add_systems( + Render, + ( + // This set applies the commands from the extract schedule while the render schedule + // is running in parallel with the main app. + apply_extract_commands.in_set(RenderSet::ExtractCommands), + ( + PipelineCache::process_pipeline_queue_system.before(render_system), + render_system, + ) + .in_set(RenderSet::Render), + World::clear_entities.in_set(RenderSet::Cleanup), + ), + ); + + let (sender, receiver) = bevy_time::create_time_channels(); + app.insert_resource(receiver); + render_app.insert_resource(sender); + + app.insert_sub_app(RenderApp, SubApp::new(render_app, move |main_world, render_app| { + #[cfg(feature = "trace")] + let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered(); + { + #[cfg(feature = "trace")] + let _stage_span = + bevy_utils::tracing::info_span!("reserve_and_flush") + .entered(); + + // reserve all existing main world entities for use in render_app + // they can only be spawned using `get_or_spawn()` + let total_count = main_world.entities().total_count(); + + assert_eq!( + render_app.world.entities().len(), + 0, + "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", + ); + + // This is safe given the clear_entities call in the past frame and the assert above + unsafe { + render_app + .world + .entities_mut() + .flush_and_reserve_invalid_assuming_no_entities(total_count); + } + } + + // run extract schedule + extract(main_world, render_app); + })); +} + /// Applies the commands from the extract schedule. This happens during /// the render schedule rather than during extraction to allow the commands to run in parallel with the /// main app when pipelined rendering is enabled. diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 6a9d9be6e162d..f98eb81189375 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -104,8 +104,8 @@ pub struct RenderAdapter(pub Arc); /// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`], /// as well as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces). -#[derive(Resource, Deref, DerefMut)] -pub struct RenderInstance(pub Instance); +#[derive(Resource, Clone, Deref, DerefMut)] +pub struct RenderInstance(pub Arc); /// The [`AdapterInfo`] of the adapter in use by the renderer. #[derive(Resource, Clone, Deref, DerefMut)] diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index 5f05770c12e28..56b4f6bc526d3 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -1,3 +1,6 @@ +use crate::renderer::{ + RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, +}; use std::borrow::Cow; pub use wgpu::{ @@ -93,6 +96,45 @@ impl Default for WgpuSettings { } } +/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin). +pub enum RenderCreation { + /// Allows renderer resource initialization to happen outside of the rendering plugin. + Manual( + RenderDevice, + RenderQueue, + RenderAdapterInfo, + RenderAdapter, + RenderInstance, + ), + /// Lets the rendering plugin create resources itself. + Automatic(WgpuSettings), +} + +impl RenderCreation { + /// Function to create a [`RenderCreation::Manual`] variant. + pub fn manual( + device: RenderDevice, + queue: RenderQueue, + adapter_info: RenderAdapterInfo, + adapter: RenderAdapter, + instance: RenderInstance, + ) -> Self { + Self::Manual(device, queue, adapter_info, adapter, instance) + } +} + +impl Default for RenderCreation { + fn default() -> Self { + Self::Automatic(Default::default()) + } +} + +impl From for RenderCreation { + fn from(value: WgpuSettings) -> Self { + Self::Automatic(value) + } +} + /// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO` pub fn settings_priority_from_env() -> Option { Some( diff --git a/examples/3d/wireframe.rs b/examples/3d/wireframe.rs index 277ab902a1910..0192b6ee43709 100644 --- a/examples/3d/wireframe.rs +++ b/examples/3d/wireframe.rs @@ -10,10 +10,11 @@ fn main() { App::new() .add_plugins(( DefaultPlugins.set(RenderPlugin { - wgpu_settings: WgpuSettings { + render_creation: WgpuSettings { features: WgpuFeatures::POLYGON_MODE_LINE, ..default() - }, + } + .into(), }), WireframePlugin, )) diff --git a/examples/app/no_renderer.rs b/examples/app/no_renderer.rs index 2c892e4b123fd..fc6aa72294340 100644 --- a/examples/app/no_renderer.rs +++ b/examples/app/no_renderer.rs @@ -11,11 +11,14 @@ use bevy::{ fn main() { App::new() - .add_plugins(DefaultPlugins.set(RenderPlugin { - wgpu_settings: WgpuSettings { - backends: None, - ..default() - }, - })) + .add_plugins( + DefaultPlugins.set(RenderPlugin { + render_creation: WgpuSettings { + backends: None, + ..default() + } + .into(), + }), + ) .run(); } From 7063c86ed4f79ca511269f9cb47cffdb8ceff307 Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Tue, 26 Sep 2023 12:46:24 -0700 Subject: [PATCH 60/72] Fix some typos (#9934) # Objective To celebrate the turning of the seasons, I took a small walk through the codebase guided by the "[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)" VS Code extension and fixed a few typos. --- crates/bevy_asset/src/processor/mod.rs | 4 ++-- crates/bevy_ecs/src/schedule/condition.rs | 2 +- crates/bevy_ecs/src/schedule/config.rs | 2 +- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/system/query.rs | 2 +- crates/bevy_gizmos/src/lib.rs | 2 +- crates/bevy_reflect/src/type_registry.rs | 2 +- crates/bevy_render/src/camera/camera.rs | 2 +- crates/bevy_render/src/view/visibility/mod.rs | 16 ++++++++-------- crates/bevy_render/src/view/window/mod.rs | 2 +- crates/bevy_ui/src/ui_node.rs | 2 +- crates/bevy_utils/macros/src/lib.rs | 8 ++++---- docs-template/EXAMPLE_README.md.tpl | 2 +- examples/3d/parallax_mapping.rs | 2 +- examples/README.md | 2 +- examples/ui/display_and_visibility.rs | 2 +- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 1228740bea578..4019f4ae04dd7 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -817,7 +817,7 @@ impl AssetProcessor { break; } LogEntryError::UnfinishedTransaction(path) => { - debug!("Asset {path:?} did not finish processing. Clearning state for that asset"); + debug!("Asset {path:?} did not finish processing. Clearing state for that asset"); if let Err(err) = self.destination_writer().remove(&path).await { match err { AssetWriterError::Io(err) => { @@ -991,7 +991,7 @@ pub(crate) struct ProcessorAssetInfo { /// * when processing assets in parallel, the processor might read an asset's process_dependencies when processing new versions of those dependencies /// * this second scenario almost certainly isn't possible with the current implementation, but its worth protecting against /// This lock defends against those scenarios by ensuring readers don't read while processed files are being written. And it ensures - /// Because this lock is shared across meta and asset bytes, readers can esure they don't read "old" versions of metadata with "new" asset data. + /// Because this lock is shared across meta and asset bytes, readers can ensure they don't read "old" versions of metadata with "new" asset data. pub(crate) file_transaction_lock: Arc>, status_sender: async_broadcast::Sender, status_receiver: async_broadcast::Receiver, diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 1327d2ea52d17..819dfe448fec6 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1096,7 +1096,7 @@ mod tests { schedule.run(&mut world); assert_eq!(world.resource::().0, 2); - // Run every other cycle oppsite to the last one + // Run every other cycle opposite to the last one schedule.add_systems(increment_counter.run_if(not(every_other_time))); schedule.run(&mut world); diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 76ddc7fbafa32..526ab1ffe3ed7 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -59,7 +59,7 @@ pub type SystemConfig = NodeConfig; /// A collections of generic [`NodeConfig`]s. pub enum NodeConfigs { - /// Configuratin for a single node. + /// Configuration for a single node. NodeConfig(NodeConfig), /// Configuration for a tuple of nested `Configs` instances. Configs { diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 79e4e99916893..48abc58990f18 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1570,7 +1570,7 @@ impl ScheduleGraph { message } - /// convert conflics to human readable format + /// convert conflicts to human readable format pub fn conflicts_to_string<'a>( &'a self, ambiguities: &'a [(NodeId, NodeId, Vec)], diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index e3b1f16a2b314..5a425784878e4 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1107,7 +1107,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns a mutable reference to the component `T` of the given entity. /// - /// In case of a nonexisting entity, mismatched component or missing write acess, a [`QueryComponentError`] is returned instead. + /// In case of a nonexisting entity, mismatched component or missing write access, a [`QueryComponentError`] is returned instead. /// /// # Example /// diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 79f696e1f063e..6ac015551781c 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -149,7 +149,7 @@ pub struct GizmoConfig { pub line_width: f32, /// Apply perspective to gizmo lines. /// - /// This setting only affects 3D, non-orhographic cameras. + /// This setting only affects 3D, non-orthographic cameras. /// /// Defaults to `false`. pub line_perspective: bool, diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index b7ce14d8301c6..f178108eeb249 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -592,7 +592,7 @@ impl FromType for ReflectFromPtr { unsafe { ptr.deref::() as &dyn Reflect } }, from_ptr_mut: |ptr| { - // SAFETY: same as above, but foor `as_reflect_mut`, `from_ptr_mut` and `deref_mut`. + // SAFETY: same as above, but for `as_reflect_mut`, `from_ptr_mut` and `deref_mut`. unsafe { ptr.deref_mut::() as &mut dyn Reflect } }, } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 4403eb76308f9..e8c72c540e666 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -784,7 +784,7 @@ pub fn sort_cameras( } } -/// A subpixel offset to jitter a perspective camera's fustrum by. +/// A subpixel offset to jitter a perspective camera's frustum by. /// /// Useful for temporal rendering techniques. /// diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index ca60da1788f5b..8d0e648641cab 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -116,7 +116,7 @@ impl ViewVisibility { /// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set /// to the proper value in [`CheckVisibility`]. /// - /// You should only manaully set this if you are defining a custom visibility system, + /// You should only manually set this if you are defining a custom visibility system, /// in which case the system should be placed in the [`CheckVisibility`] set. /// For normal user-defined entity visibility, see [`Visibility`]. /// @@ -315,15 +315,15 @@ fn visibility_propagate_system( Some(parent) => visibility_query.get(parent.get()).unwrap().1.get(), }, }; - let (_, mut inherited_visiblity) = visibility_query + let (_, mut inherited_visibility) = visibility_query .get_mut(entity) .expect("With ensures this query will return a value"); // Only update the visibility if it has changed. // This will also prevent the visibility from propagating multiple times in the same frame - // if this entity's visiblity has been updated recursively by its parent. - if inherited_visiblity.get() != is_visible { - inherited_visiblity.0 = is_visible; + // if this entity's visibility has been updated recursively by its parent. + if inherited_visibility.get() != is_visible { + inherited_visibility.0 = is_visible; // Recursively update the visibility of each child. for &child in children.into_iter().flatten() { @@ -343,7 +343,7 @@ fn propagate_recursive( // We use a result here to use the `?` operator. Ideally we'd use a try block instead ) -> Result<(), ()> { // Get the visibility components for the current entity. - // If the entity does not have the requuired components, just return early. + // If the entity does not have the required components, just return early. let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?; let is_visible = match visibility { @@ -458,7 +458,7 @@ pub fn check_visibility( let (entity, inherited_visibility, mut view_visibility, maybe_entity_mask) = query_item; // Skip computing visibility for entities that are configured to be hidden. - // ViewVisiblity has already been reset in `reset_view_visibility`. + // `ViewVisibility` has already been reset in `reset_view_visibility`. if !inherited_visibility.get() { return; } @@ -646,7 +646,7 @@ mod test { } #[test] - fn visibility_progation_change_detection() { + fn visibility_propagation_change_detection() { let mut world = World::new(); let mut schedule = Schedule::default(); schedule.add_systems(visibility_propagate_system); diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 213f609e5c9e0..88820f8566096 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -230,7 +230,7 @@ pub fn prepare_windows( .entry(window.entity) .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. - // As of wgpu 0.15, only failable if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. + // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. let surface = render_instance .create_surface(&window.handle.get_handle()) .expect("Failed to create wgpu surface"); diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 14080c82228e0..b09c4d48e19f0 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -84,7 +84,7 @@ impl Default for Node { /// /// - [MDN: Basic Concepts of Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout) /// - [A Complete Guide To Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) by CSS Tricks. This is detailed guide with illustrations and comprehensive written explanation of the different Flexbox properties and how they work. -/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flebox in a fun engaging way. +/// - [Flexbox Froggy](https://flexboxfroggy.com/). An interactive tutorial/game that teaches the essential parts of Flexbox in a fun engaging way. /// /// ### CSS Grid /// diff --git a/crates/bevy_utils/macros/src/lib.rs b/crates/bevy_utils/macros/src/lib.rs index f46c869beb6a2..26fb9839919bd 100644 --- a/crates/bevy_utils/macros/src/lib.rs +++ b/crates/bevy_utils/macros/src/lib.rs @@ -62,11 +62,11 @@ impl Parse for AllTuples { /// } /// /// all_tuples!(impl_wrapped_in_foo, 0, 15, T); -/// // impl_wrapp_in_foo!(); -/// // impl_wrapp_in_foo!(P0); -/// // impl_wrapp_in_foo!(P0, P1); +/// // impl_wrapped_in_foo!(); +/// // impl_wrapped_in_foo!(P0); +/// // impl_wrapped_in_foo!(P0, P1); /// // .. -/// // impl_wrapp_in_foo!(P0 .. P14); +/// // impl_wrapped_in_foo!(P0 .. P14); /// ``` /// Multiple parameters. /// ``` diff --git a/docs-template/EXAMPLE_README.md.tpl b/docs-template/EXAMPLE_README.md.tpl index 05e2f26b87a33..7309f21fe56fc 100644 --- a/docs-template/EXAMPLE_README.md.tpl +++ b/docs-template/EXAMPLE_README.md.tpl @@ -312,7 +312,7 @@ mv optimized.wasm examples/wasm/target/lighting_bg.wasm ``` For a small project with a basic 3d model and two lights, -the generated file sizes are, as of Jully 2022 as following: +the generated file sizes are, as of July 2022, as follows: |profile | wasm-opt | no wasm-opt | |----------------------------------|----------|-------------| diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 8260f9b3f3022..f30ae3c42e411 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -160,7 +160,7 @@ fn spin(time: Res

for SetMaterialBindGroup { - type Param = SRes>; + type Param = (SRes>, SRes>); type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - material_handle: &'_ Handle, - materials: SystemParamItem<'w, '_, Self::Param>, + _item_query: (), + (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material = materials.into_inner().get(&material_handle.id()).unwrap(); + let materials = materials.into_inner(); + let material_instances = material_instances.into_inner(); + + let Some(material_asset_id) = material_instances.get(&item.entity()) else { + return RenderCommandResult::Failure; + }; + let Some(material) = materials.get(material_asset_id) else { + return RenderCommandResult::Failure; + }; pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } } +#[derive(Resource, Deref, DerefMut)] +pub struct RenderMaterialInstances(EntityHashMap>); + +impl Default for RenderMaterialInstances { + fn default() -> Self { + Self(Default::default()) + } +} + +fn extract_material_meshes( + mut material_instances: ResMut>, + query: Extract)>>, +) { + material_instances.clear(); + for (entity, view_visibility, handle) in &query { + if view_visibility.get() { + material_instances.insert(entity, handle.id()); + } + } +} + const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key @@ -424,12 +431,8 @@ pub fn queue_material_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - mut material_meshes: Query<( - &Handle, - &mut MaterialBindGroupId, - &Handle, - &MeshTransforms, - )>, + mut render_mesh_instances: ResMut, + render_material_instances: Res>, images: Res>, mut views: Query<( &ExtractedView, @@ -493,15 +496,16 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - let Ok((material_handle, mut material_bind_group_id, mesh_handle, mesh_transforms)) = - material_meshes.get_mut(*visible_entity) - else { + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; - let Some(mesh) = render_meshes.get(mesh_handle) else { + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let Some(material) = render_materials.get(&material_handle.id()) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; let mut mesh_key = view_key; @@ -530,9 +534,10 @@ pub fn queue_material_meshes( } }; - *material_bind_group_id = material.get_bind_group_id(); + mesh_instance.material_bind_group_id = material.get_bind_group_id(); - let distance = rangefinder.distance_translation(&mesh_transforms.transform.translation) + let distance = rangefinder + .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; match material.properties.alpha_mode { AlphaMode::Opaque => { diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 9169d1083fde1..372757f935148 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -47,7 +47,8 @@ use bevy_utils::tracing::error; use crate::{ prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, - MeshTransforms, RenderMaterials, SetMaterialBindGroup, SetMeshBindGroup, + RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SetMaterialBindGroup, + SetMeshBindGroup, }; use std::{hash::Hash, marker::PhantomData}; @@ -758,8 +759,9 @@ pub fn queue_prepass_material_meshes( pipeline_cache: Res, msaa: Res, render_meshes: Res>, + render_mesh_instances: Res, render_materials: Res>, - material_meshes: Query<(&Handle, &Handle, &MeshTransforms)>, + render_material_instances: Res>, mut views: Query<( &ExtractedView, &VisibleEntities, @@ -804,16 +806,16 @@ pub fn queue_prepass_material_meshes( let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - let Ok((material_handle, mesh_handle, mesh_transforms)) = - material_meshes.get(*visible_entity) - else { + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; - - let (Some(material), Some(mesh)) = ( - render_materials.get(&material_handle.id()), - render_meshes.get(mesh_handle), - ) else { + let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { + continue; + }; + let Some(material) = render_materials.get(material_asset_id) else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; @@ -849,7 +851,8 @@ pub fn queue_prepass_material_meshes( } }; - let distance = rangefinder.distance_translation(&mesh_transforms.transform.translation) + let distance = rangefinder + .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; match alpha_mode { AlphaMode::Opaque => { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index c476afc76e1f6..feec375ddc3d3 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -3,10 +3,9 @@ use crate::{ CascadeShadowConfig, Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities, DirectionalLight, DirectionalLightShadowMap, DrawPrepass, EnvironmentMapLight, GlobalVisiblePointLights, Material, MaterialPipelineKey, MeshPipeline, MeshPipelineKey, - NotShadowCaster, PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterials, SpotLight, - VisiblePointLights, + PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterialInstances, RenderMaterials, + RenderMeshInstances, SpotLight, VisiblePointLights, }; -use bevy_asset::Handle; use bevy_core_pipeline::core_3d::Transparent3d; use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; @@ -1553,9 +1552,10 @@ pub fn prepare_clusters( pub fn queue_shadows( shadow_draw_functions: Res>, prepass_pipeline: Res>, - casting_meshes: Query<(&Handle, &Handle), Without>, render_meshes: Res>, + render_mesh_instances: Res, render_materials: Res>, + render_material_instances: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, view_lights: Query<(Entity, &ViewLightEntities)>, @@ -1598,15 +1598,22 @@ pub fn queue_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued for entity in visible_entities.iter().copied() { - let Ok((mesh_handle, material_handle)) = casting_meshes.get(entity) else { + let Some(mesh_instance) = render_mesh_instances.get(&entity) else { continue; }; - let Some(mesh) = render_meshes.get(mesh_handle) else { + if !mesh_instance.shadow_caster { + continue; + } + let Some(material_asset_id) = render_material_instances.get(&entity) else { continue; }; - let Some(material) = render_materials.get(&material_handle.id()) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + let mut mesh_key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | MeshPipelineKey::DEPTH_PREPASS; diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 992ae4cf3fa7d..583475bf9a2e2 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -5,7 +5,7 @@ use crate::{ ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; -use bevy_app::Plugin; +use bevy_app::{Plugin, PostUpdate}; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, @@ -14,6 +14,7 @@ use bevy_core_pipeline::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, }, }; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, query::{QueryItem, ROQueryItem}, @@ -21,7 +22,10 @@ use bevy_ecs::{ }; use bevy_math::{Affine3, Vec2, Vec4}; use bevy_render::{ - batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData}, + batching::{ + batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + NoAutomaticBatching, + }, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{ GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout, @@ -40,14 +44,18 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{tracing::error, HashMap, Hashed}; +use bevy_utils::{tracing::error, EntityHashMap, HashMap, Hashed}; use crate::render::{ - morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, - skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform}, + morph::{ + extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, MorphUniform, + }, + skin::{extract_skins, no_automatic_skin_batching, prepare_skins, SkinUniform}, MeshLayouts, }; +use super::skin::SkinIndices; + #[derive(Default)] pub struct MeshRenderPlugin; @@ -102,11 +110,19 @@ impl Plugin for MeshRenderPlugin { load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl); + app.add_systems( + PostUpdate, + (no_automatic_skin_batching, no_automatic_morph_batching), + ); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() .init_resource::() .init_resource::() + .init_resource::() .init_resource::() + .init_resource::() .add_systems( ExtractSchedule, (extract_meshes, extract_skins, extract_morphs), @@ -212,10 +228,24 @@ bitflags::bitflags! { } } +pub struct RenderMeshInstance { + pub transforms: MeshTransforms, + pub mesh_asset_id: AssetId, + pub material_bind_group_id: MaterialBindGroupId, + pub shadow_caster: bool, + pub automatic_batching: bool, +} + +#[derive(Default, Resource, Deref, DerefMut)] +pub struct RenderMeshInstances(EntityHashMap); + +#[derive(Component)] +pub struct Mesh3d; + pub fn extract_meshes( mut commands: Commands, - mut prev_caster_commands_len: Local, - mut prev_not_caster_commands_len: Local, + mut previous_len: Local, + mut render_mesh_instances: ResMut, meshes_query: Extract< Query<( Entity, @@ -225,15 +255,25 @@ pub fn extract_meshes( &Handle, Option>, Option>, + Has, )>, >, ) { - let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len); - let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len); + render_mesh_instances.clear(); + let mut entities = Vec::with_capacity(*previous_len); + let visible_meshes = meshes_query.iter().filter(|(_, vis, ..)| vis.get()); - for (entity, _, transform, previous_transform, handle, not_receiver, not_caster) in - visible_meshes + for ( + entity, + _, + transform, + previous_transform, + handle, + not_receiver, + not_caster, + no_automatic_batching, + ) in visible_meshes { let transform = transform.affine(); let previous_transform = previous_transform.map(|t| t.0).unwrap_or(transform); @@ -250,16 +290,22 @@ pub fn extract_meshes( previous_transform: (&previous_transform).into(), flags: flags.bits(), }; - if not_caster.is_some() { - not_caster_commands.push((entity, (handle.clone_weak(), transforms, NotShadowCaster))); - } else { - caster_commands.push((entity, (handle.clone_weak(), transforms))); - } + // FIXME: Remove this - it is just a workaround to enable rendering to work as + // render commands require an entity to exist at the moment. + entities.push((entity, Mesh3d)); + render_mesh_instances.insert( + entity, + RenderMeshInstance { + mesh_asset_id: handle.id(), + transforms, + shadow_caster: not_caster.is_none(), + material_bind_group_id: MaterialBindGroupId::default(), + automatic_batching: !no_automatic_batching, + }, + ); } - *prev_caster_commands_len = caster_commands.len(); - *prev_not_caster_commands_len = not_caster_commands.len(); - commands.insert_or_spawn_batch(caster_commands); - commands.insert_or_spawn_batch(not_caster_commands); + *previous_len = entities.len(); + commands.insert_or_spawn_batch(entities); } #[derive(Resource, Clone)] @@ -545,22 +591,26 @@ impl MeshPipeline { } impl GetBatchData for MeshPipeline { - type Query = ( - Option<&'static MaterialBindGroupId>, - &'static Handle, - &'static MeshTransforms, - ); - type CompareData = (Option, AssetId); + type Param = SRes; + type Query = Entity; + type QueryFilter = With; + type CompareData = (MaterialBindGroupId, AssetId); type BufferData = MeshUniform; - fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { - mesh_transforms.into() - } - - fn get_compare_data( - &(material_bind_group_id, mesh_handle, ..): &QueryItem, - ) -> Self::CompareData { - (material_bind_group_id.copied(), mesh_handle.id()) + fn get_batch_data( + mesh_instances: &SystemParamItem, + entity: &QueryItem, + ) -> (Self::BufferData, Option) { + let mesh_instance = mesh_instances + .get(entity) + .expect("Failed to find render mesh instance"); + ( + (&mesh_instance.transforms).into(), + mesh_instance.automatic_batching.then_some(( + mesh_instance.material_bind_group_id, + mesh_instance.mesh_asset_id, + )), + ) } } @@ -932,12 +982,12 @@ impl MeshBindGroups { /// Get the `BindGroup` for `GpuMesh` with given `handle_id`. pub fn get( &self, - handle_id: AssetId, + asset_id: AssetId, is_skinned: bool, morph: bool, ) -> Option<&BindGroup> { match (is_skinned, morph) { - (_, true) => self.morph_targets.get(&handle_id), + (_, true) => self.morph_targets.get(&asset_id), (true, false) => self.skinned.as_ref(), (false, false) => self.model_only.as_ref(), } @@ -1176,27 +1226,44 @@ impl RenderCommand

for SetMeshViewBindGroup pub struct SetMeshBindGroup; impl RenderCommand

for SetMeshBindGroup { - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = ( - Read>, - Option>, - Option>, + type Param = ( + SRes, + SRes, + SRes, + SRes, ); + type ViewWorldQuery = (); + type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), - (mesh, skin_index, morph_index): ROQueryItem, - bind_groups: SystemParamItem<'w, '_, Self::Param>, + _item_query: (), + (bind_groups, mesh_instances, skin_indices, morph_indices): SystemParamItem< + 'w, + '_, + Self::Param, + >, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let bind_groups = bind_groups.into_inner(); + let mesh_instances = mesh_instances.into_inner(); + let skin_indices = skin_indices.into_inner(); + let morph_indices = morph_indices.into_inner(); + + let entity = &item.entity(); + + let Some(mesh) = mesh_instances.get(entity) else { + return RenderCommandResult::Success; + }; + let skin_index = skin_indices.get(entity); + let morph_index = morph_indices.get(entity); + let is_skinned = skin_index.is_some(); let is_morphed = morph_index.is_some(); - let Some(bind_group) = bind_groups.get(mesh.id(), is_skinned, is_morphed) else { + let Some(bind_group) = bind_groups.get(mesh.mesh_asset_id, is_skinned, is_morphed) else { error!( "The MeshBindGroups resource wasn't set in the render phase. \ It should be set by the queue_mesh_bind_group system.\n\ @@ -1227,43 +1294,50 @@ impl RenderCommand

for SetMeshBindGroup { pub struct DrawMesh; impl RenderCommand

for DrawMesh { - type Param = SRes>; + type Param = (SRes>, SRes); type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), - mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>, - meshes: SystemParamItem<'w, '_, Self::Param>, + _item_query: (), + (meshes, mesh_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { - let batch_range = item.batch_range(); - pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); - #[cfg(all(feature = "webgl", target_arch = "wasm32"))] - pass.set_push_constants( - ShaderStages::VERTEX, - 0, - &(batch_range.start as i32).to_le_bytes(), - ); - match &gpu_mesh.buffer_info { - GpuBufferInfo::Indexed { - buffer, - index_format, - count, - } => { - pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, batch_range.clone()); - } - GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); - } + let meshes = meshes.into_inner(); + let mesh_instances = mesh_instances.into_inner(); + + let Some(mesh_instance) = mesh_instances.get(&item.entity()) else { + return RenderCommandResult::Failure; + }; + let Some(gpu_mesh) = meshes.get(mesh_instance.mesh_asset_id) else { + return RenderCommandResult::Failure; + }; + + pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); + + let batch_range = item.batch_range(); + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + pass.set_push_constants( + ShaderStages::VERTEX, + 0, + &(batch_range.start as i32).to_le_bytes(), + ); + match &gpu_mesh.buffer_info { + GpuBufferInfo::Indexed { + buffer, + index_format, + count, + } => { + pass.set_index_buffer(buffer.slice(..), 0, *index_format); + pass.draw_indexed(0..*count, 0, batch_range.clone()); + } + GpuBufferInfo::NonIndexed => { + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } - RenderCommandResult::Success - } else { - RenderCommandResult::Failure } + RenderCommandResult::Success } } diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index b39064c7f34ba..61dfef75d5280 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,5 +1,6 @@ use std::{iter, mem}; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_render::{ batching::NoAutomaticBatching, @@ -9,16 +10,22 @@ use bevy_render::{ view::ViewVisibility, Extract, }; +use bevy_utils::EntityHashMap; use bytemuck::Pod; #[derive(Component)] pub struct MorphIndex { pub(super) index: u32, } + +#[derive(Default, Resource, Deref, DerefMut)] +pub struct MorphIndices(EntityHashMap); + #[derive(Resource)] pub struct MorphUniform { pub buffer: BufferVec, } + impl Default for MorphUniform { fn default() -> Self { Self { @@ -43,6 +50,7 @@ pub fn prepare_morphs( const fn can_align(step: usize, target: usize) -> bool { step % target == 0 || target % step == 0 } + const WGPU_MIN_ALIGN: usize = 256; /// Align a [`BufferVec`] to `N` bytes by padding the end with `T::default()` values. @@ -72,15 +80,13 @@ fn add_to_alignment(buffer: &mut BufferVec) { // Notes on implementation: see comment on top of the extract_skins system in skin module. // This works similarly, but for `f32` instead of `Mat4` pub fn extract_morphs( - mut commands: Commands, - mut previous_len: Local, + mut morph_indices: ResMut, mut uniform: ResMut, query: Extract>, ) { + morph_indices.clear(); uniform.buffer.clear(); - let mut values = Vec::with_capacity(*previous_len); - for (entity, view_visibility, morph_weights) in &query { if !view_visibility.get() { continue; @@ -92,10 +98,17 @@ pub fn extract_morphs( add_to_alignment::(&mut uniform.buffer); let index = (start * mem::size_of::()) as u32; - // NOTE: Because morph targets require per-morph target texture bindings, they cannot - // currently be batched. - values.push((entity, (MorphIndex { index }, NoAutomaticBatching))); + morph_indices.insert(entity, MorphIndex { index }); + } +} + +// NOTE: Because morph targets require per-morph target texture bindings, they cannot +// currently be batched. +pub fn no_automatic_morph_batching( + mut commands: Commands, + query: Query, Without)>, +) { + for entity in &query { + commands.entity(entity).insert(NoAutomaticBatching); } - *previous_len = values.len(); - commands.insert_or_spawn_batch(values); } diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 871f504d3ebe2..bfb12fd794427 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,4 +1,5 @@ use bevy_asset::Assets; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_render::{ @@ -10,6 +11,7 @@ use bevy_render::{ Extract, }; use bevy_transform::prelude::GlobalTransform; +use bevy_utils::EntityHashMap; /// Maximum number of joints supported for skinned meshes. pub const MAX_JOINTS: usize = 256; @@ -18,6 +20,7 @@ pub const MAX_JOINTS: usize = 256; pub struct SkinIndex { pub index: u32, } + impl SkinIndex { /// Index to be in address space based on [`SkinUniform`] size. const fn new(start: usize) -> Self { @@ -27,11 +30,15 @@ impl SkinIndex { } } +#[derive(Default, Resource, Deref, DerefMut)] +pub struct SkinIndices(EntityHashMap); + // Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] pub struct SkinUniform { pub buffer: BufferVec, } + impl Default for SkinUniform { fn default() -> Self { Self { @@ -81,16 +88,14 @@ pub fn prepare_skins( // which normally only support fixed size arrays. You just have to make sure // in the shader that you only read the values that are valid for that binding. pub fn extract_skins( - mut commands: Commands, - mut previous_len: Local, + mut skin_indices: ResMut, mut uniform: ResMut, query: Extract>, inverse_bindposes: Extract>>, joints: Extract>, ) { uniform.buffer.clear(); - - let mut values = Vec::with_capacity(*previous_len); + skin_indices.clear(); let mut last_start = 0; // PERF: This can be expensive, can we move this to prepare? @@ -124,16 +129,23 @@ pub fn extract_skins( while buffer.len() % 4 != 0 { buffer.push(Mat4::ZERO); } - // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per - // entity and so cannot currently be batched. - values.push((entity, (SkinIndex::new(start), NoAutomaticBatching))); + + skin_indices.insert(entity, SkinIndex::new(start)); } // Pad out the buffer to ensure that there's enough space for bindings while uniform.buffer.len() - last_start < MAX_JOINTS { uniform.buffer.push(Mat4::ZERO); } +} - *previous_len = values.len(); - commands.insert_or_spawn_batch(values); +// NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per +// entity and so cannot currently be batched. +pub fn no_automatic_skin_batching( + mut commands: Commands, + query: Query, Without)>, +) { + for entity in &query { + commands.entity(entity).insert(NoAutomaticBatching); + } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index b1be7a2ef5ccb..9566afffb9242 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,13 +1,15 @@ -use crate::{DrawMesh, MeshPipelineKey, SetMeshBindGroup, SetMeshViewBindGroup}; -use crate::{MeshPipeline, MeshTransforms}; +use crate::MeshPipeline; +use crate::{ + DrawMesh, MeshPipelineKey, RenderMeshInstance, RenderMeshInstances, SetMeshBindGroup, + SetMeshViewBindGroup, +}; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle}; use bevy_core_pipeline::core_3d::Opaque3d; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; -use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; -use bevy_render::Render; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, @@ -20,7 +22,9 @@ use bevy_render::{ view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderSet, }; +use bevy_render::{Extract, ExtractSchedule, Render}; use bevy_utils::tracing::error; +use bevy_utils::EntityHashSet; pub const WIREFRAME_SHADER_HANDLE: Handle = Handle::weak_from_u128(192598014480025766); @@ -39,15 +43,14 @@ impl Plugin for WireframePlugin { app.register_type::() .register_type::() .init_resource::() - .add_plugins(( - ExtractResourcePlugin::::default(), - ExtractComponentPlugin::::default(), - )); + .add_plugins((ExtractResourcePlugin::::default(),)); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .init_resource::>() + .init_resource::() + .add_systems(ExtractSchedule, extract_wireframes) .add_systems(Render, queue_wireframes.in_set(RenderSet::QueueMeshes)); } } @@ -60,7 +63,7 @@ impl Plugin for WireframePlugin { } /// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled -#[derive(Component, Debug, Clone, Default, ExtractComponent, Reflect)] +#[derive(Component, Debug, Clone, Default, Reflect)] #[reflect(Component, Default)] pub struct Wireframe; @@ -71,6 +74,17 @@ pub struct WireframeConfig { pub global: bool, } +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Wireframes(EntityHashSet); + +fn extract_wireframes( + mut wireframes: ResMut, + query: Extract>>, +) { + wireframes.clear(); + wireframes.extend(&query); +} + #[derive(Resource, Clone)] pub struct WireframePipeline { mesh_pipeline: MeshPipeline, @@ -110,15 +124,13 @@ impl SpecializedMeshPipeline for WireframePipeline { fn queue_wireframes( opaque_3d_draw_functions: Res>, render_meshes: Res>, + render_mesh_instances: Res, + wireframes: Res, wireframe_config: Res, wireframe_pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, msaa: Res, - mut material_meshes: ParamSet<( - Query<(Entity, &Handle, &MeshTransforms)>, - Query<(Entity, &Handle, &MeshTransforms), With>, - )>, mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, ) { let draw_custom = opaque_3d_draw_functions.read().id::(); @@ -127,10 +139,10 @@ fn queue_wireframes( let rangefinder = view.rangefinder3d(); let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); - let add_render_phase = |phase_item: (Entity, &Handle, &MeshTransforms)| { - let (entity, mesh_handle, mesh_transforms) = phase_item; + let add_render_phase = |phase_item: (Entity, &RenderMeshInstance)| { + let (entity, mesh_instance) = phase_item; - let Some(mesh) = render_meshes.get(mesh_handle) else { + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { return; }; let mut key = @@ -151,25 +163,36 @@ fn queue_wireframes( entity, pipeline: pipeline_id, draw_function: draw_custom, - distance: rangefinder.distance_translation(&mesh_transforms.transform.translation), + distance: rangefinder + .distance_translation(&mesh_instance.transforms.transform.translation), batch_range: 0..1, dynamic_offset: None, }); }; if wireframe_config.global { - let query = material_meshes.p0(); visible_entities .entities .iter() - .filter_map(|visible_entity| query.get(*visible_entity).ok()) + .filter_map(|visible_entity| { + render_mesh_instances + .get(visible_entity) + .map(|mesh_instance| (*visible_entity, mesh_instance)) + }) .for_each(add_render_phase); } else { - let query = material_meshes.p1(); visible_entities .entities .iter() - .filter_map(|visible_entity| query.get(*visible_entity).ok()) + .filter_map(|visible_entity| { + if wireframes.contains(visible_entity) { + render_mesh_instances + .get(visible_entity) + .map(|mesh_instance| (*visible_entity, mesh_instance)) + } else { + None + } + }) .for_each(add_render_phase); } } diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 715402b2b4b16..16633b77b8a4f 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -1,8 +1,8 @@ use bevy_ecs::{ component::Component, prelude::Res, - query::{Has, QueryItem, ReadOnlyWorldQuery}, - system::{Query, ResMut}, + query::{QueryItem, ReadOnlyWorldQuery}, + system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem}, }; use bevy_utils::nonmax::NonMaxU32; @@ -56,7 +56,9 @@ impl BatchMeta { /// A trait to support getting data used for batching draw commands via phase /// items. pub trait GetBatchData { + type Param: SystemParam + 'static; type Query: ReadOnlyWorldQuery; + type QueryFilter: ReadOnlyWorldQuery; /// Data used for comparison between phase items. If the pipeline id, draw /// function id, per-instance data buffer dynamic offset and this data /// matches, the draws can be batched. @@ -65,10 +67,13 @@ pub trait GetBatchData { /// containing these data for all instances. type BufferData: GpuArrayBufferable + Sync + Send + 'static; /// Get the per-instance data to be inserted into the [`GpuArrayBuffer`]. - fn get_buffer_data(query_item: &QueryItem) -> Self::BufferData; - /// Get the data used for comparison when deciding whether draws can be - /// batched. - fn get_compare_data(query_item: &QueryItem) -> Self::CompareData; + /// If the instance can be batched, also return the data used for + /// comparison when deciding whether draws can be batched, else return None + /// for the `CompareData`. + fn get_batch_data( + param: &SystemParamItem, + query_item: &QueryItem, + ) -> (Self::BufferData, Option); } /// Batch the items in a render phase. This means comparing metadata needed to draw each phase item @@ -76,24 +81,23 @@ pub trait GetBatchData { pub fn batch_and_prepare_render_phase( gpu_array_buffer: ResMut>, mut views: Query<&mut RenderPhase>, - query: Query<(Has, F::Query)>, + query: Query, + param: StaticSystemParam, ) { let gpu_array_buffer = gpu_array_buffer.into_inner(); + let system_param_item = param.into_inner(); let mut process_item = |item: &mut I| { - let (no_auto_batching, batch_query_item) = query.get(item.entity()).ok()?; + let batch_query_item = query.get(item.entity()).ok()?; - let buffer_data = F::get_buffer_data(&batch_query_item); + let (buffer_data, compare_data) = F::get_batch_data(&system_param_item, &batch_query_item); let buffer_index = gpu_array_buffer.push(buffer_data); let index = buffer_index.index.get(); *item.batch_range_mut() = index..index + 1; *item.dynamic_offset_mut() = buffer_index.dynamic_offset; - (!no_auto_batching).then(|| { - let compare_data = F::get_compare_data(&batch_query_item); - BatchMeta::new(item, compare_data) - }) + compare_data.map(|compare_data| BatchMeta::new(item, compare_data)) }; for mut phase in &mut views { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4b496c7242ac4..74f6073066b31 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -7,11 +7,7 @@ use bevy_core_pipeline::{ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, - query::ROQueryItem, - system::{ - lifetimeless::{Read, SRes}, - SystemParamItem, - }, + system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_log::error; use bevy_render::{ @@ -33,12 +29,12 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; -use bevy_utils::{FloatOrd, HashMap, HashSet}; +use bevy_utils::{EntityHashMap, FloatOrd, HashMap, HashSet}; use std::hash::Hash; use std::marker::PhantomData; use crate::{ - DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, + DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, }; @@ -150,6 +146,7 @@ where .add_render_command::>() .init_resource::>() .init_resource::>() + .init_resource::>() .init_resource::>>() .add_systems( ExtractSchedule, @@ -176,24 +173,25 @@ where } } +#[derive(Resource, Deref, DerefMut)] +pub struct RenderMaterial2dInstances(EntityHashMap>); + +impl Default for RenderMaterial2dInstances { + fn default() -> Self { + Self(Default::default()) + } +} + fn extract_material_meshes_2d( - mut commands: Commands, - mut previous_len: Local, + mut material_instances: ResMut>, query: Extract)>>, ) { - let mut values = Vec::with_capacity(*previous_len); - for (entity, view_visibility, material) in &query { + material_instances.clear(); + for (entity, view_visibility, handle) in &query { if view_visibility.get() { - // NOTE: Material2dBindGroupId is inserted here to avoid a table move. Upcoming changes - // to use SparseSet for render world entity storage will do this automatically. - values.push(( - entity, - (material.clone_weak(), Material2dBindGroupId::default()), - )); + material_instances.insert(entity, handle.id()); } } - *previous_len = values.len(); - commands.insert_or_spawn_batch(values); } /// Render pipeline data for a given [`Material2d`] @@ -322,19 +320,29 @@ pub struct SetMaterial2dBindGroup(PhantomData) impl RenderCommand

for SetMaterial2dBindGroup { - type Param = SRes>; + type Param = ( + SRes>, + SRes>, + ); type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - material2d_handle: ROQueryItem<'_, Self::ItemWorldQuery>, - materials: SystemParamItem<'w, '_, Self::Param>, + _item_query: (), + (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material2d = materials.into_inner().get(&material2d_handle.id()).unwrap(); + let materials = materials.into_inner(); + let material_instances = material_instances.into_inner(); + let Some(material_instance) = material_instances.get(&item.entity()) else { + return RenderCommandResult::Failure; + }; + let Some(material2d) = materials.get(material_instance) else { + return RenderCommandResult::Failure; + }; pass.set_bind_group(I, &material2d.bind_group, &[]); RenderCommandResult::Success } @@ -364,12 +372,8 @@ pub fn queue_material2d_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - mut material2d_meshes: Query<( - &Handle, - &mut Material2dBindGroupId, - &Mesh2dHandle, - &Mesh2dTransforms, - )>, + mut render_mesh_instances: ResMut, + render_material_instances: Res>, mut views: Query<( &ExtractedView, &VisibleEntities, @@ -380,7 +384,7 @@ pub fn queue_material2d_meshes( ) where M::Data: PartialEq + Eq + Hash + Clone, { - if material2d_meshes.is_empty() { + if render_material_instances.is_empty() { return; } @@ -400,19 +404,16 @@ pub fn queue_material2d_meshes( } } for visible_entity in &visible_entities.entities { - let Ok(( - material2d_handle, - mut material2d_bind_group_id, - mesh2d_handle, - mesh2d_uniform, - )) = material2d_meshes.get_mut(*visible_entity) - else { + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; - let Some(material2d) = render_materials.get(&material2d_handle.id()) else { + let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; - let Some(mesh) = render_meshes.get(&mesh2d_handle.0) else { + let Some(material2d) = render_materials.get(material_asset_id) else { + continue; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let mesh_key = @@ -436,8 +437,9 @@ pub fn queue_material2d_meshes( } }; - *material2d_bind_group_id = material2d.get_bind_group_id(); - let mesh_z = mesh2d_uniform.transform.translation.z; + mesh_instance.material_bind_group_id = material2d.get_bind_group_id(); + + let mesh_z = mesh_instance.transforms.transform.translation.z; transparent_phase.add(Transparent2d { entity: *visible_entity, draw_function: draw_transparent_pbr, @@ -580,7 +582,7 @@ pub fn prepare_materials_2d( render_materials.remove(&removed); } - for (handle, material) in std::mem::take(&mut extracted_assets.extracted) { + for (asset_id, material) in std::mem::take(&mut extracted_assets.extracted) { match prepare_material2d( &material, &render_device, @@ -589,10 +591,10 @@ pub fn prepare_materials_2d( &pipeline, ) { Ok(prepared_asset) => { - render_materials.insert(handle, prepared_asset); + render_materials.insert(asset_id, prepared_asset); } Err(AsBindGroupError::RetryNextUpdate) => { - prepare_next_frame.assets.push((handle, material)); + prepare_next_frame.assets.push((asset_id, material)); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 2717acd394d4e..1639b7df97f8d 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -2,6 +2,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, query::{QueryItem, ROQueryItem}, @@ -10,7 +11,10 @@ use bevy_ecs::{ use bevy_math::{Affine3, Vec2, Vec4}; use bevy_reflect::Reflect; use bevy_render::{ - batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData}, + batching::{ + batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + NoAutomaticBatching, + }, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, @@ -26,6 +30,7 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; +use bevy_utils::EntityHashMap; use crate::Material2dBindGroupId; @@ -89,6 +94,7 @@ impl Plugin for Mesh2dRenderPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() .init_resource::>() .add_systems(ExtractSchedule, extract_mesh2d) .add_systems( @@ -178,29 +184,58 @@ bitflags::bitflags! { } } +pub struct RenderMesh2dInstance { + pub transforms: Mesh2dTransforms, + pub mesh_asset_id: AssetId, + pub material_bind_group_id: Material2dBindGroupId, + pub automatic_batching: bool, +} + +#[derive(Default, Resource, Deref, DerefMut)] +pub struct RenderMesh2dInstances(EntityHashMap); + +#[derive(Component)] +pub struct Mesh2d; + pub fn extract_mesh2d( mut commands: Commands, mut previous_len: Local, - query: Extract>, + mut render_mesh_instances: ResMut, + query: Extract< + Query<( + Entity, + &ViewVisibility, + &GlobalTransform, + &Mesh2dHandle, + Has, + )>, + >, ) { - let mut values = Vec::with_capacity(*previous_len); - for (entity, view_visibility, transform, handle) in &query { + render_mesh_instances.clear(); + let mut entities = Vec::with_capacity(*previous_len); + + for (entity, view_visibility, transform, handle, no_automatic_batching) in &query { if !view_visibility.get() { continue; } - values.push(( + // FIXME: Remove this - it is just a workaround to enable rendering to work as + // render commands require an entity to exist at the moment. + entities.push((entity, Mesh2d)); + render_mesh_instances.insert( entity, - ( - Mesh2dHandle(handle.0.clone_weak()), - Mesh2dTransforms { + RenderMesh2dInstance { + transforms: Mesh2dTransforms { transform: (&transform.affine()).into(), flags: MeshFlags::empty().bits(), }, - ), - )); + mesh_asset_id: handle.0.id(), + material_bind_group_id: Material2dBindGroupId::default(), + automatic_batching: !no_automatic_batching, + }, + ); } - *previous_len = values.len(); - commands.insert_or_spawn_batch(values); + *previous_len = entities.len(); + commands.insert_or_spawn_batch(entities); } #[derive(Resource, Clone)] @@ -325,22 +360,26 @@ impl Mesh2dPipeline { } impl GetBatchData for Mesh2dPipeline { - type Query = ( - Option<&'static Material2dBindGroupId>, - &'static Mesh2dHandle, - &'static Mesh2dTransforms, - ); - type CompareData = (Option, AssetId); + type Param = SRes; + type Query = Entity; + type QueryFilter = With; + type CompareData = (Material2dBindGroupId, AssetId); type BufferData = Mesh2dUniform; - fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { - mesh_transforms.into() - } - - fn get_compare_data( - &(material_bind_group_id, mesh_handle, ..): &QueryItem, - ) -> Self::CompareData { - (material_bind_group_id.copied(), mesh_handle.0.id()) + fn get_batch_data( + mesh_instances: &SystemParamItem, + entity: &QueryItem, + ) -> (Self::BufferData, Option) { + let mesh_instance = mesh_instances + .get(entity) + .expect("Failed to find render mesh2d instance"); + ( + (&mesh_instance.transforms).into(), + mesh_instance.automatic_batching.then_some(( + mesh_instance.material_bind_group_id, + mesh_instance.mesh_asset_id, + )), + ) } } @@ -653,43 +692,52 @@ impl RenderCommand

for SetMesh2dBindGroup { pub struct DrawMesh2d; impl RenderCommand

for DrawMesh2d { - type Param = SRes>; + type Param = (SRes>, SRes); type ViewWorldQuery = (); - type ItemWorldQuery = Read; + type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, _view: (), - mesh_handle: ROQueryItem<'w, Self::ItemWorldQuery>, - meshes: SystemParamItem<'w, '_, Self::Param>, + _item_query: (), + (meshes, render_mesh2d_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let meshes = meshes.into_inner(); + let render_mesh2d_instances = render_mesh2d_instances.into_inner(); + + let Some(RenderMesh2dInstance { mesh_asset_id, .. }) = + render_mesh2d_instances.get(&item.entity()) + else { + return RenderCommandResult::Failure; + }; + let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else { + return RenderCommandResult::Failure; + }; + + pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); + let batch_range = item.batch_range(); - if let Some(gpu_mesh) = meshes.into_inner().get(&mesh_handle.0) { - pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); - #[cfg(all(feature = "webgl", target_arch = "wasm32"))] - pass.set_push_constants( - ShaderStages::VERTEX, - 0, - &(batch_range.start as i32).to_le_bytes(), - ); - match &gpu_mesh.buffer_info { - GpuBufferInfo::Indexed { - buffer, - index_format, - count, - } => { - pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, batch_range.clone()); - } - GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); - } + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + pass.set_push_constants( + ShaderStages::VERTEX, + 0, + &(batch_range.start as i32).to_le_bytes(), + ); + match &gpu_mesh.buffer_info { + GpuBufferInfo::Indexed { + buffer, + index_format, + count, + } => { + pass.set_index_buffer(buffer.slice(..), 0, *index_format); + pass.draw_indexed(0..*count, 0, batch_range.clone()); + } + GpuBufferInfo::NonIndexed => { + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } - RenderCommandResult::Success - } else { - RenderCommandResult::Failure } + RenderCommandResult::Success } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 2d5343a867adc..c7d79dcc088e8 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -11,7 +11,6 @@ use bevy_core_pipeline::{ }; use bevy_ecs::{ prelude::*, - storage::SparseSet, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3A, Quat, Rect, Vec2, Vec4}; @@ -34,7 +33,7 @@ use bevy_render::{ Extract, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{FloatOrd, HashMap}; +use bevy_utils::{EntityHashMap, FloatOrd, HashMap}; use bytemuck::{Pod, Zeroable}; use fixedbitset::FixedBitSet; @@ -330,7 +329,7 @@ pub struct ExtractedSprite { #[derive(Resource, Default)] pub struct ExtractedSprites { - pub sprites: SparseSet, + pub sprites: EntityHashMap, } #[derive(Resource, Default)] @@ -641,7 +640,7 @@ pub fn prepare_sprites( // Compatible items share the same entity. for item_index in 0..transparent_phase.items.len() { let item = &transparent_phase.items[item_index]; - let Some(extracted_sprite) = extracted_sprites.sprites.get(item.entity) else { + let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else { // If there is a phase item that is not a sprite, then we must start a new // batch to draw the other phase item(s) and to respect draw order. This can be // done by invalidating the batch_image_handle diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index d146e53beb79d..f962298b9b1d9 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -2,7 +2,6 @@ mod pipeline; mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; -use bevy_ecs::storage::SparseSet; use bevy_hierarchy::Parent; use bevy_render::render_phase::PhaseItem; use bevy_render::view::ViewVisibility; @@ -36,7 +35,7 @@ use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; #[cfg(feature = "bevy_text")] use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; -use bevy_utils::{FloatOrd, HashMap}; +use bevy_utils::{EntityHashMap, FloatOrd, HashMap}; use bytemuck::{Pod, Zeroable}; use std::ops::Range; @@ -164,7 +163,7 @@ pub struct ExtractedUiNode { #[derive(Resource, Default)] pub struct ExtractedUiNodes { - pub uinodes: SparseSet, + pub uinodes: EntityHashMap, } pub fn extract_atlas_uinodes( @@ -733,7 +732,7 @@ pub fn prepare_uinodes( for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; - if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity) { + if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(&item.entity) { let mut existing_batch = batches .last_mut() .filter(|_| batch_image_handle == extracted_uinode.image); diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 52f33f31d7dc6..85e70e60bc541 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -250,6 +250,58 @@ impl PreHashMapExt for PreHashMap Self::Hasher { + EntityHasher::default() + } +} + +/// A very fast hash that is only designed to work on generational indices +/// like `Entity`. It will panic if attempting to hash a type containing +/// non-u64 fields. +#[derive(Debug, Default)] +pub struct EntityHasher { + hash: u64, +} + +// This value comes from rustc-hash (also known as FxHasher) which in turn got +// it from Firefox. It is something like `u64::MAX / N` for an N that gives a +// value close to π and works well for distributing bits for hashing when using +// with a wrapping multiplication. +const FRAC_U64MAX_PI: u64 = 0x517cc1b727220a95; + +impl Hasher for EntityHasher { + fn write(&mut self, _bytes: &[u8]) { + panic!("can only hash u64 using EntityHasher"); + } + + #[inline] + fn write_u64(&mut self, i: u64) { + // Apparently hashbrown's hashmap uses the upper 7 bits for some SIMD + // optimisation that uses those bits for binning. This hash function + // was faster than i | (i << (64 - 7)) in the worst cases, and was + // faster than PassHasher for all cases tested. + self.hash = i | (i.wrapping_mul(FRAC_U64MAX_PI) << 32); + } + + #[inline] + fn finish(&self) -> u64 { + self.hash + } +} + +/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing. +pub type EntityHashMap = hashbrown::HashMap; + +/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing. +pub type EntityHashSet = hashbrown::HashSet; + /// A type which calls a function when dropped. /// This can be used to ensure that cleanup code is run even in case of a panic. /// diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index d5e751ae0fa1d..fc18bb63c73b7 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -6,7 +6,9 @@ use bevy::{ query::QueryItem, system::{lifetimeless::*, SystemParamItem}, }, - pbr::{MeshPipeline, MeshPipelineKey, MeshTransforms, SetMeshBindGroup, SetMeshViewBindGroup}, + pbr::{ + MeshPipeline, MeshPipelineKey, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, + }, prelude::*, render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -113,7 +115,8 @@ fn queue_custom( mut pipelines: ResMut>, pipeline_cache: Res, meshes: Res>, - material_meshes: Query<(Entity, &MeshTransforms, &Handle), With>, + render_mesh_instances: Res, + material_meshes: Query>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions.read().id::(); @@ -123,23 +126,26 @@ fn queue_custom( for (view, mut transparent_phase) in &mut views { let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); let rangefinder = view.rangefinder3d(); - for (entity, mesh_transforms, mesh_handle) in &material_meshes { - if let Some(mesh) = meshes.get(mesh_handle) { - let key = - view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline = pipelines - .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) - .unwrap(); - transparent_phase.add(Transparent3d { - entity, - pipeline, - draw_function: draw_custom, - distance: rangefinder - .distance_translation(&mesh_transforms.transform.translation), - batch_range: 0..1, - dynamic_offset: None, - }); - } + for entity in &material_meshes { + let Some(mesh_instance) = render_mesh_instances.get(&entity) else { + continue; + }; + let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + let key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = pipelines + .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) + .unwrap(); + transparent_phase.add(Transparent3d { + entity, + pipeline, + draw_function: draw_custom, + distance: rangefinder + .distance_translation(&mesh_instance.transforms.transform.translation), + batch_range: 0..1, + dynamic_offset: None, + }); } } } @@ -238,19 +244,22 @@ type DrawCustom = ( pub struct DrawMeshInstanced; impl RenderCommand

for DrawMeshInstanced { - type Param = SRes>; + type Param = (SRes>, SRes); type ViewWorldQuery = (); - type ItemWorldQuery = (Read>, Read); + type ItemWorldQuery = Read; #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - (mesh_handle, instance_buffer): (&'w Handle, &'w InstanceBuffer), - meshes: SystemParamItem<'w, '_, Self::Param>, + instance_buffer: &'w InstanceBuffer, + (meshes, render_mesh_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let gpu_mesh = match meshes.into_inner().get(mesh_handle) { + let Some(mesh_instance) = render_mesh_instances.get(&item.entity()) else { + return RenderCommandResult::Failure; + }; + let gpu_mesh = match meshes.into_inner().get(mesh_instance.mesh_asset_id) { Some(gpu_mesh) => gpu_mesh, None => return RenderCommandResult::Failure, }; diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index cd8c1ad77f3c2..34112ebd7e0c9 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -97,7 +97,8 @@ fn main() { DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "BevyMark".into(), - resolution: (800., 600.).into(), + resolution: WindowResolution::new(1920.0, 1080.0) + .with_scale_factor_override(1.0), present_mode: PresentMode::AutoNoVsync, ..default() }), diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 218757e471c79..740b9bbcc7659 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -16,7 +16,7 @@ use bevy::{ math::{DVec2, DVec3}, prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, - window::{PresentMode, WindowPlugin}, + window::{PresentMode, WindowPlugin, WindowResolution}, }; use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; @@ -70,6 +70,8 @@ fn main() { DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { present_mode: PresentMode::AutoNoVsync, + resolution: WindowResolution::new(1920.0, 1080.0) + .with_scale_factor_override(1.0), ..default() }), ..default() From 20ed3e0e765405ee334a44ce3c49c72bd29ab753 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 27 Sep 2023 19:41:16 -0300 Subject: [PATCH 65/72] =?UTF-8?q?macOS=20Sonoma=20(14.0)=20/=20Xcode=2015.?= =?UTF-8?q?0=20=E2=80=94=20Compatibility=20Fixes=20+=20Docs=20(#9905)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Improve compatibility with macOS Sonoma and Xcode 15.0. ## Solution - Adds the workaround by @ptxmac to ignore the invalid window sizes provided by `winit` on macOS 14.0 - This still provides a slightly wrong content size when resizing (it fails to account for the window title bar, so some content gets clipped at the bottom) but it's _much better_ than crashing. - Adds docs on how to work around the `bindgen` bug on Xcode 15.0. ## Related Issues: - https://github.com/RustAudio/coreaudio-sys/issues/85 - https://github.com/rust-windowing/winit/issues/2876 --- ## Changelog - Added a workaround for a `winit`-related crash under macOS Sonoma (14.0) --------- Co-authored-by: Peter Kristensen --- crates/bevy_winit/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index a13c158709bfe..3947b5020a867 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -441,6 +441,14 @@ pub fn winit_runner(mut app: App) { match event { WindowEvent::Resized(size) => { + // TODO: Remove this once we upgrade winit to a version with the fix + #[cfg(target_os = "macos")] + if size.width == u32::MAX || size.height == u32::MAX { + // HACK to fix a bug on Macos 14 + // https://github.com/rust-windowing/winit/issues/2876 + return; + } + window .resolution .set_physical_resolution(size.width, size.height); From 418405046aff3c1d64e49ddc7fbcefc9a77593e3 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 28 Sep 2023 22:15:55 +0100 Subject: [PATCH 66/72] `text_wrap_debug` scale factor commandline args (#9951) # Objective Add commandline arguments to `text_wrap_debug` to set the window and UI scale factors. --- examples/ui/text_wrap_debug.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/examples/ui/text_wrap_debug.rs b/examples/ui/text_wrap_debug.rs index a7d01e3ab8e1e..1ff439d6bdf5d 100644 --- a/examples/ui/text_wrap_debug.rs +++ b/examples/ui/text_wrap_debug.rs @@ -1,13 +1,41 @@ //! This example demonstrates text wrapping and use of the `LineBreakOn` property. +use argh::FromArgs; use bevy::prelude::*; use bevy::text::BreakLineOn; +use bevy::window::WindowResolution; use bevy::winit::WinitSettings; +#[derive(FromArgs, Resource)] +/// `text_wrap_debug` demonstrates text wrapping and use of the `LineBreakOn` property +struct Args { + #[argh(option)] + /// window scale factor + scale_factor: Option, + + #[argh(option, default = "1.")] + /// ui scale factor + ui_scale: f64, +} + fn main() { + let args: Args = argh::from_env(); + let window = if let Some(scale_factor) = args.scale_factor { + Window { + resolution: WindowResolution::default().with_scale_factor_override(scale_factor), + ..Default::default() + } + } else { + Window::default() + }; + App::new() - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(window), + ..Default::default() + })) .insert_resource(WinitSettings::desktop_app()) + .insert_resource(UiScale(args.ui_scale)) .add_systems(Startup, spawn) .run(); } From 96a7b4a777d717d0a431f15cde8088c0a3ee2879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 29 Sep 2023 00:40:13 +0200 Subject: [PATCH 67/72] run mobile tests on more devices / OS versions (#9936) # Objective - run mobile tests on more devices / OS versions ## Solution - Add more recent iOS devices / OS versions - Add older Android devices / OS versions You can check the results of a recent run on those devices here: https://percy.io/dede4209/Bevy-Mobile-Example/builds/30355307 --- .github/workflows/daily.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 79df092add62a..7e6e7ceef2447 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -89,6 +89,14 @@ jobs: include: - device: "iPhone 13" os_version: "15" + - device: "iPhone 14" + os_version: "16" + - device: "iPhone 15" + os_version: "17" + - device: "Xiaomi Redmi Note 11" + os_version: "11.0" + - device: "Google Pixel 6" + os_version: "12.0" - device: "Samsung Galaxy S23" os_version: "13.0" steps: From edba496697d3918ca5a2110363c502692ef9d2dd Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Thu, 28 Sep 2023 23:42:13 +0100 Subject: [PATCH 68/72] Store both the rounded and unrounded node size in Node (#9923) # Objective Text bounds are computed by the layout algorithm using the text's measurefunc so that text will only wrap after it's used the maximum amount of available horizontal space. When the layout size is returned the layout coordinates are rounded and this sometimes results in the final size of the Node not matching the size computed with the measurefunc. This means that the text may no longer fit the horizontal available space and instead wrap onto a new line. However, no glyphs will be generated for this new line because no vertical space for the extra line was allocated. fixes #9874 ## Solution Store both the rounded and unrounded node sizes in `Node`. Rounding is used to eliminate pixel-wide gaps between nodes that should be touching edge to edge, but this isn't necessary for text nodes as they don't have solid edges. ## Changelog * Added the `rounded_size: Vec2` field to `Node`. * `text_system` uses the unrounded node size when computing a text layout. --------- Co-authored-by: Rob Parrett --- crates/bevy_ui/src/layout/mod.rs | 5 ++++- crates/bevy_ui/src/ui_node.rs | 10 ++++++++++ crates/bevy_ui/src/widget/text.rs | 5 ++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index fbf7a986381d6..a38209e283a8a 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -342,14 +342,17 @@ pub fn ui_layout_system( inverse_target_scale_factor * Vec2::new(layout.location.x, layout.location.y); absolute_location += layout_location; + let rounded_size = round_layout_coords(absolute_location + layout_size) - round_layout_coords(absolute_location); + let rounded_location = round_layout_coords(layout_location) + 0.5 * (rounded_size - parent_size); // only trigger change detection when the new values are different - if node.calculated_size != rounded_size { + if node.calculated_size != rounded_size || node.unrounded_size != layout_size { node.calculated_size = rounded_size; + node.unrounded_size = layout_size; } if transform.translation.truncate() != rounded_location { transform.translation = rounded_location.extend(0.); diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 420058f35c857..d378806bfff3c 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -17,6 +17,9 @@ pub struct Node { /// The size of the node as width and height in logical pixels /// automatically calculated by [`super::layout::ui_layout_system`] pub(crate) calculated_size: Vec2, + /// The unrounded size of the node as width and height in logical pixels + /// automatically calculated by [`super::layout::ui_layout_system`] + pub(crate) unrounded_size: Vec2, } impl Node { @@ -26,6 +29,12 @@ impl Node { self.calculated_size } + /// The calculated node size as width and height in logical pixels before rounding + /// automatically calculated by [`super::layout::ui_layout_system`] + pub const fn unrounded_size(&self) -> Vec2 { + self.unrounded_size + } + /// Returns the size of the node in physical pixels based on the given scale factor and `UiScale`. #[inline] pub fn physical_size(&self, scale_factor: f64, ui_scale: f64) -> Vec2 { @@ -66,6 +75,7 @@ impl Node { impl Node { pub const DEFAULT: Self = Self { calculated_size: Vec2::ZERO, + unrounded_size: Vec2::ZERO, }; } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 9a400ed209147..5ff128ebb6f94 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -166,7 +166,10 @@ fn queue_text( Vec2::splat(f32::INFINITY) } else { // `scale_factor` is already multiplied by `UiScale` - node.physical_size(scale_factor, 1.) + Vec2::new( + (node.unrounded_size.x as f64 * scale_factor) as f32, + (node.unrounded_size.y as f64 * scale_factor) as f32, + ) }; match text_pipeline.queue_text( From a5a457c3c82cc2e501bf6e85a2e2d07b5b7838a9 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Fri, 29 Sep 2023 14:09:14 +0700 Subject: [PATCH 69/72] docs: Use intradoc links for method references. (#9958) # Objective - Use intradoc links to let the compiler verify correctness. ## Solution - Use intradoc links. --- crates/bevy_ecs/src/system/commands/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index bf9945c347e23..89c8493dc667b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -88,9 +88,9 @@ pub trait Command: Send + 'static { /// /// # Implementing /// -/// Each built-in command is implemented as a separate method, e.g. [`spawn`](#method.spawn). +/// Each built-in command is implemented as a separate method, e.g. [`Commands::spawn`]. /// In addition to the pre-defined command methods, you can add commands with any arbitrary -/// behavior using [`Commands::add`](#method.add), which accepts any type implementing [`Command`]. +/// behavior using [`Commands::add`], which accepts any type implementing [`Command`]. /// /// Since closures and other functions implement this trait automatically, this allows one-shot, /// anonymous custom commands. From 7a72bac7799115eb1aa705f6ffbe28c2344beb84 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Fri, 29 Sep 2023 03:08:13 -0500 Subject: [PATCH 70/72] Fix unused variable warning for simple AssetV2 derives (#9961) # Objective Fix #9960 ## Solution Make the `visit` parameter `_visit` if there are no dependencies. New `cargo expand` output: ```rust pub struct Rarity { pub name: SharedStr, pub color: Color, } impl bevy::asset::Asset for Rarity {} impl bevy::asset::VisitAssetDependencies for Rarity { fn visit_dependencies( &self, _visit: &mut impl FnMut(bevy::asset::UntypedAssetId), // <-- fixed ) {} } impl bevy::reflect::TypePath for Rarity { fn type_path() -> &'static str { "myasset::item::Rarity" } fn short_type_path() -> &'static str { "Rarity" } fn type_ident() -> Option<&'static str> { ::core::option::Option::Some("Rarity") } fn crate_name() -> Option<&'static str> { ::core::option::Option::Some( "myasset::item".split(':').next().unwrap(), ) } fn module_path() -> Option<&'static str> { ::core::option::Option::Some("myasset::item") } } ``` --- crates/bevy_asset/macros/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index 580587248649d..eec95e3c50eb2 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -66,9 +66,16 @@ fn derive_dependency_visitor_internal( let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + // prevent unused variable warning in case there are no dependencies + let visit = if field_visitors.is_empty() { + quote! { _visit } + } else { + quote! { visit } + }; + Ok(quote! { impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause { - fn visit_dependencies(&self, visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) { + fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) { #(#field_visitors)* } } From f8fd93f418bf044455751bbcdf5528593b216510 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Fri, 29 Sep 2023 03:27:30 -0500 Subject: [PATCH 71/72] Add TypePath to the prelude (#9963) # Objective In order to derive `Asset`s (v2), `TypePath` must also be implemented. `TypePath` is not currently in the prelude, but given it is *required* when deriving something that *is* in the prelude, I think it deserves to be added. ## Solution Add `TypePath` to `bevy_reflect::prelude`. --- crates/bevy_reflect/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 6977b0bb71e9e..b8ba8da5dffdd 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -515,6 +515,7 @@ pub mod prelude { pub use crate::{ reflect_trait, FromReflect, GetField, GetPath, GetTupleStructField, Reflect, ReflectDeserialize, ReflectFromReflect, ReflectPath, ReflectSerialize, Struct, TupleStruct, + TypePath, }; } From 483f2464a82a437248c92eb3169bfa5c6485cd8e Mon Sep 17 00:00:00 2001 From: SADIK KUZU Date: Fri, 29 Sep 2023 15:26:41 +0300 Subject: [PATCH 72/72] Fix typos (#9965) # Objective - There were a few typos in the project. - This PR fixes these typos. ## Solution - Fixing the typos. Signed-off-by: SADIK KUZU --- crates/bevy_ecs/src/schedule/schedule.rs | 2 +- crates/bevy_ecs/src/system/system_registry.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- crates/bevy_hierarchy/src/components/parent.rs | 2 +- crates/bevy_ui/src/ui_node.rs | 2 +- examples/input/gamepad_input_events.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 48abc58990f18..e6b5d502f8e4f 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -251,7 +251,7 @@ impl Schedule { world.check_change_ticks(); self.initialize(world) - .unwrap_or_else(|e| panic!("Error when intializing schedule {:?}: {e}", self.name)); + .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.name)); self.executor.run(&mut self.executable, world); } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f0522606d978f..3d6320d301593 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -43,7 +43,7 @@ pub struct SystemId(Entity); impl World { /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. /// - /// It's possible to register the same systems more than once, they'll be stored seperately. + /// It's possible to register the same systems more than once, they'll be stored separately. /// /// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule), /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 3a497db57aa02..274daeed05547 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -174,7 +174,7 @@ impl<'a> From<&'a EntityWorldMut<'_>> for EntityRef<'a> { impl<'w> From> for EntityRef<'w> { fn from(value: EntityMut<'w>) -> Self { // SAFETY: - // - `EntityMut` gurantees exclusive access to all of the entity's components. + // - `EntityMut` guarantees exclusive access to all of the entity's components. unsafe { EntityRef::new(value.0) } } } @@ -182,7 +182,7 @@ impl<'w> From> for EntityRef<'w> { impl<'a> From<&'a EntityMut<'_>> for EntityRef<'a> { fn from(value: &'a EntityMut<'_>) -> Self { // SAFETY: - // - `EntityMut` gurantees exclusive access to all of the entity's components. + // - `EntityMut` guarantees exclusive access to all of the entity's components. // - `&value` ensures there are no mutable accesses. unsafe { EntityRef::new(value.0) } } diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 68ba8c7c91ed0..2a013ad8a142d 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -26,7 +26,7 @@ impl Parent { /// Gets the parent [`Entity`] as a slice of length 1. /// - /// Useful for making APIs that require a type or homogenous storage + /// Useful for making APIs that require a type or homogeneous storage /// for both [`Children`] & [`Parent`] that is agnostic to edge direction. /// /// [`Children`]: super::children::Children diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index d378806bfff3c..277ce3f760759 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1392,7 +1392,7 @@ fn try_into_grid_span(span: u16) -> Result, GridPlacementErro )) } -/// Errors that occur when setting contraints for a `GridPlacement` +/// Errors that occur when setting constraints for a `GridPlacement` #[derive(Debug, Eq, PartialEq, Clone, Copy, Error)] pub enum GridPlacementError { #[error("Zero is not a valid grid position")] diff --git a/examples/input/gamepad_input_events.rs b/examples/input/gamepad_input_events.rs index 73bd6ffef95c8..db1e8fc04d773 100644 --- a/examples/input/gamepad_input_events.rs +++ b/examples/input/gamepad_input_events.rs @@ -24,7 +24,7 @@ fn gamepad_events( // Handles the boolean measure of whether a button is considered pressed or unpressed, as // defined by the thresholds in `GamepadSettings::button_settings` and measured by // `Input`. When the threshold is crossed and the button state changes, - // this event is emmitted. + // this event is emitted. mut button_input_events: EventReader, ) { for connection_event in connection_events.read() {