From 565324daa32fc4d84da14f3c68b994048f6a4d2e Mon Sep 17 00:00:00 2001 From: Robert Walter <26892280+RobWalt@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:51:36 +0000 Subject: [PATCH 001/219] Improve the gizmo for `Plane3d`, reusing grid (#14650) # Objective With the current implementation of `Plane3d` gizmos, it's really hard to get a good feeling for big planes. Usually I tend to add more axes as a user but that doesn't scale well and is pretty wasteful. It's hard to recognize the plane in the distance here. Especially if there would've been other rendered objects in the scene ![image](https://github.com/user-attachments/assets/b65b7015-c08c-46d7-aa27-c7c0d49b2021) ## Solution - Since we got grid gizmos in the mean time, I went ahead and just reused them here. ## Testing I added an instance of the new `Plane3D` to the `3d_gizmos.rs` example. If you want to look at it you need to look around a bit. I didn't position it in the center since that was too crowded already. --- ## Showcase ![image](https://github.com/user-attachments/assets/e4982afe-7296-416c-9801-7dd85cd975c1) ## Migration Guide The optional builder methods on ```rust gizmos.primitive_3d(&Plane3d { }, ...); ``` changed from - `segment_length` - `segment_count` - `axis_count` to - `cell_count` - `spacing` --- crates/bevy_gizmos/src/grid.rs | 2 + crates/bevy_gizmos/src/primitives/dim3.rs | 73 +++++++---------------- examples/gizmos/3d_gizmos.rs | 15 +++++ 3 files changed, 39 insertions(+), 51 deletions(-) diff --git a/crates/bevy_gizmos/src/grid.rs b/crates/bevy_gizmos/src/grid.rs index 05b04c0376735..8c387fa349e41 100644 --- a/crates/bevy_gizmos/src/grid.rs +++ b/crates/bevy_gizmos/src/grid.rs @@ -182,6 +182,8 @@ where /// /// This should be called for each frame the grid needs to be rendered. /// + /// The grid's default orientation aligns with the XY-plane. + /// /// # Arguments /// /// - `isometry` defines the translation and rotation of the grid. diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index e2da1a115894e..f2ee075c16c75 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -1,14 +1,13 @@ //! A module for rendering each of the 3D [`bevy_math::primitives`] with [`Gizmos`]. use super::helpers::*; -use std::f32::consts::TAU; use bevy_color::Color; use bevy_math::primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d, }; -use bevy_math::{Dir3, Isometry3d, Quat, Vec3}; +use bevy_math::{Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3}; use crate::circles::SphereBuilder; use crate::prelude::{GizmoConfigGroup, Gizmos}; @@ -83,19 +82,17 @@ where { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - // direction of the normal orthogonal to the plane + // Direction of the normal orthogonal to the plane normal: Dir3, isometry: Isometry3d, // Color of the plane color: Color, - // Number of axis to hint the plane - axis_count: u32, - // Number of segments used to hint the plane - segment_count: u32, - // Length of segments used to hint the plane - segment_length: f32, + // Defines the amount of cells in the x and y axes + cell_count: UVec2, + // Defines the distance between cells along the x and y axes + spacing: Vec2, } impl Plane3dBuilder<'_, '_, '_, Config, Clear> @@ -103,21 +100,15 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - /// Set the number of segments used to hint the plane. - pub fn segment_count(mut self, count: u32) -> Self { - self.segment_count = count; + /// Set the number of cells in the x and y axes direction. + pub fn cell_count(mut self, cell_count: UVec2) -> Self { + self.cell_count = cell_count; self } - /// Set the length of segments used to hint the plane. - pub fn segment_length(mut self, length: f32) -> Self { - self.segment_length = length; - self - } - - /// Set the number of axis used to hint the plane. - pub fn axis_count(mut self, count: u32) -> Self { - self.axis_count = count; + /// Set the distance between cells along the x and y axes. + pub fn spacing(mut self, spacing: Vec2) -> Self { + self.spacing = spacing; self } } @@ -140,9 +131,8 @@ where normal: primitive.normal, isometry, color: color.into(), - axis_count: 4, - segment_count: 3, - segment_length: 0.25, + cell_count: UVec2::splat(3), + spacing: Vec2::splat(1.0), } } } @@ -157,35 +147,16 @@ where return; } - // draws the normal self.gizmos .primitive_3d(&self.normal, self.isometry, self.color); - - // draws the axes - // get rotation for each direction - let normals_normal = self.normal.any_orthonormal_vector(); - (0..self.axis_count) - .map(|i| i as f32 * (1.0 / self.axis_count as f32) * TAU) - .map(|angle| Quat::from_axis_angle(self.normal.as_vec3(), angle)) - .flat_map(|quat| { - let segment_length = self.segment_length; - let isometry = self.isometry; - // for each axis draw dotted line - (0..) - .filter(|i| i % 2 != 0) - .take(self.segment_count as usize) - .map(|i| [i, i + 1]) - .map(move |percents| { - percents - .map(|percent| percent as f32 + 0.5) - .map(|percent| percent * segment_length * normals_normal) - .map(|vec3| quat * vec3) - .map(|vec3| isometry * vec3) - }) - }) - .for_each(|[start, end]| { - self.gizmos.line(start, end, self.color); - }); + // the default orientation of the grid is Z-up + let rot = Quat::from_rotation_arc(Vec3::Z, self.normal.as_vec3()); + self.gizmos.grid( + Isometry3d::new(self.isometry.translation, self.isometry.rotation * rot), + self.cell_count, + self.spacing, + self.color, + ); } } diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index e66682180c3fd..9e506442c1a82 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -97,6 +97,21 @@ fn draw_example_collection( ); gizmos.sphere(Isometry3d::from_translation(Vec3::ONE * 10.0), 1.0, PURPLE); + gizmos + .primitive_3d( + &Plane3d { + normal: Dir3::Y, + half_size: Vec2::splat(1.0), + }, + Isometry3d::new( + Vec3::ONE * 4.0 + Vec2::from(time.elapsed_seconds().sin_cos()).extend(0.0), + Quat::from_rotation_x(PI / 2. + time.elapsed_seconds()), + ), + GREEN, + ) + .cell_count(UVec2::new(5, 10)) + .spacing(Vec2::new(0.2, 0.1)); + gizmos.cuboid( Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.25)), BLACK, From 1cca4f2968f0600e0f3a06b87bab777d1c822936 Mon Sep 17 00:00:00 2001 From: akimakinai <105044389+akimakinai@users.noreply.github.com> Date: Fri, 30 Aug 2024 01:47:58 +0900 Subject: [PATCH 002/219] Remove some asset examples from web showcase (#14973) # Objective - `custom_asset_reader` and `extra_asset_source` examples are not working on web. - Fixes #14689 ## Solution - Make these examples `wasm=false` per https://github.com/bevyengine/bevy/issues/14689#issuecomment-2313064396 ## Testing --- Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fde3db1f420d2..6e9daa74a4fba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1407,7 +1407,8 @@ doc-scrape-examples = true name = "Custom Asset IO" description = "Implements a custom AssetReader" category = "Assets" -wasm = true +# Incompatible with the asset path patching of the example-showcase tool +wasm = false [[example]] name = "embedded_asset" @@ -1429,7 +1430,8 @@ doc-scrape-examples = true name = "Extra asset source" description = "Load an asset from a non-standard asset source" category = "Assets" -wasm = true +# Uses non-standard asset path +wasm = false [[example]] name = "hot_asset_reloading" From 9e784334276669295f0d1c83727868ec0086cdf7 Mon Sep 17 00:00:00 2001 From: Robert Walter <26892280+RobWalt@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:48:22 +0000 Subject: [PATCH 003/219] `Curve` gizmos integration (#14971) # Objective - Add gizmos integration for the new `Curve` things in the math lib ## Solution - Add the following methods - `curve_2d(curve, sample_times, color)` - `curve_3d(curve, sample_times, color)` - `curve_gradient_2d(curve, sample_times_with_colors)` - `curve_gradient_3d(curve, sample_times_with_colors)` ## Testing - I added examples of the 2D and 3D variants of the gradient curve gizmos to the gizmos examples. ## Showcase ### 2D ![image](https://github.com/user-attachments/assets/01a75706-a7b4-4fc5-98d5-18018185c877) ```rust let domain = Interval::EVERYWHERE; let curve = function_curve(domain, |t| Vec2::new(t, (t / 25.0).sin() * 100.0)); let resolution = ((time.elapsed_seconds().sin() + 1.0) * 50.0) as usize; let times_and_colors = (0..=resolution) .map(|n| n as f32 / resolution as f32) .map(|t| (t - 0.5) * 600.0) .map(|t| (t, TEAL.mix(&HOT_PINK, (t + 300.0) / 600.0))); gizmos.curve_gradient_2d(curve, times_and_colors); ``` ### 3D ![image](https://github.com/user-attachments/assets/3fd23983-1ec9-46cd-baed-5b5e2dc935d0) ```rust let domain = Interval::EVERYWHERE; let curve = function_curve(domain, |t| { (Vec2::from((t * 10.0).sin_cos())).extend(t - 6.0) }); let resolution = ((time.elapsed_seconds().sin() + 1.0) * 100.0) as usize; let times_and_colors = (0..=resolution) .map(|n| n as f32 / resolution as f32) .map(|t| t * 5.0) .map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0))); gizmos.curve_gradient_3d(curve, times_and_colors); ``` --- crates/bevy_gizmos/src/curves.rs | 175 +++++++++++++++++++++++++++++++ crates/bevy_gizmos/src/lib.rs | 1 + crates/bevy_math/src/lib.rs | 1 + examples/gizmos/2d_gizmos.rs | 9 ++ examples/gizmos/3d_gizmos.rs | 11 ++ 5 files changed, 197 insertions(+) create mode 100644 crates/bevy_gizmos/src/curves.rs diff --git a/crates/bevy_gizmos/src/curves.rs b/crates/bevy_gizmos/src/curves.rs new file mode 100644 index 0000000000000..4a7b1aec1e045 --- /dev/null +++ b/crates/bevy_gizmos/src/curves.rs @@ -0,0 +1,175 @@ +//! Additional [`Gizmos`] Functions -- Curves +//! +//! Includes the implementation of [`Gizmos::curve_2d`], +//! [`Gizmos::curve_3d`] and assorted support items. + +use bevy_color::Color; +use bevy_math::{curve::Curve, Vec2, Vec3}; + +use crate::prelude::{GizmoConfigGroup, Gizmos}; + +impl<'w, 's, Config, Clear> Gizmos<'w, 's, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + /// Draw a curve, at the given time points, sampling in 2D. + /// + /// This should be called for each frame the curve needs to be rendered. + /// + /// Samples of time points outside of the curve's domain will be filtered out and won't + /// contribute to the rendering. If you wish to render the curve outside of its domain you need + /// to create a new curve with an extended domain. + /// + /// # Arguments + /// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s + /// - `times` some iterable type yielding `f32` which will be used for sampling the curve + /// - `color` the color of the curve + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::palettes::basic::{RED}; + /// fn system(mut gizmos: Gizmos) { + /// let domain = Interval::UNIT; + /// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos())); + /// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn curve_2d( + &mut self, + curve_2d: impl Curve, + times: impl IntoIterator, + color: impl Into, + ) { + self.linestrip_2d(curve_2d.sample_iter(times).flatten(), color); + } + + /// Draw a curve, at the given time points, sampling in 3D. + /// + /// This should be called for each frame the curve needs to be rendered. + /// + /// Samples of time points outside of the curve's domain will be filtered out and won't + /// contribute to the rendering. If you wish to render the curve outside of its domain you need + /// to create a new curve with an extended domain. + /// + /// # Arguments + /// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s + /// - `times` some iterable type yielding `f32` which will be used for sampling the curve + /// - `color` the color of the curve + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::palettes::basic::{RED}; + /// fn system(mut gizmos: Gizmos) { + /// let domain = Interval::UNIT; + /// let curve = function_curve(domain, |t| { + /// let (x,y) = t.sin_cos(); + /// Vec3::new(x, y, t) + /// }); + /// gizmos.curve_3d(curve, (0..=100).map(|n| n as f32 / 100.0), RED); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn curve_3d( + &mut self, + curve_3d: impl Curve, + times: impl IntoIterator, + color: impl Into, + ) { + self.linestrip(curve_3d.sample_iter(times).flatten(), color); + } + + /// Draw a curve, at the given time points, sampling in 2D, with a color gradient. + /// + /// This should be called for each frame the curve needs to be rendered. + /// + /// Samples of time points outside of the curve's domain will be filtered out and won't + /// contribute to the rendering. If you wish to render the curve outside of its domain you need + /// to create a new curve with an extended domain. + /// + /// # Arguments + /// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s + /// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling + /// the curve together with the color at this position + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}}; + /// fn system(mut gizmos: Gizmos) { + /// let domain = Interval::UNIT; + /// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos())); + /// gizmos.curve_gradient_2d( + /// curve, + /// (0..=100).map(|n| n as f32 / 100.0) + /// .map(|t| (t, GREEN.mix(&RED, t))) + /// ); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn curve_gradient_2d( + &mut self, + curve_2d: impl Curve, + times_with_colors: impl IntoIterator, + ) where + C: Into, + { + self.linestrip_gradient_2d( + times_with_colors + .into_iter() + .filter_map(|(time, color)| curve_2d.sample(time).map(|sample| (sample, color))), + ); + } + + /// Draw a curve, at the given time points, sampling in 3D, with a color gradient. + /// + /// This should be called for each frame the curve needs to be rendered. + /// + /// Samples of time points outside of the curve's domain will be filtered out and won't + /// contribute to the rendering. If you wish to render the curve outside of its domain you need + /// to create a new curve with an extended domain. + /// + /// # Arguments + /// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s + /// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling + /// the curve together with the color at this position + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}}; + /// fn system(mut gizmos: Gizmos) { + /// let domain = Interval::UNIT; + /// let curve = function_curve(domain, |t| { + /// let (x,y) = t.sin_cos(); + /// Vec3::new(x, y, t) + /// }); + /// gizmos.curve_gradient_3d( + /// curve, + /// (0..=100).map(|n| n as f32 / 100.0) + /// .map(|t| (t, GREEN.mix(&RED, t))) + /// ); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn curve_gradient_3d( + &mut self, + curve_3d: impl Curve, + times_with_colors: impl IntoIterator, + ) where + C: Into, + { + self.linestrip_gradient( + times_with_colors + .into_iter() + .filter_map(|(time, color)| curve_3d.sample(time).map(|sample| (sample, color))), + ); + } +} diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 3e28516b608b7..c30d0b09314ef 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -37,6 +37,7 @@ pub mod arrows; pub mod circles; pub mod config; pub mod cross; +pub mod curves; pub mod gizmos; pub mod grid; pub mod primitives; diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 76ad5b06a7b5a..cbbc25e219fa8 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -55,6 +55,7 @@ pub mod prelude { CubicHermite, CubicNurbs, CubicNurbsError, CubicSegment, CyclicCubicGenerator, RationalCurve, RationalGenerator, RationalSegment, }, + curve::*, direction::{Dir2, Dir3, Dir3A}, primitives::*, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index 23d07a8b1a06f..0fa75863d50f8 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -73,6 +73,15 @@ fn draw_example_collection( FUCHSIA, ); + let domain = Interval::EVERYWHERE; + let curve = function_curve(domain, |t| Vec2::new(t, (t / 25.0).sin() * 100.0)); + let resolution = ((time.elapsed_seconds().sin() + 1.0) * 50.0) as usize; + let times_and_colors = (0..=resolution) + .map(|n| n as f32 / resolution as f32) + .map(|t| (t - 0.5) * 600.0) + .map(|t| (t, TEAL.mix(&HOT_PINK, (t + 300.0) / 600.0))); + gizmos.curve_gradient_2d(curve, times_and_colors); + my_gizmos .rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK) .corner_radius((time.elapsed_seconds() / 3.).cos() * 100.); diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index 9e506442c1a82..1e4238a58a93c 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -131,6 +131,17 @@ fn draw_example_collection( FUCHSIA, ); + let domain = Interval::EVERYWHERE; + let curve = function_curve(domain, |t| { + (Vec2::from((t * 10.0).sin_cos())).extend(t - 6.0) + }); + let resolution = ((time.elapsed_seconds().sin() + 1.0) * 100.0) as usize; + let times_and_colors = (0..=resolution) + .map(|n| n as f32 / resolution as f32) + .map(|t| t * 5.0) + .map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0))); + gizmos.curve_gradient_3d(curve, times_and_colors); + my_gizmos.sphere( Isometry3d::from_translation(Vec3::new(1., 0.5, 0.)), 0.5, From e08497dc8f26d52b956621153c03d0d22196ea88 Mon Sep 17 00:00:00 2001 From: Chris Juchem Date: Thu, 29 Aug 2024 20:43:07 -0400 Subject: [PATCH 004/219] Replace `bevy_utils::CowArc` with `atomicow` (#14977) # Objective - Fixes https://github.com/bevyengine/bevy/issues/14975 ## Solution - Replace usages of `bevy_utils::CowArc` with `atomicow::CowArc` - Remove bevy_utils::CowArc ## Testing - `bevy_asset` test suite continues to pass. --- ## Migration Guide `bevy_utils::CowArc` has moved to a new crate called [atomicow](https://crates.io/crates/atomicow). --- crates/bevy_asset/Cargo.toml | 1 + crates/bevy_asset/src/io/source.rs | 3 +- crates/bevy_asset/src/loader.rs | 3 +- crates/bevy_asset/src/path.rs | 2 +- crates/bevy_asset/src/saver.rs | 3 +- crates/bevy_asset/src/server/mod.rs | 3 +- crates/bevy_asset/src/transformer.rs | 3 +- crates/bevy_utils/src/cow_arc.rs | 191 --------------------------- crates/bevy_utils/src/lib.rs | 2 - 9 files changed, 12 insertions(+), 199 deletions(-) delete mode 100644 crates/bevy_utils/src/cow_arc.rs diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 0b95ab505bae7..b2348d65094d3 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -29,6 +29,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } stackfuture = "0.3" +atomicow = "1.0" async-broadcast = "0.5" async-fs = "2.0" async-lock = "3.0" diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index a979a3327791e..ec1947a3fee1e 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -2,9 +2,10 @@ use crate::{ io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher}, processor::AssetProcessorData, }; +use atomicow::CowArc; use bevy_ecs::system::Resource; use bevy_utils::tracing::{error, warn}; -use bevy_utils::{CowArc, Duration, HashMap}; +use bevy_utils::{Duration, HashMap}; use std::{fmt::Display, hash::Hash, sync::Arc}; use thiserror::Error; diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 1b444bba8c19b..f0dce3593da40 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -6,8 +6,9 @@ use crate::{ Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId, UntypedHandle, }; +use atomicow::CowArc; use bevy_ecs::world::World; -use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap, HashSet}; +use bevy_utils::{BoxedFuture, ConditionalSendFuture, HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use ron::error::SpannedError; use serde::{Deserialize, Serialize}; diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index dc7719a25f9ad..67c7c65286fac 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -1,6 +1,6 @@ use crate::io::AssetSourceId; +use atomicow::CowArc; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; -use bevy_utils::CowArc; use serde::{de::Visitor, Deserialize, Serialize}; use std::{ fmt::{Debug, Display}, diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 36408dd125f29..4d5925dc5492c 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -1,7 +1,8 @@ use crate::transformer::TransformedAsset; use crate::{io::Writer, meta::Settings, Asset, ErasedLoadedAsset}; use crate::{AssetLoader, Handle, LabeledAsset, UntypedHandle}; -use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap}; +use atomicow::CowArc; +use bevy_utils::{BoxedFuture, ConditionalSendFuture, HashMap}; use serde::{Deserialize, Serialize}; use std::{borrow::Borrow, hash::Hash, ops::Deref}; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index ef72d2b404295..61c802e505766 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -17,10 +17,11 @@ use crate::{ DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle, }; +use atomicow::CowArc; use bevy_ecs::prelude::*; use bevy_tasks::IoTaskPool; use bevy_utils::tracing::{error, info}; -use bevy_utils::{CowArc, HashSet}; +use bevy_utils::HashSet; use crossbeam_channel::{Receiver, Sender}; use futures_lite::{FutureExt, StreamExt}; use info::*; diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 0ffddc4658a43..1b2cd92991c15 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -1,5 +1,6 @@ use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; -use bevy_utils::{ConditionalSendFuture, CowArc, HashMap}; +use atomicow::CowArc; +use bevy_utils::{ConditionalSendFuture, HashMap}; use serde::{Deserialize, Serialize}; use std::{ borrow::Borrow, diff --git a/crates/bevy_utils/src/cow_arc.rs b/crates/bevy_utils/src/cow_arc.rs deleted file mode 100644 index 635d31a583ef6..0000000000000 --- a/crates/bevy_utils/src/cow_arc.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::{ - borrow::Borrow, - fmt::{Debug, Display}, - hash::Hash, - ops::Deref, - path::{Path, PathBuf}, - sync::Arc, -}; - -/// Much like a [`Cow`](std::borrow::Cow), but owned values are Arc-ed to make clones cheap. This should be used for values that -/// are cloned for use across threads and change rarely (if ever). -/// -/// This also makes an opinionated tradeoff by adding a [`CowArc::Static`] and implementing [`From<&'static T>`] instead of -/// [`From<'a T>`]. This preserves the static context and prevents conversion to [`CowArc::Owned`] in cases where a reference -/// is known to be static. This is an optimization that prevents allocations and atomic ref-counting. -/// -/// This means that static references should prefer [`From::from`] or [`CowArc::Static`] and non-static references must -/// use [`CowArc::Borrowed`]. -pub enum CowArc<'a, T: ?Sized + 'static> { - /// A borrowed value - Borrowed(&'a T), - /// A static value reference. This exists to avoid conversion to [`CowArc::Owned`] in cases where a reference is - /// known to be static. This is an optimization that prevents allocations and atomic ref-counting. - Static(&'static T), - /// An owned [`Arc`]-ed value - Owned(Arc), -} - -impl CowArc<'static, T> { - /// Indicates this [`CowArc`] should have a static lifetime. - /// This ensures if this was created with a value `Borrowed(&'static T)`, it is replaced with `Static(&'static T)`. - #[inline] - pub fn as_static(self) -> Self { - match self { - Self::Borrowed(value) | Self::Static(value) => Self::Static(value), - Self::Owned(value) => Self::Owned(value), - } - } -} - -impl<'a, T: ?Sized> Deref for CowArc<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - match self { - CowArc::Borrowed(v) | CowArc::Static(v) => v, - CowArc::Owned(v) => v, - } - } -} - -impl<'a, T: ?Sized> Borrow for CowArc<'a, T> { - #[inline] - fn borrow(&self) -> &T { - self - } -} - -impl<'a, T: ?Sized> AsRef for CowArc<'a, T> { - #[inline] - fn as_ref(&self) -> &T { - self - } -} - -impl<'a, T: ?Sized> CowArc<'a, T> -where - &'a T: Into>, -{ - /// Converts this into an "owned" value. If internally a value is borrowed, it will be cloned into an "owned [`Arc`]". - /// If it is already a [`CowArc::Owned`] or a [`CowArc::Static`], it will remain unchanged. - #[inline] - pub fn into_owned(self) -> CowArc<'static, T> { - match self { - CowArc::Borrowed(value) => CowArc::Owned(value.into()), - CowArc::Static(value) => CowArc::Static(value), - CowArc::Owned(value) => CowArc::Owned(value), - } - } - - /// Clones into an owned [`CowArc<'static>`]. If internally a value is borrowed, it will be cloned into an "owned [`Arc`]". - /// If it is already a [`CowArc::Owned`] or [`CowArc::Static`], the value will be cloned. - /// This is equivalent to `.clone().into_owned()`. - #[inline] - pub fn clone_owned(&self) -> CowArc<'static, T> { - self.clone().into_owned() - } -} - -impl<'a, T: ?Sized> Clone for CowArc<'a, T> { - #[inline] - fn clone(&self) -> Self { - match self { - Self::Borrowed(value) => Self::Borrowed(value), - Self::Static(value) => Self::Static(value), - Self::Owned(value) => Self::Owned(value.clone()), - } - } -} - -impl<'a, T: PartialEq + ?Sized> PartialEq for CowArc<'a, T> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other.deref()) - } -} - -impl<'a, T: PartialEq + ?Sized> Eq for CowArc<'a, T> {} - -impl<'a, T: Hash + ?Sized> Hash for CowArc<'a, T> { - #[inline] - fn hash(&self, state: &mut H) { - self.deref().hash(state); - } -} - -impl<'a, T: Debug + ?Sized> Debug for CowArc<'a, T> { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self.deref(), f) - } -} - -impl<'a, T: Display + ?Sized> Display for CowArc<'a, T> { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(self.deref(), f) - } -} - -impl<'a, T: PartialOrd + ?Sized> PartialOrd for CowArc<'a, T> { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - self.deref().partial_cmp(other.deref()) - } -} - -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 { - self.deref().cmp(other.deref()) - } -} - -impl From for CowArc<'static, Path> { - #[inline] - fn from(value: PathBuf) -> Self { - CowArc::Owned(value.into()) - } -} - -impl From<&'static str> for CowArc<'static, Path> { - #[inline] - fn from(value: &'static str) -> Self { - CowArc::Static(Path::new(value)) - } -} - -impl From for CowArc<'static, str> { - #[inline] - fn from(value: String) -> Self { - CowArc::Owned(value.into()) - } -} - -impl<'a> From<&'a String> for CowArc<'a, str> { - #[inline] - fn from(value: &'a String) -> Self { - CowArc::Borrowed(value) - } -} - -impl From<&'static T> for CowArc<'static, T> { - #[inline] - fn from(value: &'static T) -> Self { - CowArc::Static(value) - } -} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index a37a6e18ba3fc..772d3fda31ff3 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -21,7 +21,6 @@ pub use short_names::get_short_name; pub mod synccell; pub mod syncunsafecell; -mod cow_arc; mod default; mod object_safe; pub use object_safe::assert_object_safe; @@ -30,7 +29,6 @@ mod parallel_queue; pub use ahash::{AHasher, RandomState}; pub use bevy_utils_proc_macros::*; -pub use cow_arc::*; pub use default::default; pub use hashbrown; pub use parallel_queue::*; From f2cf02408ff8766ef53ad7fa2e42fd30d2f0f8e5 Mon Sep 17 00:00:00 2001 From: Alix Bott Date: Fri, 30 Aug 2024 02:43:56 +0200 Subject: [PATCH 005/219] Fix observer unregistering unsetting archetype flags (#14963) # Objective - Fixes https://github.com/bevyengine/bevy/issues/14961 ## Solution - Check that the archetypes don't contain any other observed components before unsetting their flags ## Testing - I added a regression test: `observer_despawn_archetype_flags` --- crates/bevy_ecs/src/archetype.rs | 2 +- crates/bevy_ecs/src/observer/mod.rs | 32 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index b7cad4e389512..15e962291cbd3 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -367,7 +367,7 @@ pub struct Archetype { edges: Edges, entities: Vec, components: ImmutableSparseSet, - flags: ArchetypeFlags, + pub(crate) flags: ArchetypeFlags, } impl Archetype { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 3ef2d6b28d63d..c6ace80ca5202 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -429,7 +429,17 @@ impl World { if observers.map.is_empty() && observers.entity_map.is_empty() { cache.component_observers.remove(component); if let Some(flag) = Observers::is_archetype_cached(event_type) { - archetypes.update_flags(*component, flag, false); + for archetype in &mut archetypes.archetypes { + if archetype.contains(*component) { + let no_longer_observed = archetype + .components() + .all(|id| !cache.component_observers.contains_key(&id)); + + if no_longer_observed { + archetype.flags.set(flag, false); + } + } + } } } } @@ -656,6 +666,26 @@ mod tests { world.spawn(A).flush(); } + // Regression test for https://github.com/bevyengine/bevy/issues/14961 + #[test] + fn observer_despawn_archetype_flags() { + let mut world = World::new(); + world.init_resource::(); + + let entity = world.spawn((A, B)).flush(); + + world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + + let observer = world + .observe(|_: Trigger| panic!("Observer triggered after being despawned.")) + .flush(); + world.despawn(observer); + + world.despawn(entity); + + assert_eq!(1, world.resource::().0); + } + #[test] fn observer_multiple_matches() { let mut world = World::new(); From 147768adf612218051f633ab1498ba8af5ace92f Mon Sep 17 00:00:00 2001 From: akimakinai <105044389+akimakinai@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:22:11 +0900 Subject: [PATCH 006/219] Use CowArc::Static (#14981) # Objective - There's one occurence of `CowArc::Borrow` that wraps '&'static str` ## Solution - Replaces it with `CowArc::Static`. I don't think this change is important but I can't unsee it:) ## Testing - `cargo check` compiles fine --- crates/bevy_asset/src/server/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 61c802e505766..a9e5fcddbf31c 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -415,7 +415,7 @@ impl AssetServer { ) -> Handle { let path = path.into().into_owned(); let untyped_source = AssetSourceId::Name(match path.source() { - AssetSourceId::Default => CowArc::Borrowed(UNTYPED_SOURCE_SUFFIX), + AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX), AssetSourceId::Name(source) => { CowArc::Owned(format!("{source}--{UNTYPED_SOURCE_SUFFIX}").into()) } From c816cf9072e39413c41faeb943d114aa5f74d6eb Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:24:31 -0400 Subject: [PATCH 007/219] Reorganize some of `bevy_animation`'s imports into a more consistent style (#14983) # Objective `bevy_animation` imports a lot of items - and it uses a very inconsistent code style to do so. ## Solution Changes the offending `use` statements to be more consistent across the crate. ## Testing - Did you test these changes? If so, how? - No testing is needed beyond lint checks, and those finished successfully. - ~~Are there any parts that need more testing?~~ - ~~How can other people (reviewers) test your changes? Is there anything specific they need to know?~~ - ~~If relevant, what platforms did you test these changes on, and are there any important ones you can't test?~~ --- crates/bevy_animation/src/graph.rs | 3 +-- crates/bevy_animation/src/lib.rs | 16 +++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index aeeed9fcdf631..7154a77e974c0 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -3,8 +3,7 @@ use std::io::{self, Write}; use std::ops::{Index, IndexMut}; -use bevy_asset::io::Reader; -use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, Handle, LoadContext}; +use bevy_asset::{io::Reader, Asset, AssetId, AssetLoader, AssetPath, Handle, LoadContext}; use bevy_reflect::{Reflect, ReflectSerialize}; use petgraph::graph::{DiGraph, NodeIndex}; use ron::de::SpannedError; diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index bf2904440ce5b..4277f0b1141b0 100755 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -21,24 +21,19 @@ use std::ops::{Add, Mul}; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_core::Name; -use bevy_ecs::entity::MapEntities; -use bevy_ecs::prelude::*; -use bevy_ecs::reflect::ReflectMapEntities; +use bevy_ecs::{entity::MapEntities, prelude::*, reflect::ReflectMapEntities}; use bevy_math::{FloatExt, Quat, Vec3}; use bevy_reflect::Reflect; use bevy_render::mesh::morph::MorphWeights; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; -use bevy_utils::hashbrown::HashMap; use bevy_utils::{ + hashbrown::HashMap, tracing::{error, trace}, NoOpHash, }; use fixedbitset::FixedBitSet; -use graph::{AnimationGraph, AnimationNodeIndex}; -use petgraph::graph::NodeIndex; -use petgraph::Direction; -use prelude::{AnimationGraphAssetLoader, AnimationTransitions}; +use petgraph::{graph::NodeIndex, Direction}; use thread_local::ThreadLocal; use uuid::Uuid; @@ -51,7 +46,10 @@ pub mod prelude { }; } -use crate::transition::{advance_transitions, expire_completed_transitions}; +use crate::{ + graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex}, + transition::{advance_transitions, expire_completed_transitions, AnimationTransitions}, +}; /// The [UUID namespace] of animation targets (e.g. bones). /// From bc131614169a1a8d55c401aa3a5a3a737a83f7f1 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Fri, 30 Aug 2024 12:37:47 +1000 Subject: [PATCH 008/219] Migrated `NonZero*` to `NonZero<*>` (#14978) # Objective - Fixes #14974 ## Solution - Replace all* instances of `NonZero*` with `NonZero<*>` ## Testing - CI passed locally. --- ## Notes Within the `bevy_reflect` implementations for `std` types, `impl_reflect_value!()` will continue to use the type aliases instead, as it inappropriately parses the concrete type parameter as a generic argument. If the `ZeroablePrimitive` trait was stable, or the macro could be modified to accept a finite list of types, then we could fully migrate. --- crates/bevy_app/src/app.rs | 8 +- .../src/auto_exposure/pipeline.rs | 6 +- crates/bevy_ecs/src/entity/mod.rs | 73 ++++++++++--------- crates/bevy_ecs/src/identifier/masks.rs | 66 ++++++++--------- crates/bevy_ecs/src/identifier/mod.rs | 10 +-- crates/bevy_ecs/src/lib.rs | 7 +- crates/bevy_ecs/src/storage/blob_vec.rs | 11 +-- crates/bevy_pbr/src/cluster/mod.rs | 8 +- .../src/light_probe/environment_map.rs | 4 +- .../src/light_probe/irradiance_volume.rs | 4 +- crates/bevy_pbr/src/material.rs | 8 +- .../bevy_pbr/src/meshlet/persistent_buffer.rs | 5 +- crates/bevy_pbr/src/render/gpu_preprocess.rs | 4 +- .../bevy_pbr/src/render/mesh_view_bindings.rs | 4 +- crates/bevy_ptr/src/lib.rs | 6 +- crates/bevy_reflect/src/impls/std.rs | 8 +- .../render_resource/batched_uniform_buffer.rs | 6 +- .../bind_group_layout_entries.rs | 14 ++-- .../src/render_resource/resource_macros.rs | 10 +-- .../src/render_resource/uniform_buffer.rs | 4 +- crates/bevy_render/src/view/window/mod.rs | 6 +- crates/bevy_tasks/src/lib.rs | 4 +- crates/bevy_ui/src/ui_node.rs | 28 +++---- crates/bevy_window/src/window.rs | 4 +- examples/app/headless_renderer.rs | 2 +- examples/shader/texture_binding_array.rs | 6 +- 26 files changed, 161 insertions(+), 155 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 07a29800f93df..da41efbc152c4 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -18,7 +18,7 @@ use std::{ process::{ExitCode, Termination}, }; use std::{ - num::NonZeroU8, + num::NonZero, panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, }; use thiserror::Error; @@ -1061,14 +1061,14 @@ pub enum AppExit { Success, /// The [`App`] experienced an unhandleable error. /// Holds the exit code we expect our app to return. - Error(NonZeroU8), + Error(NonZero), } impl AppExit { /// Creates a [`AppExit::Error`] with a error code of 1. #[must_use] pub const fn error() -> Self { - Self::Error(NonZeroU8::MIN) + Self::Error(NonZero::::MIN) } /// Returns `true` if `self` is a [`AppExit::Success`]. @@ -1089,7 +1089,7 @@ impl AppExit { /// [`AppExit::Error`] is constructed. #[must_use] pub const fn from_code(code: u8) -> Self { - match NonZeroU8::new(code) { + match NonZero::::new(code) { Some(code) => Self::Error(code), None => Self::Success, } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index eacff931c7211..937e18f410485 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -10,7 +10,7 @@ use bevy_render::{ texture::Image, view::ViewUniform, }; -use std::num::NonZeroU64; +use std::num::NonZero; #[derive(Resource)] pub struct AutoExposurePipeline { @@ -64,8 +64,8 @@ impl FromWorld for AutoExposurePipeline { texture_2d(TextureSampleType::Float { filterable: false }), texture_1d(TextureSampleType::Float { filterable: false }), uniform_buffer::(false), - storage_buffer_sized(false, NonZeroU64::new(HISTOGRAM_BIN_COUNT * 4)), - storage_buffer_sized(false, NonZeroU64::new(4)), + storage_buffer_sized(false, NonZero::::new(HISTOGRAM_BIN_COUNT * 4)), + storage_buffer_sized(false, NonZero::::new(4)), storage_buffer::(true), ), ), diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index deee320fcd0b2..93b1e78ffabe3 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -59,7 +59,7 @@ use crate::{ }; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; -use std::{fmt, hash::Hash, mem, num::NonZeroU32, sync::atomic::Ordering}; +use std::{fmt, hash::Hash, mem, num::NonZero, sync::atomic::Ordering}; #[cfg(target_has_atomic = "64")] use std::sync::atomic::AtomicI64 as AtomicIdCursor; @@ -157,7 +157,7 @@ pub struct Entity { // to make this struct equivalent to a u64. #[cfg(target_endian = "little")] index: u32, - generation: NonZeroU32, + generation: NonZero, #[cfg(target_endian = "big")] index: u32, } @@ -223,7 +223,7 @@ impl Entity { /// Construct an [`Entity`] from a raw `index` value and a non-zero `generation` value. /// Ensure that the generation value is never greater than `0x7FFF_FFFF`. #[inline(always)] - pub(crate) const fn from_raw_and_generation(index: u32, generation: NonZeroU32) -> Entity { + pub(crate) const fn from_raw_and_generation(index: u32, generation: NonZero) -> Entity { debug_assert!(generation.get() <= HIGH_MASK); Self { index, generation } @@ -279,7 +279,7 @@ impl Entity { /// a component. #[inline(always)] pub const fn from_raw(index: u32) -> Entity { - Self::from_raw_and_generation(index, NonZeroU32::MIN) + Self::from_raw_and_generation(index, NonZero::::MIN) } /// Convert to a form convenient for passing outside of rust. @@ -722,7 +722,7 @@ impl Entities { meta.generation = IdentifierMask::inc_masked_high_by(meta.generation, 1); - if meta.generation == NonZeroU32::MIN { + if meta.generation == NonZero::::MIN { warn!( "Entity({}) generation wrapped on Entities::free, aliasing may occur", entity.index @@ -949,7 +949,7 @@ impl Entities { #[repr(C)] struct EntityMeta { /// The current generation of the [`Entity`]. - pub generation: NonZeroU32, + pub generation: NonZero, /// The current location of the [`Entity`] pub location: EntityLocation, } @@ -957,7 +957,7 @@ struct EntityMeta { impl EntityMeta { /// meta for **pending entity** const EMPTY: EntityMeta = EntityMeta { - generation: NonZeroU32::MIN, + generation: NonZero::::MIN, location: EntityLocation::INVALID, }; } @@ -1014,7 +1014,8 @@ mod tests { #[test] fn entity_bits_roundtrip() { // Generation cannot be greater than 0x7FFF_FFFF else it will be an invalid Entity id - let e = Entity::from_raw_and_generation(0xDEADBEEF, NonZeroU32::new(0x5AADF00D).unwrap()); + let e = + Entity::from_raw_and_generation(0xDEADBEEF, NonZero::::new(0x5AADF00D).unwrap()); assert_eq!(Entity::from_bits(e.to_bits()), e); } @@ -1091,65 +1092,65 @@ mod tests { #[allow(clippy::nonminimal_bool)] // This is intentionally testing `lt` and `ge` as separate functions. fn entity_comparison() { assert_eq!( - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZeroU32::new(789).unwrap()), - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()), + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZeroU32::new(789).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), + Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()), - Entity::from_raw_and_generation(456, NonZeroU32::new(123).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), + Entity::from_raw_and_generation(456, NonZero::::new(123).unwrap()) ); // ordering is by generation then by index assert!( - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) - >= Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + >= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) ); assert!( - Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) - <= Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) + Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + <= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) ); assert!( - !(Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) - < Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())) + !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + < Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) ); assert!( - !(Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()) - > Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())) + !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + > Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) ); assert!( - Entity::from_raw_and_generation(9, NonZeroU32::new(1).unwrap()) - < Entity::from_raw_and_generation(1, NonZeroU32::new(9).unwrap()) + Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) + < Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) ); assert!( - Entity::from_raw_and_generation(1, NonZeroU32::new(9).unwrap()) - > Entity::from_raw_and_generation(9, NonZeroU32::new(1).unwrap()) + Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) + > Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) ); assert!( - Entity::from_raw_and_generation(1, NonZeroU32::new(1).unwrap()) - < Entity::from_raw_and_generation(2, NonZeroU32::new(1).unwrap()) + Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) + < Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) ); assert!( - Entity::from_raw_and_generation(1, NonZeroU32::new(1).unwrap()) - <= Entity::from_raw_and_generation(2, NonZeroU32::new(1).unwrap()) + Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) + <= Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) ); assert!( - Entity::from_raw_and_generation(2, NonZeroU32::new(2).unwrap()) - > Entity::from_raw_and_generation(1, NonZeroU32::new(2).unwrap()) + Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) + > Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) ); assert!( - Entity::from_raw_and_generation(2, NonZeroU32::new(2).unwrap()) - >= Entity::from_raw_and_generation(1, NonZeroU32::new(2).unwrap()) + Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) + >= Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) ); } diff --git a/crates/bevy_ecs/src/identifier/masks.rs b/crates/bevy_ecs/src/identifier/masks.rs index d5adc856ddb88..85fb393cb6f13 100644 --- a/crates/bevy_ecs/src/identifier/masks.rs +++ b/crates/bevy_ecs/src/identifier/masks.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroU32; +use std::num::NonZero; use super::kinds::IdKind; @@ -61,7 +61,7 @@ impl IdentifierMask { /// Will never be greater than [`HIGH_MASK`] or less than `1`, and increments are masked to /// never be greater than [`HIGH_MASK`]. #[inline(always)] - pub(crate) const fn inc_masked_high_by(lhs: NonZeroU32, rhs: u32) -> NonZeroU32 { + pub(crate) const fn inc_masked_high_by(lhs: NonZero, rhs: u32) -> NonZero { let lo = (lhs.get() & HIGH_MASK).wrapping_add(rhs & HIGH_MASK); // Checks high 32 bit for whether we have overflowed 31 bits. let overflowed = lo >> 31; @@ -70,7 +70,7 @@ impl IdentifierMask { // - Adding the overflow flag will offset overflows to start at 1 instead of 0 // - The sum of `0x7FFF_FFFF` + `u32::MAX` + 1 (overflow) == `0x7FFF_FFFF` // - If the operation doesn't overflow at 31 bits, no offsetting takes place - unsafe { NonZeroU32::new_unchecked(lo.wrapping_add(overflowed) & HIGH_MASK) } + unsafe { NonZero::::new_unchecked(lo.wrapping_add(overflowed) & HIGH_MASK) } } } @@ -166,68 +166,68 @@ mod tests { // Adding from lowest value with lowest to highest increment // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK assert_eq!( - NonZeroU32::MIN, - IdentifierMask::inc_masked_high_by(NonZeroU32::MIN, 0) + NonZero::::MIN, + IdentifierMask::inc_masked_high_by(NonZero::::MIN, 0) ); assert_eq!( - NonZeroU32::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MIN, 1) + NonZero::::new(2).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MIN, 1) ); assert_eq!( - NonZeroU32::new(3).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MIN, 2) + NonZero::::new(3).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MIN, 2) ); assert_eq!( - NonZeroU32::MIN, - IdentifierMask::inc_masked_high_by(NonZeroU32::MIN, HIGH_MASK) + NonZero::::MIN, + IdentifierMask::inc_masked_high_by(NonZero::::MIN, HIGH_MASK) ); assert_eq!( - NonZeroU32::MIN, - IdentifierMask::inc_masked_high_by(NonZeroU32::MIN, u32::MAX) + NonZero::::MIN, + IdentifierMask::inc_masked_high_by(NonZero::::MIN, u32::MAX) ); // Adding from absolute highest value with lowest to highest increment // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MAX, 0) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MAX, 0) ); assert_eq!( - NonZeroU32::MIN, - IdentifierMask::inc_masked_high_by(NonZeroU32::MAX, 1) + NonZero::::MIN, + IdentifierMask::inc_masked_high_by(NonZero::::MAX, 1) ); assert_eq!( - NonZeroU32::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MAX, 2) + NonZero::::new(2).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MAX, 2) ); assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MAX, HIGH_MASK) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MAX, HIGH_MASK) ); assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::MAX, u32::MAX) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::MAX, u32::MAX) ); // Adding from actual highest value with lowest to highest increment // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::new(HIGH_MASK).unwrap(), 0) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 0) ); assert_eq!( - NonZeroU32::MIN, - IdentifierMask::inc_masked_high_by(NonZeroU32::new(HIGH_MASK).unwrap(), 1) + NonZero::::MIN, + IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 1) ); assert_eq!( - NonZeroU32::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::new(HIGH_MASK).unwrap(), 2) + NonZero::::new(2).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 2) ); assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::new(HIGH_MASK).unwrap(), HIGH_MASK) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), HIGH_MASK) ); assert_eq!( - NonZeroU32::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZeroU32::new(HIGH_MASK).unwrap(), u32::MAX) + NonZero::::new(HIGH_MASK).unwrap(), + IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), u32::MAX) ); } } diff --git a/crates/bevy_ecs/src/identifier/mod.rs b/crates/bevy_ecs/src/identifier/mod.rs index 04a93cde45c0c..e9c7df8006e38 100644 --- a/crates/bevy_ecs/src/identifier/mod.rs +++ b/crates/bevy_ecs/src/identifier/mod.rs @@ -7,7 +7,7 @@ use bevy_reflect::Reflect; use self::{error::IdentifierError, kinds::IdKind, masks::IdentifierMask}; -use std::{hash::Hash, num::NonZeroU32}; +use std::{hash::Hash, num::NonZero}; pub mod error; pub(crate) mod kinds; @@ -28,7 +28,7 @@ pub struct Identifier { // to make this struct equivalent to a u64. #[cfg(target_endian = "little")] low: u32, - high: NonZeroU32, + high: NonZero, #[cfg(target_endian = "big")] low: u32, } @@ -56,7 +56,7 @@ impl Identifier { unsafe { Ok(Self { low, - high: NonZeroU32::new_unchecked(packed_high), + high: NonZero::::new_unchecked(packed_high), }) } } @@ -71,7 +71,7 @@ impl Identifier { /// Returns the value of the high segment of the [`Identifier`]. This /// does not apply any masking. #[inline(always)] - pub const fn high(self) -> NonZeroU32 { + pub const fn high(self) -> NonZero { self.high } @@ -114,7 +114,7 @@ impl Identifier { /// This method is the fallible counterpart to [`Identifier::from_bits`]. #[inline(always)] pub const fn try_from_bits(value: u64) -> Result { - let high = NonZeroU32::new(IdentifierMask::get_high(value)); + let high = NonZero::::new(IdentifierMask::get_high(value)); match high { Some(high) => Ok(Self { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 09e86f7711cca..442c25d9a2662 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -88,7 +88,7 @@ mod tests { }; use bevy_tasks::{ComputeTaskPool, TaskPool}; use bevy_utils::HashSet; - use std::num::NonZeroU32; + use std::num::NonZero; use std::{ any::TypeId, marker::PhantomData, @@ -1659,7 +1659,7 @@ mod tests { ); let e4_mismatched_generation = - Entity::from_raw_and_generation(3, NonZeroU32::new(2).unwrap()); + Entity::from_raw_and_generation(3, NonZero::::new(2).unwrap()); assert!( world_b.get_or_spawn(e4_mismatched_generation).is_none(), "attempting to spawn on top of an entity with a mismatched entity generation fails" @@ -1754,7 +1754,8 @@ mod tests { let e0 = world.spawn(A(0)).id(); let e1 = Entity::from_raw(1); let e2 = world.spawn_empty().id(); - let invalid_e2 = Entity::from_raw_and_generation(e2.index(), NonZeroU32::new(2).unwrap()); + let invalid_e2 = + Entity::from_raw_and_generation(e2.index(), NonZero::::new(2).unwrap()); let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))]; diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index dca9c1542a551..d5699f37164cb 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -1,7 +1,7 @@ use std::{ alloc::{handle_alloc_error, Layout}, cell::UnsafeCell, - num::NonZeroUsize, + num::NonZero, ptr::NonNull, }; @@ -56,7 +56,7 @@ impl BlobVec { drop: Option)>, capacity: usize, ) -> BlobVec { - let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0"); + let align = NonZero::::new(item_layout.align()).expect("alignment must be > 0"); let data = bevy_ptr::dangling_with_align(align); if item_layout.size() == 0 { BlobVec { @@ -119,7 +119,8 @@ impl BlobVec { let available_space = self.capacity - self.len; if available_space < additional { // SAFETY: `available_space < additional`, so `additional - available_space > 0` - let increment = unsafe { NonZeroUsize::new_unchecked(additional - available_space) }; + let increment = + unsafe { NonZero::::new_unchecked(additional - available_space) }; self.grow_exact(increment); } } @@ -132,7 +133,7 @@ impl BlobVec { #[cold] fn do_reserve(slf: &mut BlobVec, additional: usize) { let increment = slf.capacity.max(additional - (slf.capacity - slf.len)); - let increment = NonZeroUsize::new(increment).unwrap(); + let increment = NonZero::::new(increment).unwrap(); slf.grow_exact(increment); } @@ -148,7 +149,7 @@ impl BlobVec { /// Panics if the new capacity overflows `usize`. /// For ZST it panics unconditionally because ZST `BlobVec` capacity /// is initialized to `usize::MAX` and always stays that way. - fn grow_exact(&mut self, increment: NonZeroUsize) { + fn grow_exact(&mut self, increment: NonZero) { let new_capacity = self .capacity .checked_add(increment.get()) diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index fe913a1d196bb..7da6c5da026cf 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -1,6 +1,6 @@ //! Spatial clustering of objects, currently just point and spot lights. -use std::num::NonZeroU64; +use std::num::NonZero; use bevy_core_pipeline::core_3d::Camera3d; use bevy_ecs::{ @@ -468,7 +468,7 @@ impl GpuClusterableObjects { } } - pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZeroU64 { + pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero { match buffer_binding_type { BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(), BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(), @@ -749,7 +749,7 @@ impl ViewClusterBindings { pub fn min_size_clusterable_object_index_lists( buffer_binding_type: BufferBindingType, - ) -> NonZeroU64 { + ) -> NonZero { match buffer_binding_type { BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(), BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(), @@ -758,7 +758,7 @@ impl ViewClusterBindings { pub fn min_size_cluster_offsets_and_counts( buffer_binding_type: BufferBindingType, - ) -> NonZeroU64 { + ) -> NonZero { match buffer_binding_type { BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(), BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(), diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 8a78e93083024..1b1604df4d48d 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -65,7 +65,7 @@ use bevy_render::{ texture::{FallbackImage, GpuImage, Image}, }; -use std::num::NonZeroU32; +use std::num::NonZero; use std::ops::Deref; use crate::{ @@ -217,7 +217,7 @@ pub(crate) fn get_bind_group_layout_entries( binding_types::texture_cube(TextureSampleType::Float { filterable: true }); if binding_arrays_are_usable(render_device) { texture_cube_binding = - texture_cube_binding.count(NonZeroU32::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); + texture_cube_binding.count(NonZero::::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); } [ diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 618da04fa4490..58110e98afb29 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -142,7 +142,7 @@ use bevy_render::{ renderer::RenderDevice, texture::{FallbackImage, GpuImage, Image}, }; -use std::{num::NonZeroU32, ops::Deref}; +use std::{num::NonZero, ops::Deref}; use bevy_asset::{AssetId, Handle}; use bevy_reflect::Reflect; @@ -306,7 +306,7 @@ pub(crate) fn get_bind_group_layout_entries( binding_types::texture_3d(TextureSampleType::Float { filterable: true }); if binding_arrays_are_usable(render_device) { texture_3d_binding = - texture_3d_binding.count(NonZeroU32::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); + texture_3d_binding.count(NonZero::::new(MAX_VIEW_LIGHT_PROBES as _).unwrap()); } [ diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cee8129febe75..9da61b2ce9e64 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -35,7 +35,7 @@ use bevy_render::{ use bevy_utils::tracing::error; use std::marker::PhantomData; use std::sync::atomic::{AtomicU32, Ordering}; -use std::{hash::Hash, num::NonZeroU32}; +use std::{hash::Hash, num::NonZero}; use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; @@ -978,7 +978,7 @@ impl AtomicMaterialBindGroupId { /// See also: [`AtomicU32::store`]. pub fn set(&self, id: MaterialBindGroupId) { let id = if let Some(id) = id.0 { - NonZeroU32::from(id).get() + NonZero::::from(id).get() } else { 0 }; @@ -990,7 +990,9 @@ impl AtomicMaterialBindGroupId { /// /// See also: [`AtomicU32::load`]. pub fn get(&self) -> MaterialBindGroupId { - MaterialBindGroupId(NonZeroU32::new(self.0.load(Ordering::Relaxed)).map(BindGroupId::from)) + MaterialBindGroupId( + NonZero::::new(self.0.load(Ordering::Relaxed)).map(BindGroupId::from), + ) } } diff --git a/crates/bevy_pbr/src/meshlet/persistent_buffer.rs b/crates/bevy_pbr/src/meshlet/persistent_buffer.rs index 60e163a87446a..e10dad6ef0aed 100644 --- a/crates/bevy_pbr/src/meshlet/persistent_buffer.rs +++ b/crates/bevy_pbr/src/meshlet/persistent_buffer.rs @@ -6,7 +6,7 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, }; use range_alloc::RangeAllocator; -use std::{num::NonZeroU64, ops::Range}; +use std::{num::NonZero, ops::Range}; /// Wrapper for a GPU buffer holding a large amount of data that persists across frames. pub struct PersistentGpuBuffer { @@ -66,7 +66,8 @@ impl PersistentGpuBuffer { let queue_count = self.write_queue.len(); for (data, metadata, buffer_slice) in self.write_queue.drain(..) { - let buffer_slice_size = NonZeroU64::new(buffer_slice.end - buffer_slice.start).unwrap(); + let buffer_slice_size = + NonZero::::new(buffer_slice.end - buffer_slice.start).unwrap(); let mut buffer_view = render_queue .write_buffer_with(&self.buffer, buffer_slice.start, buffer_slice_size) .unwrap(); diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 67eca5df5e13d..38650d280891b 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -6,7 +6,7 @@ //! [`MeshInputUniform`]s instead and use the GPU to calculate the remaining //! derived fields in [`MeshUniform`]. -use std::num::NonZeroU64; +use std::num::NonZero; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; @@ -408,7 +408,7 @@ pub fn prepare_preprocess_bind_groups( // Don't use `as_entire_binding()` here; the shader reads the array // length and the underlying buffer may be longer than the actual size // of the vector. - let index_buffer_size = NonZeroU64::try_from( + let index_buffer_size = NonZero::::try_from( index_buffer_vec.buffer.len() as u64 * u64::from(PreprocessWorkItem::min_size()), ) .ok(); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 36c5d7bfe044f..d0a506e9c3e42 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -1,4 +1,4 @@ -use std::{array, num::NonZeroU64, sync::Arc}; +use std::{array, num::NonZero, sync::Arc}; use bevy_core_pipeline::{ core_3d::ViewTransmissionTexture, @@ -164,7 +164,7 @@ impl From> for MeshPipelineViewLayoutKey { fn buffer_layout( buffer_binding_type: BufferBindingType, has_dynamic_offset: bool, - min_binding_size: Option, + min_binding_size: Option>, ) -> BindGroupLayoutEntryBuilder { match buffer_binding_type { BufferBindingType::Uniform => uniform_buffer_sized(has_dynamic_offset, min_binding_size), diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 24ea602296256..5b2186c44fbc5 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -12,7 +12,7 @@ use core::{ fmt::{self, Formatter, Pointer}, marker::PhantomData, mem::{align_of, ManuallyDrop}, - num::NonZeroUsize, + num::NonZero, ptr::NonNull, }; @@ -535,10 +535,10 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> { /// Creates a dangling pointer with specified alignment. /// See [`NonNull::dangling`]. -pub fn dangling_with_align(align: NonZeroUsize) -> NonNull { +pub fn dangling_with_align(align: NonZero) -> NonNull { debug_assert!(align.is_power_of_two(), "Alignment must be power of two."); // SAFETY: The pointer will not be null, since it was created - // from the address of a `NonZeroUsize`. + // from the address of a `NonZero`. unsafe { NonNull::new_unchecked(align.get() as *mut u8) } } diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 6320090e6d4f6..c9f27a4cb34a4 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -2444,11 +2444,11 @@ mod tests { #[test] fn nonzero_usize_impl_reflect_from_reflect() { - let a: &dyn PartialReflect = &std::num::NonZeroUsize::new(42).unwrap(); - let b: &dyn PartialReflect = &std::num::NonZeroUsize::new(42).unwrap(); + let a: &dyn PartialReflect = &std::num::NonZero::::new(42).unwrap(); + let b: &dyn PartialReflect = &std::num::NonZero::::new(42).unwrap(); assert!(a.reflect_partial_eq(b).unwrap_or_default()); - let forty_two: std::num::NonZeroUsize = FromReflect::from_reflect(a).unwrap(); - assert_eq!(forty_two, std::num::NonZeroUsize::new(42).unwrap()); + let forty_two: std::num::NonZero = FromReflect::from_reflect(a).unwrap(); + assert_eq!(forty_two, std::num::NonZero::::new(42).unwrap()); } #[test] 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 d92c7a3897e06..75a747cf46fe3 100644 --- a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -8,7 +8,7 @@ use encase::{ ShaderType, }; use nonmax::NonMaxU32; -use std::{marker::PhantomData, num::NonZeroU64}; +use std::{marker::PhantomData, num::NonZero}; use wgpu::{BindingResource, Limits}; // 1MB else we will make really large arrays on macOS which reports very large @@ -69,7 +69,7 @@ impl BatchedUniformBuffer { } #[inline] - pub fn size(&self) -> NonZeroU64 { + pub fn size(&self) -> NonZero { self.temp.size() } @@ -141,7 +141,7 @@ where const METADATA: Metadata = T::METADATA; - fn size(&self) -> NonZeroU64 { + fn size(&self) -> NonZero { Self::METADATA.stride().mul(self.1.max(1) as u64).0 } } diff --git a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs b/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs index 05c5ee9c3e818..58d1d62a19889 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs @@ -1,5 +1,5 @@ use bevy_utils::all_tuples_with_size; -use std::num::NonZeroU32; +use std::num::NonZero; use wgpu::{BindGroupLayoutEntry, BindingType, ShaderStages}; /// Helper for constructing bind group layouts. @@ -130,7 +130,7 @@ use wgpu::{BindGroupLayoutEntry, BindingType, ShaderStages}; pub struct BindGroupLayoutEntryBuilder { ty: BindingType, visibility: Option, - count: Option, + count: Option>, } impl BindGroupLayoutEntryBuilder { @@ -139,7 +139,7 @@ impl BindGroupLayoutEntryBuilder { self } - pub fn count(mut self, count: NonZeroU32) -> Self { + pub fn count(mut self, count: NonZero) -> Self { self.count = Some(count); self } @@ -353,7 +353,7 @@ pub mod binding_types { BufferBindingType, SamplerBindingType, TextureSampleType, TextureViewDimension, }; use encase::ShaderType; - use std::num::NonZeroU64; + use std::num::NonZero; use wgpu::{StorageTextureAccess, TextureFormat}; use super::*; @@ -364,7 +364,7 @@ pub mod binding_types { pub fn storage_buffer_sized( has_dynamic_offset: bool, - min_binding_size: Option, + min_binding_size: Option>, ) -> BindGroupLayoutEntryBuilder { BindingType::Buffer { ty: BufferBindingType::Storage { read_only: false }, @@ -382,7 +382,7 @@ pub mod binding_types { pub fn storage_buffer_read_only_sized( has_dynamic_offset: bool, - min_binding_size: Option, + min_binding_size: Option>, ) -> BindGroupLayoutEntryBuilder { BindingType::Buffer { ty: BufferBindingType::Storage { read_only: true }, @@ -398,7 +398,7 @@ pub mod binding_types { pub fn uniform_buffer_sized( has_dynamic_offset: bool, - min_binding_size: Option, + min_binding_size: Option>, ) -> BindGroupLayoutEntryBuilder { BindingType::Buffer { ty: BufferBindingType::Uniform, diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs index 68896092ce016..e22c59b1c031f 100644 --- a/crates/bevy_render/src/render_resource/resource_macros.rs +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -149,7 +149,7 @@ macro_rules! render_resource_wrapper { macro_rules! define_atomic_id { ($atomic_id_type:ident) => { #[derive(Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, Debug)] - pub struct $atomic_id_type(core::num::NonZeroU32); + pub struct $atomic_id_type(core::num::NonZero); // We use new instead of default to indicate that each ID created will be unique. #[allow(clippy::new_without_default)] @@ -160,7 +160,7 @@ macro_rules! define_atomic_id { static COUNTER: AtomicU32 = AtomicU32::new(1); let counter = COUNTER.fetch_add(1, Ordering::Relaxed); - Self(core::num::NonZeroU32::new(counter).unwrap_or_else(|| { + Self(core::num::NonZero::::new(counter).unwrap_or_else(|| { panic!( "The system ran out of unique `{}`s.", stringify!($atomic_id_type) @@ -169,14 +169,14 @@ macro_rules! define_atomic_id { } } - impl From<$atomic_id_type> for core::num::NonZeroU32 { + impl From<$atomic_id_type> for core::num::NonZero { fn from(value: $atomic_id_type) -> Self { value.0 } } - impl From for $atomic_id_type { - fn from(value: core::num::NonZeroU32) -> Self { + impl From> for $atomic_id_type { + fn from(value: core::num::NonZero) -> Self { Self(value) } } diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index de4d84c94a06e..db51653f146ea 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -1,4 +1,4 @@ -use std::{marker::PhantomData, num::NonZeroU64}; +use std::{marker::PhantomData, num::NonZero}; use crate::{ render_resource::Buffer, @@ -309,7 +309,7 @@ impl DynamicUniformBuffer { if let Some(buffer) = self.buffer.as_deref() { let buffer_view = queue - .write_buffer_with(buffer, 0, NonZeroU64::new(buffer.size())?) + .write_buffer_with(buffer, 0, NonZero::::new(buffer.size())?) .unwrap(); Some(DynamicUniformBufferWriter { buffer: encase::DynamicUniformBuffer::new_with_alignment( diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 816d8c4e8dfd7..d9d30d440dcf5 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -13,7 +13,7 @@ use bevy_window::{ }; use bevy_winit::CustomCursorCache; use std::{ - num::NonZeroU32, + num::NonZero, ops::{Deref, DerefMut}, }; use wgpu::{ @@ -63,7 +63,7 @@ pub struct ExtractedWindow { pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, - pub desired_maximum_frame_latency: Option, + pub desired_maximum_frame_latency: Option>, /// Note: this will not always be the swap chain texture view. When taking a screenshot, /// this will point to an alternative texture instead to allow for copying the render result /// to CPU memory. @@ -395,7 +395,7 @@ pub fn create_surfaces( }, desired_maximum_frame_latency: window .desired_maximum_frame_latency - .map(NonZeroU32::get) + .map(NonZero::::get) .unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY), alpha_mode: match window.alpha_mode { CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index 3eb33c6603e70..1fe4f9511d997 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -55,7 +55,7 @@ pub mod prelude { }; } -use std::num::NonZeroUsize; +use std::num::NonZero; /// Gets the logical CPU core count available to the current process. /// @@ -65,6 +65,6 @@ use std::num::NonZeroUsize; /// This will always return at least 1. pub fn available_parallelism() -> usize { std::thread::available_parallelism() - .map(NonZeroUsize::get) + .map(NonZero::::get) .unwrap_or(1) } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index fb285823b3798..3a46e952b32a7 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -12,7 +12,7 @@ use bevy_transform::prelude::GlobalTransform; use bevy_utils::warn_once; use bevy_window::{PrimaryWindow, WindowRef}; use smallvec::SmallVec; -use std::num::{NonZeroI16, NonZeroU16}; +use std::num::NonZero; use thiserror::Error; /// Base component for a UI node, which also provides the computed size of the node. @@ -1481,15 +1481,15 @@ pub struct GridPlacement { /// Lines are 1-indexed. /// Negative indexes count backwards from the end of the grid. /// Zero is not a valid index. - pub(crate) start: Option, + pub(crate) start: Option>, /// How many grid tracks the item should span. /// Defaults to 1. - pub(crate) span: Option, + pub(crate) span: Option>, /// The grid line at which the item should end. /// Lines are 1-indexed. /// Negative indexes count backwards from the end of the grid. /// Zero is not a valid index. - pub(crate) end: Option, + pub(crate) end: Option>, } impl GridPlacement { @@ -1497,7 +1497,7 @@ impl GridPlacement { pub const DEFAULT: Self = Self { start: None, // SAFETY: This is trivially safe as 1 is non-zero. - span: Some(unsafe { NonZeroU16::new_unchecked(1) }), + span: Some(unsafe { NonZero::::new_unchecked(1) }), end: None, }; @@ -1614,17 +1614,17 @@ impl GridPlacement { /// Returns the grid line at which the item should start, or `None` if not set. pub fn get_start(self) -> Option { - self.start.map(NonZeroI16::get) + self.start.map(NonZero::::get) } /// Returns the grid line at which the item should end, or `None` if not set. pub fn get_end(self) -> Option { - self.end.map(NonZeroI16::get) + self.end.map(NonZero::::get) } /// Returns span for this grid item, or `None` if not set. pub fn get_span(self) -> Option { - self.span.map(NonZeroU16::get) + self.span.map(NonZero::::get) } } @@ -1634,17 +1634,17 @@ impl Default for GridPlacement { } } -/// Convert an `i16` to `NonZeroI16`, fails on `0` and returns the `InvalidZeroIndex` error. -fn try_into_grid_index(index: i16) -> Result, GridPlacementError> { +/// Convert an `i16` to `NonZero`, fails on `0` and returns the `InvalidZeroIndex` error. +fn try_into_grid_index(index: i16) -> Result>, GridPlacementError> { Ok(Some( - NonZeroI16::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?, + NonZero::::new(index).ok_or(GridPlacementError::InvalidZeroIndex)?, )) } -/// Convert a `u16` to `NonZeroU16`, fails on `0` and returns the `InvalidZeroSpan` error. -fn try_into_grid_span(span: u16) -> Result, GridPlacementError> { +/// Convert a `u16` to `NonZero`, fails on `0` and returns the `InvalidZeroSpan` error. +fn try_into_grid_span(span: u16) -> Result>, GridPlacementError> { Ok(Some( - NonZeroU16::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?, + NonZero::::new(span).ok_or(GridPlacementError::InvalidZeroSpan)?, )) } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index dfaa0ecdae36b..eb8ca610fc9af 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroU32; +use std::num::NonZero; use bevy_ecs::{ entity::{Entity, EntityMapper, MapEntities}, @@ -279,7 +279,7 @@ pub struct Window { /// /// [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`]: /// https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html#structfield.desired_maximum_frame_latency - pub desired_maximum_frame_latency: Option, + pub desired_maximum_frame_latency: Option>, /// Sets whether this window recognizes [`PinchGesture`](https://docs.rs/bevy/latest/bevy/input/gestures/struct.PinchGesture.html) /// /// ## Platform-specific diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index 2a882063d9abf..f6fa5c347257d 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -381,7 +381,7 @@ impl render_graph::Node for ImageCopyDriver { layout: ImageDataLayout { offset: 0, bytes_per_row: Some( - std::num::NonZeroU32::new(padded_bytes_per_row as u32) + std::num::NonZero::::new(padded_bytes_per_row as u32) .unwrap() .into(), ), diff --git a/examples/shader/texture_binding_array.rs b/examples/shader/texture_binding_array.rs index 53e774df0485e..24a8a847a1c19 100644 --- a/examples/shader/texture_binding_array.rs +++ b/examples/shader/texture_binding_array.rs @@ -17,7 +17,7 @@ use bevy::{ RenderApp, }, }; -use std::{num::NonZeroU32, process::exit}; +use std::{num::NonZero, process::exit}; /// This example uses a shader source file from the assets subdirectory const SHADER_ASSET_PATH: &str = "shaders/texture_binding_array.wgsl"; @@ -166,7 +166,7 @@ impl AsBindGroup for BindlessMaterial { ( 0, texture_2d(TextureSampleType::Float { filterable: true }) - .count(NonZeroU32::new(MAX_TEXTURE_COUNT as u32).unwrap()), + .count(NonZero::::new(MAX_TEXTURE_COUNT as u32).unwrap()), ), // Sampler // @@ -177,7 +177,7 @@ impl AsBindGroup for BindlessMaterial { // // ``` // sampler(SamplerBindingType::Filtering) - // .count(NonZeroU32::new(MAX_TEXTURE_COUNT as u32).unwrap()), + // .count(NonZero::::new(MAX_TEXTURE_COUNT as u32).unwrap()), // ``` // // One may need to pay attention to the limit of sampler binding From ffe0f7f2ba592139f5414c61c82e4a89c2c81344 Mon Sep 17 00:00:00 2001 From: BigWingBeat Date: Fri, 30 Aug 2024 19:57:08 +0100 Subject: [PATCH 009/219] Fix compile error caused by incorrect feature flag in `bevy_state` (#14987) # Objective The `reflect` module in `bevy_state` is gated behind the `bevy_reflect` feature, but the type exports from that module in the crate prelude are erroneously gated behind the `bevy_app` feature, causing a compile error when the `bevy_reflect` feature is disabled, but the `bevy_app` feature is enabled. ## Solution Change the feature gate to `bevy_reflect`. ## Testing - Discovered by depending on `bevy_state` with `default-features = false, features = ["bevy_app"]` - Tested by running `cargo check -p bevy_state --no-default-features --features bevy_app` --- crates/bevy_state/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 5506221ce35cf..b9ded18d4ef0e 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -54,7 +54,7 @@ pub mod prelude { pub use crate::app::AppExtStates; #[doc(hidden)] pub use crate::condition::*; - #[cfg(feature = "bevy_app")] + #[cfg(feature = "bevy_reflect")] #[doc(hidden)] pub use crate::reflect::{ReflectFreelyMutableState, ReflectState}; #[doc(hidden)] From 4bea611a434128873598b547b37e3f253865ba38 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Sat, 31 Aug 2024 14:38:34 -0400 Subject: [PATCH 010/219] Don't require going through `bevy_animation::prelude` to get to certain items in `bevy_animation` (#14979) # Objective * Fixes https://github.com/bevyengine/bevy/issues/14889 ## Solution Exposes `bevy_animation::{animatable, graph, transition}` to the world. ## Testing - Did you test these changes? If so, how? - These changes do not need testing, as they do not modify/add/remove any functionality. - ~~Are there any parts that need more testing?~~ - ~~How can other people (reviewers) test your changes? Is there anything specific they need to know?~~ - ~~If relevant, what platforms did you test these changes on, and are there any important ones you can't test?~~ --------- Co-authored-by: Alice Cecile --- crates/bevy_animation/src/animatable.rs | 2 ++ crates/bevy_animation/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_animation/src/animatable.rs b/crates/bevy_animation/src/animatable.rs index 4e59ccc8b2875..c51b995b3c20a 100644 --- a/crates/bevy_animation/src/animatable.rs +++ b/crates/bevy_animation/src/animatable.rs @@ -1,3 +1,5 @@ +//! Traits and type for interpolating between values. + use crate::util; use bevy_color::{Laba, LinearRgba, Oklaba, Srgba, Xyza}; use bevy_ecs::world::World; diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 4277f0b1141b0..f1382906c7dfd 100755 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -7,9 +7,9 @@ //! Animation for the game engine Bevy -mod animatable; -mod graph; -mod transition; +pub mod animatable; +pub mod graph; +pub mod transition; mod util; use std::cell::RefCell; From f0560b8e78528afd70ca913cbebf2ed459fa2716 Mon Sep 17 00:00:00 2001 From: charlotte Date: Sat, 31 Aug 2024 15:03:01 -0700 Subject: [PATCH 011/219] Ensure more explicit system ordering for preparing view target. (#15000) Fixes #14993 (maybe). Adds a system ordering constraint that was missed in the refactor in #14833. The theory here is that the single threaded forces a topology that causes the prepare system to run before `prepare_windows` in a way that causes issues. For whatever reason, this appears to be unlikely when multi-threading is enabled. --- crates/bevy_render/src/view/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 3729f4048ff21..d4392c8e7c6ff 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -123,7 +123,8 @@ impl Plugin for ViewPlugin { ( prepare_view_attachments .in_set(RenderSet::ManageViews) - .before(prepare_view_targets), + .before(prepare_view_targets) + .after(prepare_windows), prepare_view_targets .in_set(RenderSet::ManageViews) .after(prepare_windows) From 41474226c3b8efa1c4515a3a39e79dcb127ea5c6 Mon Sep 17 00:00:00 2001 From: UkoeHB <37489173+UkoeHB@users.noreply.github.com> Date: Sun, 1 Sep 2024 06:50:54 -0500 Subject: [PATCH 012/219] Optimize UI text measurement (#15003) # Objective - Avoid cloning the `CosmicBuffer` every time you create a new text measurement. ## Solution - Inject a buffer query when calculating layout so existing buffers can be reused. ## Testing - I tested the `text`, `text_debug`, and `text_wrap_debug` examples. - I did not do a performance test. --- crates/bevy_text/src/pipeline.rs | 31 ++++++++------------- crates/bevy_ui/src/layout/mod.rs | 7 ++++- crates/bevy_ui/src/layout/ui_surface.rs | 37 +++++++++++++++++++++++-- crates/bevy_ui/src/measurement.rs | 2 ++ crates/bevy_ui/src/widget/text.rs | 31 +++++++++++++++++---- 5 files changed, 81 insertions(+), 27 deletions(-) diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 6568a9204462e..a67e79f663352 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use bevy_asset::{AssetId, Assets}; -use bevy_ecs::{component::Component, reflect::ReflectComponent, system::Resource}; +use bevy_ecs::{component::Component, entity::Entity, reflect::ReflectComponent, system::Resource}; use bevy_math::{UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::texture::Image; @@ -238,8 +238,10 @@ impl TextPipeline { /// /// Produces a [`TextMeasureInfo`] which can be used by a layout system /// to measure the text area on demand. + #[allow(clippy::too_many_arguments)] pub fn create_text_measure( &mut self, + entity: Entity, fonts: &Assets, sections: &[TextSection], scale_factor: f64, @@ -270,9 +272,7 @@ impl TextPipeline { Ok(TextMeasureInfo { min: min_width_content_size, max: max_width_content_size, - // TODO: This clone feels wasteful, is there another way to structure TextMeasureInfo - // that it doesn't need to own a buffer? - bytemunch - buffer: buffer.0.clone(), + entity, }) } @@ -299,23 +299,14 @@ pub struct TextLayoutInfo { /// Size information for a corresponding [`Text`](crate::Text) component. /// /// Generated via [`TextPipeline::create_text_measure`]. +#[derive(Debug)] pub struct TextMeasureInfo { /// Minimum size for a text area in pixels, to be used when laying out widgets with taffy pub min: Vec2, /// Maximum size for a text area in pixels, to be used when laying out widgets with taffy pub max: Vec2, - buffer: Buffer, -} - -impl std::fmt::Debug for TextMeasureInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TextMeasureInfo") - .field("min", &self.min) - .field("max", &self.max) - .field("buffer", &"_") - .field("font_system", &"_") - .finish() - } + /// The entity that is measured. + pub entity: Entity, } impl TextMeasureInfo { @@ -323,11 +314,13 @@ impl TextMeasureInfo { pub fn compute_size( &mut self, bounds: TextBounds, + buffer: &mut Buffer, font_system: &mut cosmic_text::FontSystem, ) -> Vec2 { - self.buffer - .set_size(font_system, bounds.width, bounds.height); - buffer_dimensions(&self.buffer) + // Note that this arbitrarily adjusts the buffer layout. We assume the buffer is always 'refreshed' + // whenever a canonical state is required. + buffer.set_size(font_system, bounds.width, bounds.height); + buffer_dimensions(buffer) } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index f74ef601319cb..53ac334c601d3 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -12,7 +12,7 @@ use bevy_hierarchy::{Children, Parent}; use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; #[cfg(feature = "bevy_text")] -use bevy_text::TextPipeline; +use bevy_text::{CosmicBuffer, TextPipeline}; use bevy_transform::components::Transform; use bevy_utils::tracing::warn; use bevy_utils::{HashMap, HashSet}; @@ -95,6 +95,7 @@ pub fn ui_layout_system( just_children_query: Query<&Children>, mut removed_components: UiLayoutSystemRemovedComponentParam, mut node_transform_query: Query<(&mut Node, &mut Transform)>, + #[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>, #[cfg(feature = "bevy_text")] mut text_pipeline: ResMut, ) { struct CameraLayoutInfo { @@ -217,6 +218,8 @@ pub fn ui_layout_system( } }); + #[cfg(feature = "bevy_text")] + let text_buffers = &mut buffer_query; #[cfg(feature = "bevy_text")] let font_system = text_pipeline.font_system_mut(); // clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used) @@ -236,6 +239,8 @@ pub fn ui_layout_system( *camera_id, camera.size, #[cfg(feature = "bevy_text")] + text_buffers, + #[cfg(feature = "bevy_text")] font_system, ); for root in &camera.root_nodes { diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs index f191042aa3c13..0cca1d6a0b695 100644 --- a/crates/bevy_ui/src/layout/ui_surface.rs +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -196,11 +196,14 @@ without UI components as a child of an entity with UI components, results may be } /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_camera_layout( + pub fn compute_camera_layout<'a>( &mut self, camera: Entity, render_target_resolution: UVec2, - #[cfg(feature = "bevy_text")] font_system: &mut bevy_text::cosmic_text::FontSystem, + #[cfg(feature = "bevy_text")] buffer_query: &'a mut bevy_ecs::prelude::Query< + &mut bevy_text::CosmicBuffer, + >, + #[cfg(feature = "bevy_text")] font_system: &'a mut bevy_text::cosmic_text::FontSystem, ) { let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { return; @@ -223,6 +226,15 @@ without UI components as a child of an entity with UI components, results may be -> taffy::Size { context .map(|ctx| { + #[cfg(feature = "bevy_text")] + let buffer = get_text_buffer( + crate::widget::TextMeasure::needs_buffer( + known_dimensions.height, + available_space.width, + ), + ctx, + buffer_query, + ); let size = ctx.measure( MeasureArgs { width: known_dimensions.width, @@ -231,6 +243,8 @@ without UI components as a child of an entity with UI components, results may be available_height: available_space.height, #[cfg(feature = "bevy_text")] font_system, + #[cfg(feature = "bevy_text")] + buffer, #[cfg(not(feature = "bevy_text"))] font_system: std::marker::PhantomData, }, @@ -284,3 +298,22 @@ with UI components as a child of an entity without UI components, results may be } } } + +#[cfg(feature = "bevy_text")] +fn get_text_buffer<'a>( + needs_buffer: bool, + ctx: &mut NodeMeasure, + query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::CosmicBuffer>, +) -> Option<&'a mut bevy_text::cosmic_text::Buffer> { + // We avoid a query lookup whenever the buffer is not required. + if !needs_buffer { + return None; + } + let NodeMeasure::Text(crate::widget::TextMeasure { info }) = ctx else { + return None; + }; + let Ok(buffer) = query.get_mut(info.entity) else { + return None; + }; + Some(buffer.into_inner()) +} diff --git a/crates/bevy_ui/src/measurement.rs b/crates/bevy_ui/src/measurement.rs index 5c565930f53d0..647bc27a4a92a 100644 --- a/crates/bevy_ui/src/measurement.rs +++ b/crates/bevy_ui/src/measurement.rs @@ -23,6 +23,8 @@ pub struct MeasureArgs<'a> { pub available_height: AvailableSpace, #[cfg(feature = "bevy_text")] pub font_system: &'a mut bevy_text::cosmic_text::FontSystem, + #[cfg(feature = "bevy_text")] + pub buffer: Option<&'a mut bevy_text::cosmic_text::Buffer>, // When `bevy_text` is disabled, use `PhantomData` in order to keep lifetime in type signature. #[cfg(not(feature = "bevy_text"))] pub font_system: std::marker::PhantomData<&'a mut ()>, diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 5750cc6e77e4d..e32296c12c6e5 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -19,7 +19,7 @@ use bevy_text::{ scale_value, BreakLineOn, CosmicBuffer, Font, FontAtlasSets, JustifyText, Text, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, YAxisOrientation, }; -use bevy_utils::Entry; +use bevy_utils::{tracing::error, Entry}; use taffy::style::AvailableSpace; /// Text system flags @@ -47,12 +47,20 @@ pub struct TextMeasure { pub info: TextMeasureInfo, } +impl TextMeasure { + /// Checks if the cosmic text buffer is needed for measuring the text. + pub fn needs_buffer(height: Option, available_width: AvailableSpace) -> bool { + height.is_none() && matches!(available_width, AvailableSpace::Definite(_)) + } +} + impl Measure for TextMeasure { fn measure(&mut self, measure_args: MeasureArgs, _style: &taffy::Style) -> Vec2 { let MeasureArgs { width, height, available_width, + buffer, font_system, .. } = measure_args; @@ -71,9 +79,18 @@ impl Measure for TextMeasure { height .map_or_else( || match available_width { - AvailableSpace::Definite(_) => self - .info - .compute_size(TextBounds::new_horizontal(x), font_system), + AvailableSpace::Definite(_) => { + if let Some(buffer) = buffer { + self.info.compute_size( + TextBounds::new_horizontal(x), + buffer, + font_system, + ) + } else { + error!("text measure failed, buffer is missing"); + Vec2::default() + } + } AvailableSpace::MinContent => Vec2::new(x, self.info.min.y), AvailableSpace::MaxContent => Vec2::new(x, self.info.max.y), }, @@ -86,6 +103,7 @@ impl Measure for TextMeasure { #[allow(clippy::too_many_arguments)] #[inline] fn create_text_measure( + entity: Entity, fonts: &Assets, scale_factor: f64, text: Ref, @@ -96,6 +114,7 @@ fn create_text_measure( text_alignment: JustifyText, ) { match text_pipeline.create_text_measure( + entity, fonts, &text.sections, scale_factor, @@ -141,6 +160,7 @@ pub fn measure_text_system( ui_scale: Res, mut text_query: Query< ( + Entity, Ref, &mut ContentSize, &mut TextFlags, @@ -153,7 +173,7 @@ pub fn measure_text_system( ) { let mut scale_factors: EntityHashMap = EntityHashMap::default(); - for (text, content_size, text_flags, camera, mut buffer) in &mut text_query { + for (entity, text, content_size, text_flags, camera, mut buffer) in &mut text_query { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { continue; @@ -176,6 +196,7 @@ pub fn measure_text_system( { let text_alignment = text.justify; create_text_measure( + entity, &fonts, scale_factor.into(), text, From 3a8d5598ad3268fca59a05fd19e173163f3a76d9 Mon Sep 17 00:00:00 2001 From: no-materials Date: Mon, 2 Sep 2024 00:18:13 +0200 Subject: [PATCH 013/219] Interpolate `WorldQuery` path in docs of generated types (#14985) # Objective Fixes #14972 ## Solution Uses the `concat!` macro to interpolate the `path` variable. ## Testing * Run `cargo doc --workspace --open` * Check functionality of `WorldQuery` links within `NodeQueryItem`, `NodeQueryReadOnly`, `NodeQueryReadOnlyItem` docs --- crates/bevy_ecs/macros/src/query_data.rs | 20 ++++++++++++----- crates/bevy_ecs/macros/src/query_filter.rs | 10 ++++++--- crates/bevy_ecs/macros/src/world_query.rs | 26 ++++++++++++++-------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index acec7548e9845..9480bfbadf42a 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -250,9 +250,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { user_where_clauses_with_world, ); let read_only_structs = quote! { - #[doc = "Automatically generated [`WorldQuery`] type for a read-only variant of [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`]."] + #[doc = concat!( + "Automatically generated [`WorldQuery`](", + stringify!(#path), + "::query::WorldQuery) type for a read-only variant of [`", + stringify!(#struct_name), + "`]." + )] #[automatically_derived] #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { #( @@ -331,9 +335,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { const _: () = { #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for caching."] + #[doc = concat!( + "Automatically generated internal [`WorldQuery`](", + stringify!(#path), + "::query::WorldQuery) state type for [`", + stringify!(#struct_name), + "`], used for caching." + )] #[automatically_derived] #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index ff056857df41f..2a2916905ae20 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -145,9 +145,13 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { const _: () = { #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for caching."] + #[doc = concat!( + "Automatically generated internal [`WorldQuery`](", + stringify!(#path), + "::query::WorldQuery) state type for [`", + stringify!(#struct_name), + "`], used for caching." + )] #[automatically_derived] #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { #(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)* diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 26cb8edadd8f1..9af4e2b3a148a 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -19,12 +19,16 @@ pub(crate) fn item_struct( user_ty_generics_with_world: &TypeGenerics, user_where_clauses_with_world: Option<&WhereClause>, ) -> proc_macro2::TokenStream { - let item_attrs = quote!( - #[doc = "Automatically generated [`WorldQuery`](#path::query::WorldQuery) item type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], returned when iterating over query results."] - #[automatically_derived] - ); + let item_attrs = quote! { + #[doc = concat!( + "Automatically generated [`WorldQuery`](", + stringify!(#path), + "::query::WorldQuery) item type for [`", + stringify!(#struct_name), + "`], returned when iterating over query results." + )] + #[automatically_derived] + }; match fields { Fields::Named(_) => quote! { @@ -69,9 +73,13 @@ pub(crate) fn world_query_impl( ) -> proc_macro2::TokenStream { quote! { #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used to define the world data accessed by this query."] + #[doc = concat!( + "Automatically generated internal [`WorldQuery`](", + stringify!(#path), + "::query::WorldQuery) fetch type for [`", + stringify!(#struct_name), + "`], used to define the world data accessed by this query." + )] #[automatically_derived] #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { #(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* From a4640046fcf1ce8e3e2bb94838aa520f0f99cfef Mon Sep 17 00:00:00 2001 From: charlotte Date: Mon, 2 Sep 2024 09:46:34 -0700 Subject: [PATCH 014/219] Adds `ShaderStorageBuffer` asset (#14663) Adds a new `Handle` asset type that can be used as a render asset, particularly for use with `AsBindGroup`. Closes: #13658 # Objective Allow users to create storage buffers in the main world without having to access the `RenderDevice`. While this resource is technically available, it's bad form to use in the main world and requires mixing rendering details with main world code. Additionally, this makes storage buffers easier to use with `AsBindGroup`, particularly in the following scenarios: - Sharing the same buffers between a compute stage and material shader. We already have examples of this for storage textures (see game of life example) and these changes allow a similar pattern to be used with storage buffers. - Preventing repeated gpu upload (see the previous easier to use `Vec` `AsBindGroup` option). - Allow initializing custom materials using `Default`. Previously, the lack of a `Default` implement for the raw `wgpu::Buffer` type made implementing a `AsBindGroup + Default` bound difficult in the presence of buffers. ## Solution Adds a new `Handle` asset type that is prepared into a `GpuStorageBuffer` render asset. This asset can either be initialized with a `Vec` of properly aligned data or with a size hint. Users can modify the underlying `wgpu::BufferDescriptor` to provide additional usage flags. ## Migration Guide The `AsBindGroup` `storage` attribute has been modified to reference the new `Handle` asset instead. Usages of Vec` should be converted into assets instead. --------- Co-authored-by: IceSentry --- Cargo.toml | 11 ++ assets/shaders/storage_buffer.wgsl | 38 ++++++ .../bevy_render/macros/src/as_bind_group.rs | 36 ++---- crates/bevy_render/src/lib.rs | 3 + .../src/render_resource/bind_group.rs | 15 ++- crates/bevy_render/src/storage.rs | 109 +++++++++++++++++ examples/README.md | 1 + examples/shader/storage_buffer.rs | 113 ++++++++++++++++++ 8 files changed, 297 insertions(+), 29 deletions(-) create mode 100644 assets/shaders/storage_buffer.wgsl create mode 100644 crates/bevy_render/src/storage.rs create mode 100644 examples/shader/storage_buffer.rs diff --git a/Cargo.toml b/Cargo.toml index 6e9daa74a4fba..e8101a191cd44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2417,6 +2417,17 @@ description = "A shader that shows how to bind and sample multiple textures as a category = "Shaders" wasm = false +[[example]] +name = "storage_buffer" +path = "examples/shader/storage_buffer.rs" +doc-scrape-examples = true + +[package.metadata.example.storage_buffer] +name = "Storage Buffer" +description = "A shader that shows how to bind a storage buffer using a custom material." +category = "Shaders" +wasm = true + [[example]] name = "specialized_mesh_pipeline" path = "examples/shader/specialized_mesh_pipeline.rs" diff --git a/assets/shaders/storage_buffer.wgsl b/assets/shaders/storage_buffer.wgsl new file mode 100644 index 0000000000000..c052411e3f198 --- /dev/null +++ b/assets/shaders/storage_buffer.wgsl @@ -0,0 +1,38 @@ +#import bevy_pbr::{ + mesh_functions, + view_transformations::position_world_to_clip +} + +@group(2) @binding(0) var colors: array, 5>; + +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, + @location(1) color: vec4, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); + out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); + out.clip_position = position_world_to_clip(out.world_position.xyz); + + // We have 5 colors in the storage buffer, but potentially many instances of the mesh, so + // we use the instance index to select a color from the storage buffer. + out.color = colors[vertex.instance_index % 5]; + + return out; +} + +@fragment +fn fragment( + mesh: VertexOutput, +) -> @location(0) vec4 { + return mesh.color; +} \ No newline at end of file diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 81fb920f3c444..9e4a4ab9fdc30 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -212,13 +212,6 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { visibility.hygienic_quote("e! { #render_path::render_resource }); let field_name = field.ident.as_ref().unwrap(); - let field_ty = &field.ty; - - let min_binding_size = if buffer { - quote! {None} - } else { - quote! {Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size())} - }; if buffer { binding_impls.push(quote! { @@ -230,21 +223,15 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }); } else { - binding_impls.push(quote! {{ - use #render_path::render_resource::AsBindGroupShaderType; - let mut buffer = #render_path::render_resource::encase::StorageBuffer::new(Vec::new()); - buffer.write(&self.#field_name).unwrap(); - ( - #binding_index, - #render_path::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data( - &#render_path::render_resource::BufferInitDescriptor { - label: None, - usage: #render_path::render_resource::BufferUsages::COPY_DST | #render_path::render_resource::BufferUsages::STORAGE, - contents: buffer.as_ref(), - }, - )) - ) - }}); + binding_impls.push(quote! { + ( + #binding_index, + #render_path::render_resource::OwnedBindingResource::Buffer({ + let handle: &#asset_path::Handle<#render_path::storage::ShaderStorageBuffer> = (&self.#field_name); + storage_buffers.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.buffer.clone() + }) + ) + }); } binding_layouts.push(quote! { @@ -254,7 +241,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ty: #render_path::render_resource::BindingType::Buffer { ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only }, has_dynamic_offset: false, - min_binding_size: #min_binding_size, + min_binding_size: None, }, count: None, } @@ -527,6 +514,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { type Param = ( #ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>>, #ecs_path::system::lifetimeless::SRes<#render_path::texture::FallbackImage>, + #ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::storage::GpuShaderStorageBuffer>>, ); fn label() -> Option<&'static str> { @@ -537,7 +525,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { &self, layout: &#render_path::render_resource::BindGroupLayout, render_device: &#render_path::renderer::RenderDevice, - (images, fallback_image): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>, + (images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>, ) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> { let bindings = vec![#(#binding_impls,)*]; diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 32dff45d1df7c..fc26a0c895431 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -35,6 +35,7 @@ pub mod render_resource; pub mod renderer; pub mod settings; mod spatial_bundle; +pub mod storage; pub mod texture; pub mod view; pub mod prelude { @@ -75,6 +76,7 @@ use crate::{ render_resource::{PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance}, settings::RenderCreation, + storage::StoragePlugin, view::{ViewPlugin, WindowRenderPlugin}, }; use bevy_app::{App, AppLabel, Plugin, SubApp}; @@ -356,6 +358,7 @@ impl Plugin for RenderPlugin { GlobalsPlugin, MorphPlugin, BatchingPlugin, + StoragePlugin, )); app.init_resource::() diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 4380890e17c27..cb3a6fb347631 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -77,6 +77,8 @@ impl Deref for BindGroup { /// # use bevy_render::{render_resource::*, texture::Image}; /// # use bevy_color::LinearRgba; /// # use bevy_asset::Handle; +/// # use bevy_render::storage::ShaderStorageBuffer; +/// /// #[derive(AsBindGroup)] /// struct CoolMaterial { /// #[uniform(0)] @@ -85,9 +87,9 @@ impl Deref for BindGroup { /// #[sampler(2)] /// color_texture: Handle, /// #[storage(3, read_only)] -/// values: Vec, +/// storage_buffer: Handle, /// #[storage(4, read_only, buffer)] -/// buffer: Buffer, +/// raw_buffer: Buffer, /// #[storage_texture(5)] /// storage_texture: Handle, /// } @@ -99,7 +101,8 @@ impl Deref for BindGroup { /// @group(2) @binding(0) var color: vec4; /// @group(2) @binding(1) var color_texture: texture_2d; /// @group(2) @binding(2) var color_sampler: sampler; -/// @group(2) @binding(3) var values: array; +/// @group(2) @binding(3) var storage_buffer: array; +/// @group(2) @binding(4) var raw_buffer: array; /// @group(2) @binding(5) var storage_texture: texture_storage_2d; /// ``` /// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups @@ -151,15 +154,17 @@ impl Deref for BindGroup { /// |------------------------|-------------------------------------------------------------------------|------------------------| /// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`. | `"filtering"` | /// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | -/// /// * `storage(BINDING_INDEX, arguments)` -/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a storage buffer. +/// * The field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Buffer`] GPU resource, which +/// will be bound as a storage buffer in shaders. If the `storage` attribute is used, the field is expected a raw +/// buffer, and the buffer will be bound as a storage buffer in shaders. /// * It supports and optional `read_only` parameter. Defaults to false if not present. /// /// | Arguments | Values | Default | /// |------------------------|-------------------------------------------------------------------------|----------------------| /// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | /// | `read_only` | if present then value is true, otherwise false | `false` | +/// | `buffer` | if present then the field will be assumed to be a raw wgpu buffer | | /// /// Note that fields without field-level binding attributes will be ignored. /// ``` diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs new file mode 100644 index 0000000000000..4225ee7e28834 --- /dev/null +++ b/crates/bevy_render/src/storage.rs @@ -0,0 +1,109 @@ +use crate::render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages}; +use crate::render_resource::{Buffer, BufferUsages}; +use crate::renderer::RenderDevice; +use bevy_app::{App, Plugin}; +use bevy_asset::{Asset, AssetApp}; +use bevy_ecs::system::lifetimeless::SRes; +use bevy_ecs::system::SystemParamItem; +use bevy_reflect::prelude::ReflectDefault; +use bevy_reflect::Reflect; +use bevy_utils::default; +use wgpu::util::BufferInitDescriptor; + +/// Adds [`ShaderStorageBuffer`] as an asset that is extracted and uploaded to the GPU. +#[derive(Default)] +pub struct StoragePlugin; + +impl Plugin for StoragePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(RenderAssetPlugin::::default()) + .register_type::() + .init_asset::() + .register_asset_reflect::(); + } +} + +/// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. +#[derive(Asset, Reflect, Debug, Clone)] +#[reflect_value(Default)] +pub struct ShaderStorageBuffer { + /// Optional data used to initialize the buffer. + pub data: Option>, + /// The buffer description used to create the buffer. + pub buffer_description: wgpu::BufferDescriptor<'static>, + /// The asset usage of the storage buffer. + pub asset_usage: RenderAssetUsages, +} + +impl Default for ShaderStorageBuffer { + fn default() -> Self { + Self { + data: None, + buffer_description: wgpu::BufferDescriptor { + label: None, + size: 0, + usage: BufferUsages::STORAGE, + mapped_at_creation: false, + }, + asset_usage: RenderAssetUsages::default(), + } + } +} + +impl ShaderStorageBuffer { + /// Creates a new storage buffer with the given data and asset usage. + pub fn new(data: &[u8], asset_usage: RenderAssetUsages) -> Self { + let mut storage = ShaderStorageBuffer { + data: Some(data.to_vec()), + ..default() + }; + storage.asset_usage = asset_usage; + storage + } + + /// Creates a new storage buffer with the given size and asset usage. + pub fn with_size(size: usize, asset_usage: RenderAssetUsages) -> Self { + let mut storage = ShaderStorageBuffer { + data: None, + ..default() + }; + storage.buffer_description.size = size as u64; + storage.buffer_description.mapped_at_creation = false; + storage.asset_usage = asset_usage; + storage + } +} + +/// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. +pub struct GpuShaderStorageBuffer { + pub buffer: Buffer, +} + +impl RenderAsset for GpuShaderStorageBuffer { + type SourceAsset = ShaderStorageBuffer; + type Param = SRes; + + fn asset_usage(source_asset: &Self::SourceAsset) -> RenderAssetUsages { + source_asset.asset_usage + } + + fn prepare_asset( + source_asset: Self::SourceAsset, + render_device: &mut SystemParamItem, + ) -> Result> { + match source_asset.data { + Some(data) => { + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + label: source_asset.buffer_description.label, + contents: &data, + usage: source_asset.buffer_description.usage, + }); + Ok(GpuShaderStorageBuffer { buffer }) + } + None => { + let buffer = render_device.create_buffer(&source_asset.buffer_description); + Ok(GpuShaderStorageBuffer { buffer }) + } + } + } +} diff --git a/examples/README.md b/examples/README.md index e592f427dcdc5..f2ee223b37403 100644 --- a/examples/README.md +++ b/examples/README.md @@ -404,6 +404,7 @@ Example | Description [Post Processing - Custom Render Pass](../examples/shader/custom_post_processing.rs) | A custom post processing effect, using a custom render pass that runs after the main pass [Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader) [Specialized Mesh Pipeline](../examples/shader/specialized_mesh_pipeline.rs) | Demonstrates how to write a specialized mesh pipeline +[Storage Buffer](../examples/shader/storage_buffer.rs) | A shader that shows how to bind a storage buffer using a custom material. [Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures). ## State diff --git a/examples/shader/storage_buffer.rs b/examples/shader/storage_buffer.rs new file mode 100644 index 0000000000000..7661a103a7533 --- /dev/null +++ b/examples/shader/storage_buffer.rs @@ -0,0 +1,113 @@ +//! This example demonstrates how to use a storage buffer with `AsBindGroup` in a custom material. +use bevy::{ + prelude::*, + reflect::TypePath, + render::render_resource::{AsBindGroup, ShaderRef}, +}; +use bevy_render::render_asset::RenderAssetUsages; +use bevy_render::storage::ShaderStorageBuffer; + +const SHADER_ASSET_PATH: &str = "shaders/storage_buffer.wgsl"; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, MaterialPlugin::::default())) + .add_systems(Startup, setup) + .add_systems(Update, update) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut buffers: ResMut>, + mut materials: ResMut>, +) { + // Example data for the storage buffer + let color_data: Vec<[f32; 4]> = vec![ + [1.0, 0.0, 0.0, 1.0], + [0.0, 1.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 1.0], + [1.0, 1.0, 0.0, 1.0], + [0.0, 1.0, 1.0, 1.0], + ]; + + let colors = buffers.add(ShaderStorageBuffer::new( + bytemuck::cast_slice(color_data.as_slice()), + RenderAssetUsages::default(), + )); + + // Create the custom material with the storage buffer + let custom_material = CustomMaterial { colors }; + + let material_handle = materials.add(custom_material); + commands.insert_resource(CustomMaterialHandle(material_handle.clone())); + + // Spawn cubes with the custom material + for i in -6..=6 { + for j in -3..=3 { + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.3))), + material: material_handle.clone(), + transform: Transform::from_xyz(i as f32, j as f32, 0.0), + ..default() + }); + } + } + + // Camera + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +// Update the material color by time +fn update( + time: Res