From 9790c02e18aa31114e202c62c121e1b8905aff2c Mon Sep 17 00:00:00 2001 From: Brezak Date: Mon, 1 Apr 2024 21:13:46 +0200 Subject: [PATCH 01/12] Fix wrong dependency in weekly ci (#12832) # Objective Weekly Ci fails with a `Invalid workflow error` ## Solution Make `check-compiles` depend on a job that actually exists. --- .github/workflows/weekly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index 7bf55f041fc99..9de4fac5ac1ed 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -48,7 +48,7 @@ jobs: check-compiles: runs-on: ubuntu-latest timeout-minutes: 30 - needs: ci + needs: test steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@beta From abd94480ab79000a07291403c70b288b1de91e9f Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 1 Apr 2024 21:56:56 +0200 Subject: [PATCH 02/12] Normalize warning messages with Nvidia drivers (#12833) # Objective There are currently 2 different warning messages that are logged when resizing on Linux with Nvidia drivers (introduced in https://github.com/bevyengine/bevy/commit/70c69cdd51311e4f2be6f4ee245ff5ab89c1fc24). Fixes #12830 ## Solution Generalize both to say: ```Couldn't get swap chain texture. This often happens with the NVIDIA drivers on Linux. It can be safely ignored.``` --- crates/bevy_render/src/view/window/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index ef940901e06ae..72a23baea4f8f 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -341,7 +341,7 @@ pub fn prepare_windows( Err(wgpu::SurfaceError::Outdated) if is_nvidia() => { warn_once!( "Couldn't get swap chain texture. This often happens with \ - the Nvidia 550 driver. It can be safely ignored." + the NVIDIA drivers on Linux. It can be safely ignored." ); } Err(wgpu::SurfaceError::Outdated) => { From 84363f2fab7a1797bf799f44e57da95e5cc86729 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:59:08 -0400 Subject: [PATCH 03/12] Remove redundant imports (#12817) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - There are several redundant imports in the tests and examples that are not caught by CI because additional flags need to be passed. ## Solution - Run `cargo check --workspace --tests` and `cargo check --workspace --examples`, then fix all warnings. - Add `test-check` to CI, which will be run in the check-compiles job. This should catch future warnings for tests. Examples are already checked, but I'm not yet sure why they weren't caught. ## Discussion - Should the `--tests` and `--examples` flags be added to CI, so this is caught in the future? - If so, #12818 will need to be merged first. It was also a warning raised by checking the examples, but I chose to split off into a separate PR. --------- Co-authored-by: François Mockers --- crates/bevy_color/src/hsla.rs | 1 - crates/bevy_color/src/hsva.rs | 1 - crates/bevy_color/src/hwba.rs | 1 - crates/bevy_color/src/laba.rs | 1 - crates/bevy_color/src/lcha.rs | 1 - crates/bevy_color/src/oklaba.rs | 2 +- crates/bevy_color/src/oklcha.rs | 2 +- crates/bevy_ecs/src/entity/hash.rs | 2 -- crates/bevy_ecs/src/query/builder.rs | 2 -- crates/bevy_ecs/src/schedule/condition.rs | 2 +- crates/bevy_ecs/src/schedule/mod.rs | 3 +-- crates/bevy_ecs/src/schedule/stepping.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 4 +-- crates/bevy_math/src/direction.rs | 1 - crates/bevy_math/src/primitives/dim3.rs | 1 - crates/bevy_reflect/src/path/mod.rs | 1 - crates/bevy_render/src/texture/image.rs | 1 - .../src/texture/image_texture_conversion.rs | 1 - crates/bevy_time/src/common_conditions.rs | 1 - crates/bevy_transform/src/systems.rs | 3 +-- examples/3d/3d_viewport_to_world.rs | 2 +- examples/3d/shadow_caster_receiver.rs | 2 +- examples/animation/cubic_curve.rs | 2 +- examples/animation/custom_skinned_mesh.rs | 1 - examples/animation/gltf_skinned_mesh.rs | 2 +- examples/app/return_after_run.rs | 2 +- examples/asset/custom_asset.rs | 2 +- examples/ecs/component_change_detection.rs | 2 +- examples/ecs/dynamic.rs | 2 +- examples/gizmos/light_gizmos.rs | 1 - examples/input/char_input_events.rs | 2 +- examples/input/gamepad_input.rs | 2 +- .../shader/compute_shader_game_of_life.rs | 1 - .../stress_tests/many_animated_sprites.rs | 2 -- examples/stress_tests/many_buttons.rs | 2 +- examples/stress_tests/many_glyphs.rs | 2 +- examples/stress_tests/many_lights.rs | 2 +- examples/stress_tests/many_sprites.rs | 2 +- examples/stress_tests/text_pipeline.rs | 2 +- examples/tools/gamepad_viewer.rs | 4 +-- examples/tools/scene_viewer/main.rs | 1 - examples/transforms/align.rs | 7 ++--- examples/ui/text_debug.rs | 4 +-- examples/window/low_power.rs | 2 +- examples/window/transparent_window.rs | 5 +--- tests/how_to_test_systems.rs | 2 +- tools/ci/src/main.rs | 26 ++++++++++++++++++- 47 files changed, 56 insertions(+), 63 deletions(-) diff --git a/crates/bevy_color/src/hsla.rs b/crates/bevy_color/src/hsla.rs index ac7cdf93dabb6..3c225b369ddab 100644 --- a/crates/bevy_color/src/hsla.rs +++ b/crates/bevy_color/src/hsla.rs @@ -304,7 +304,6 @@ mod tests { use super::*; use crate::{ color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, - Srgba, }; #[test] diff --git a/crates/bevy_color/src/hsva.rs b/crates/bevy_color/src/hsva.rs index 014b447cba980..f58cdefac5659 100644 --- a/crates/bevy_color/src/hsva.rs +++ b/crates/bevy_color/src/hsva.rs @@ -227,7 +227,6 @@ mod tests { use super::*; use crate::{ color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, - Srgba, }; #[test] diff --git a/crates/bevy_color/src/hwba.rs b/crates/bevy_color/src/hwba.rs index 41b4bd06be591..f5e7cf3a93c38 100644 --- a/crates/bevy_color/src/hwba.rs +++ b/crates/bevy_color/src/hwba.rs @@ -260,7 +260,6 @@ mod tests { use super::*; use crate::{ color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, - Srgba, }; #[test] diff --git a/crates/bevy_color/src/laba.rs b/crates/bevy_color/src/laba.rs index 5c95472932420..2ce9b55b72393 100644 --- a/crates/bevy_color/src/laba.rs +++ b/crates/bevy_color/src/laba.rs @@ -320,7 +320,6 @@ mod tests { use super::*; use crate::{ color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, - Srgba, }; #[test] diff --git a/crates/bevy_color/src/lcha.rs b/crates/bevy_color/src/lcha.rs index 75051a3ffe59e..b33236d6f10ed 100644 --- a/crates/bevy_color/src/lcha.rs +++ b/crates/bevy_color/src/lcha.rs @@ -289,7 +289,6 @@ mod tests { use super::*; use crate::{ color_difference::EuclideanDistance, test_colors::TEST_COLORS, testing::assert_approx_eq, - Srgba, }; #[test] diff --git a/crates/bevy_color/src/oklaba.rs b/crates/bevy_color/src/oklaba.rs index 65cd88f7758b7..858091a719de6 100644 --- a/crates/bevy_color/src/oklaba.rs +++ b/crates/bevy_color/src/oklaba.rs @@ -305,7 +305,7 @@ impl From for Xyza { #[cfg(test)] mod tests { use super::*; - use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq, Srgba}; + use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq}; #[test] fn test_to_from_srgba() { diff --git a/crates/bevy_color/src/oklcha.rs b/crates/bevy_color/src/oklcha.rs index 949b80eb08450..01b21b6780560 100644 --- a/crates/bevy_color/src/oklcha.rs +++ b/crates/bevy_color/src/oklcha.rs @@ -344,7 +344,7 @@ impl From for Xyza { #[cfg(test)] mod tests { use super::*; - use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq, Srgba}; + use crate::{test_colors::TEST_COLORS, testing::assert_approx_eq}; #[test] fn test_to_from_srgba() { diff --git a/crates/bevy_ecs/src/entity/hash.rs b/crates/bevy_ecs/src/entity/hash.rs index b3de84e52d264..fac4ad12d8055 100644 --- a/crates/bevy_ecs/src/entity/hash.rs +++ b/crates/bevy_ecs/src/entity/hash.rs @@ -87,8 +87,6 @@ pub type EntityHashSet = hashbrown::HashSet; #[cfg(test)] mod tests { use super::*; - #[cfg(feature = "bevy_reflect")] - use bevy_reflect::Reflect; use static_assertions::assert_impl_all; // Check that the HashMaps are Clone if the key/values are Clone diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index b19c5a6b516d8..101371d00400f 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -253,8 +253,6 @@ mod tests { use crate::prelude::*; use crate::world::FilteredEntityRef; - use super::QueryBuilder; - #[derive(Component, PartialEq, Debug)] struct A(usize); diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 1be8e4b7edded..95e2442e271b6 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1032,7 +1032,7 @@ mod tests { use crate as bevy_ecs; use crate::component::Component; use crate::schedule::IntoSystemConfigs; - use crate::schedule::{common_conditions::not, State, States}; + use crate::schedule::{State, States}; use crate::system::Local; use crate::{change_detection::ResMut, schedule::Schedule, world::World}; use bevy_ecs_macros::Event; diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 8ac9a9d47bec5..b38f7adb67923 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -26,7 +26,7 @@ mod tests { use std::sync::atomic::{AtomicU32, Ordering}; pub use crate as bevy_ecs; - pub use crate::schedule::{IntoSystemSetConfigs, Schedule, SystemSet}; + pub use crate::schedule::{Schedule, SystemSet}; pub use crate::system::{Res, ResMut}; pub use crate::{prelude::World, system::Resource}; @@ -724,7 +724,6 @@ mod tests { use super::*; // Required to make the derive macro behave use crate as bevy_ecs; - use crate::event::Events; use crate::prelude::*; #[derive(Resource)] diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index eb8b4699eb2b6..a129993e895d1 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -828,7 +828,7 @@ impl ScheduleState { mod tests { use super::*; use crate::prelude::*; - use crate::{schedule::ScheduleLabel, world::World}; + use crate::schedule::ScheduleLabel; pub use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index eabe8d5d19983..5d63b310a5e0a 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1531,9 +1531,9 @@ mod tests { use super::*; use crate::{ self as bevy_ecs, // Necessary for the `SystemParam` Derive when used inside `bevy_ecs`. - system::{assert_is_system, Query}, + system::assert_is_system, }; - use std::{cell::RefCell, marker::PhantomData}; + use std::cell::RefCell; // Compile test for https://github.com/bevyengine/bevy/pull/2838. #[test] diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 150a9105b86e1..9b2470890b924 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -564,7 +564,6 @@ impl approx::UlpsEq for Dir3A { #[cfg(test)] mod tests { use super::*; - use crate::InvalidDirectionError; #[test] fn dir2_creation() { diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index a469fa7efb91c..47d83b8b5e66f 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -828,7 +828,6 @@ mod tests { // Reference values were computed by hand and/or with external tools use super::*; - use crate::{InvalidDirectionError, Quat}; use approx::assert_relative_eq; #[test] diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index a1d92732aec4c..fdf1c5d9d5e07 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -491,7 +491,6 @@ mod tests { use super::*; use crate as bevy_reflect; use crate::*; - use error::AccessErrorKind; #[derive(Reflect)] struct A { diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index d163841837c03..4b8a5d71ceb25 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -937,7 +937,6 @@ impl CompressedImageFormats { #[cfg(test)] mod test { use super::*; - use crate::render_asset::RenderAssetUsages; #[test] fn image_size() { diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index d5bbed5ee6c87..5284c0adcca10 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -222,7 +222,6 @@ mod test { use image::{GenericImage, Rgba}; use super::*; - use crate::render_asset::RenderAssetUsages; #[test] fn two_way_conversion() { diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index a175e4632acb4..760ddf84ffa34 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -239,7 +239,6 @@ pub fn paused(time: Res>) -> bool { mod tests { use super::*; use bevy_ecs::schedule::{IntoSystemConfigs, Schedule}; - use std::time::Duration; fn test_system() {} diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 12e44b193b837..401a32cb22cc4 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -188,10 +188,9 @@ mod test { use bevy_math::{vec3, Vec3}; use bevy_tasks::{ComputeTaskPool, TaskPool}; - use crate::components::{GlobalTransform, Transform}; use crate::systems::*; use crate::TransformBundle; - use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent}; + use bevy_hierarchy::{BuildChildren, BuildWorldChildren}; #[test] fn correct_parent_removed() { diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs index dafcd9765d479..8a3aed6e5b9b6 100644 --- a/examples/3d/3d_viewport_to_world.rs +++ b/examples/3d/3d_viewport_to_world.rs @@ -1,6 +1,6 @@ //! This example demonstrates how to use the `Camera::viewport_to_world` method. -use bevy::{math::Dir3, prelude::*}; +use bevy::prelude::*; fn main() { App::new() diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index 97426737d925f..f19adf15e001e 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -4,7 +4,7 @@ use std::f32::consts::PI; use bevy::{ color::palettes::basic::{BLUE, LIME, RED}, - pbr::{light_consts, CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver}, + pbr::{CascadeShadowConfigBuilder, NotShadowCaster, NotShadowReceiver}, prelude::*, }; diff --git a/examples/animation/cubic_curve.rs b/examples/animation/cubic_curve.rs index 80035d574e370..ce7a6da198009 100644 --- a/examples/animation/cubic_curve.rs +++ b/examples/animation/cubic_curve.rs @@ -2,7 +2,7 @@ use bevy::{ color::palettes::css::{ORANGE, SILVER, WHITE}, - math::{cubic_splines::CubicCurve, vec3}, + math::vec3, prelude::*, }; diff --git a/examples/animation/custom_skinned_mesh.rs b/examples/animation/custom_skinned_mesh.rs index cc41ef6a63e71..8c37e3b9568e6 100644 --- a/examples/animation/custom_skinned_mesh.rs +++ b/examples/animation/custom_skinned_mesh.rs @@ -4,7 +4,6 @@ use std::f32::consts::*; use bevy::{ - pbr::AmbientLight, prelude::*, render::{ mesh::{ diff --git a/examples/animation/gltf_skinned_mesh.rs b/examples/animation/gltf_skinned_mesh.rs index ae245ffdadffd..b7ae0278219ed 100644 --- a/examples/animation/gltf_skinned_mesh.rs +++ b/examples/animation/gltf_skinned_mesh.rs @@ -3,7 +3,7 @@ use std::f32::consts::*; -use bevy::{pbr::AmbientLight, prelude::*, render::mesh::skinning::SkinnedMesh}; +use bevy::{prelude::*, render::mesh::skinning::SkinnedMesh}; fn main() { App::new() diff --git a/examples/app/return_after_run.rs b/examples/app/return_after_run.rs index 0c3493f6b1676..52d162af16fad 100644 --- a/examples/app/return_after_run.rs +++ b/examples/app/return_after_run.rs @@ -5,7 +5,7 @@ //! - `App::run()` will never return on iOS and Web. //! - It is not possible to recreate a window after the event loop has been terminated. -use bevy::{prelude::*, window::WindowPlugin}; +use bevy::prelude::*; fn main() { println!("Running Bevy App"); diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index b3167bbd1d8bb..e414f0ff630b7 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -1,7 +1,7 @@ //! Implements loader for a custom asset type. use bevy::{ - asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext}, + asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}, prelude::*, reflect::TypePath, }; diff --git a/examples/ecs/component_change_detection.rs b/examples/ecs/component_change_detection.rs index 234a7021e9cea..096fb4a9fe60a 100644 --- a/examples/ecs/component_change_detection.rs +++ b/examples/ecs/component_change_detection.rs @@ -1,6 +1,6 @@ //! This example illustrates how to react to component change. -use bevy::{ecs::world::Ref, prelude::*}; +use bevy::prelude::*; use rand::Rng; fn main() { diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index d5f18e059c313..c804062aefe91 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -9,7 +9,7 @@ use bevy::prelude::*; use bevy::{ ecs::{ component::{ComponentDescriptor, ComponentId, ComponentInfo, StorageType}, - query::{QueryBuilder, QueryData}, + query::QueryData, world::FilteredEntityMut, }, ptr::{Aligned, OwningPtr}, diff --git a/examples/gizmos/light_gizmos.rs b/examples/gizmos/light_gizmos.rs index e06b6c0e8dedd..e8f2bf4ec6edc 100644 --- a/examples/gizmos/light_gizmos.rs +++ b/examples/gizmos/light_gizmos.rs @@ -4,7 +4,6 @@ use std::f32::consts::{FRAC_PI_2, PI}; use bevy::{ color::palettes::css::{DARK_CYAN, GOLD, GRAY, PURPLE}, - gizmos::light::{LightGizmoColor, LightGizmoConfigGroup}, prelude::*, }; diff --git a/examples/input/char_input_events.rs b/examples/input/char_input_events.rs index bdaefb7233688..f2e52dd7336c3 100644 --- a/examples/input/char_input_events.rs +++ b/examples/input/char_input_events.rs @@ -1,6 +1,6 @@ //! Prints out all chars as they are inputted. -use bevy::{prelude::*, window::ReceivedCharacter}; +use bevy::prelude::*; fn main() { App::new() diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs index 13796c59facf3..e07b43a5c2e27 100644 --- a/examples/input/gamepad_input.rs +++ b/examples/input/gamepad_input.rs @@ -1,6 +1,6 @@ //! Shows handling of gamepad input, connections, and disconnections. -use bevy::{input::gamepad::GamepadButton, prelude::*}; +use bevy::prelude::*; fn main() { App::new() diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 21b5cc92c4479..43f8a0b448a5d 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -14,7 +14,6 @@ use bevy::{ renderer::{RenderContext, RenderDevice}, Render, RenderApp, RenderSet, }, - window::WindowPlugin, }; use std::borrow::Cow; diff --git a/examples/stress_tests/many_animated_sprites.rs b/examples/stress_tests/many_animated_sprites.rs index 91f2e122d51a7..17be46f0ac572 100644 --- a/examples/stress_tests/many_animated_sprites.rs +++ b/examples/stress_tests/many_animated_sprites.rs @@ -7,9 +7,7 @@ use std::time::Duration; use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, - math::Quat, prelude::*, - render::camera::Camera, window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; diff --git a/examples/stress_tests/many_buttons.rs b/examples/stress_tests/many_buttons.rs index ae4d8c47e001a..03fad2bc865ba 100644 --- a/examples/stress_tests/many_buttons.rs +++ b/examples/stress_tests/many_buttons.rs @@ -5,7 +5,7 @@ use bevy::{ color::palettes::css::ORANGE_RED, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - window::{PresentMode, WindowPlugin, WindowResolution}, + window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index ff7b68f6b92fe..ca4bc6db28c53 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -10,7 +10,7 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, text::{BreakLineOn, Text2dBounds}, - window::{PresentMode, WindowPlugin, WindowResolution}, + window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index b9134c42c4f5e..e37a096562d62 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -10,7 +10,7 @@ use bevy::{ pbr::{ExtractedPointLight, GlobalLightMeta}, prelude::*, render::{camera::ScalingMode, Render, RenderApp, RenderSet}, - window::{PresentMode, WindowPlugin, WindowResolution}, + window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; use rand::{thread_rng, Rng}; diff --git a/examples/stress_tests/many_sprites.rs b/examples/stress_tests/many_sprites.rs index 1562b44e63d94..fb08d2942f4b1 100644 --- a/examples/stress_tests/many_sprites.rs +++ b/examples/stress_tests/many_sprites.rs @@ -11,7 +11,7 @@ use bevy::{ color::palettes::css::*, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, - window::{PresentMode, WindowPlugin, WindowResolution}, + window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; diff --git a/examples/stress_tests/text_pipeline.rs b/examples/stress_tests/text_pipeline.rs index 7ce555a9fb84b..716d867d0d4e5 100644 --- a/examples/stress_tests/text_pipeline.rs +++ b/examples/stress_tests/text_pipeline.rs @@ -7,7 +7,7 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, text::{BreakLineOn, Text2dBounds}, - window::{PresentMode, WindowPlugin, WindowResolution}, + window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index 58b4f503cf605..c99f3f01beccd 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -3,9 +3,7 @@ use std::f32::consts::PI; use bevy::{ - input::gamepad::{ - GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadSettings, - }, + input::gamepad::{GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadSettings}, prelude::*, sprite::{Anchor, MaterialMesh2dBundle, Mesh2dHandle}, }; diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index b22abb000bc44..aebd3064fff04 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -11,7 +11,6 @@ use bevy::{ math::Vec3A, prelude::*, render::primitives::{Aabb, Sphere}, - window::WindowPlugin, }; #[path = "../../helpers/camera_controller.rs"] diff --git a/examples/transforms/align.rs b/examples/transforms/align.rs index 37dd13a079b2b..f5b152775f050 100644 --- a/examples/transforms/align.rs +++ b/examples/transforms/align.rs @@ -1,10 +1,7 @@ //! This example shows how to align the orientations of objects in 3D space along two axes using the `Transform::align` API. -use bevy::color::{ - palettes::basic::{GRAY, RED, WHITE}, - Color, -}; -use bevy::input::mouse::{MouseButton, MouseButtonInput, MouseMotion}; +use bevy::color::palettes::basic::{GRAY, RED, WHITE}; +use bevy::input::mouse::{MouseButtonInput, MouseMotion}; use bevy::prelude::*; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; diff --git a/examples/ui/text_debug.rs b/examples/ui/text_debug.rs index c8f06d91c188f..5f0cc6b8eba0d 100644 --- a/examples/ui/text_debug.rs +++ b/examples/ui/text_debug.rs @@ -1,10 +1,10 @@ //! Shows various text layout options. use bevy::{ - color::palettes::{basic::RED, css::*}, + color::palettes::css::*, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, prelude::*, - window::{PresentMode, WindowPlugin}, + window::PresentMode, }; fn main() { diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 3782d50171c0d..8d6593fc66e45 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -6,7 +6,7 @@ use bevy::{ prelude::*, utils::Duration, - window::{PresentMode, RequestRedraw, WindowPlugin}, + window::{PresentMode, RequestRedraw}, winit::WinitSettings, }; diff --git a/examples/window/transparent_window.rs b/examples/window/transparent_window.rs index ffb8090208703..f95408ff52fe0 100644 --- a/examples/window/transparent_window.rs +++ b/examples/window/transparent_window.rs @@ -4,12 +4,9 @@ //! [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html#structfield.transparent) //! for more details. +use bevy::prelude::*; #[cfg(target_os = "macos")] use bevy::window::CompositeAlphaMode; -use bevy::{ - prelude::*, - window::{Window, WindowPlugin}, -}; fn main() { App::new() diff --git a/tests/how_to_test_systems.rs b/tests/how_to_test_systems.rs index f14a14a60eba9..53ac65310f692 100644 --- a/tests/how_to_test_systems.rs +++ b/tests/how_to_test_systems.rs @@ -1,4 +1,4 @@ -use bevy::{ecs::event::Events, prelude::*}; +use bevy::prelude::*; #[derive(Component, Default)] struct Enemy { diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 617f96f7f7da8..d5e4f62487308 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -18,6 +18,7 @@ bitflags! { const EXAMPLE_CHECK = 0b10000000; const COMPILE_CHECK = 0b100000000; const CFG_CHECK = 0b1000000000; + const TEST_CHECK = 0b10000000000; } } @@ -56,13 +57,18 @@ fn main() { ("doc", Check::DOC_TEST | Check::DOC_CHECK), ( "compile", - Check::COMPILE_FAIL | Check::BENCH_CHECK | Check::EXAMPLE_CHECK | Check::COMPILE_CHECK, + Check::COMPILE_FAIL + | Check::BENCH_CHECK + | Check::EXAMPLE_CHECK + | Check::COMPILE_CHECK + | Check::TEST_CHECK, ), ("format", Check::FORMAT), ("clippy", Check::CLIPPY), ("compile-fail", Check::COMPILE_FAIL), ("bench-check", Check::BENCH_CHECK), ("example-check", Check::EXAMPLE_CHECK), + ("test-check", Check::TEST_CHECK), ("cfg-check", Check::CFG_CHECK), ("doc-check", Check::DOC_CHECK), ("doc-test", Check::DOC_TEST), @@ -314,6 +320,24 @@ fn main() { ); } + if checks.contains(Check::TEST_CHECK) { + let mut args = vec!["--workspace", "--tests"]; + + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::TEST_CHECK, + vec![CITest { + command: cmd!(sh, "cargo check {args...}"), + failure_message: "Please fix compiler examples for tests in output above.", + subdir: None, + env_vars: Vec::new(), + }], + ); + } + // Actually run the tests: let mut failed_checks: Check = Check::empty(); From 7618884b2f39c85a798815d5f86e54b47d8e16d6 Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 1 Apr 2024 22:00:45 +0200 Subject: [PATCH 04/12] Fix UV coords in `generate_custom_mesh` example (#12826) # Objective Fix the last coordinate of the top side in the `generate_custom_mesh` example. Fixes #12822 ## Images Added a yellow square to the texture to demonstrate as suggested in [the issue](https://github.com/bevyengine/bevy/issues/12822).
Texture:
Before:
After:
## Solution Change the coordinate from 0.25 to 0.2. --- examples/3d/generate_custom_mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3d/generate_custom_mesh.rs b/examples/3d/generate_custom_mesh.rs index 6ddc03300565b..10e760c3441d1 100644 --- a/examples/3d/generate_custom_mesh.rs +++ b/examples/3d/generate_custom_mesh.rs @@ -166,7 +166,7 @@ fn create_cube_mesh() -> Mesh { Mesh::ATTRIBUTE_UV_0, vec![ // Assigning the UV coords for the top side. - [0.0, 0.2], [0.0, 0.0], [1.0, 0.0], [1.0, 0.25], + [0.0, 0.2], [0.0, 0.0], [1.0, 0.0], [1.0, 0.2], // Assigning the UV coords for the bottom side. [0.0, 0.45], [0.0, 0.25], [1.0, 0.25], [1.0, 0.45], // Assigning the UV coords for the right side. From 891c2f120363523f1d108f12d082297160ef8cc9 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Mon, 1 Apr 2024 23:20:30 +0300 Subject: [PATCH 05/12] Add `RemovedComponentEvents::iter` (#12815) # Objective Sometimes it's useful to iterate over removed entities. For example, in my library [bevy_replicon](https://github.com/projectharmonia/bevy_replicon) I need it to iterate over all removals to replicate them over the network. Right now we do lookups, but it would be more convenient and faster to just iterate over all removals. ## Solution Add `RemovedComponentEvents::iter`. --- ## Changelog ### Added - `RemovedComponentEvents::iter` to iterate over all removed components. --------- Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> --- crates/bevy_ecs/src/removal_detection.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index 62a28bdbea975..da780e9e8f199 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -83,6 +83,11 @@ impl RemovedComponentEvents { } } + /// Returns an iterator over components and their entity events. + pub fn iter(&self) -> impl Iterator)> { + self.event_sets.iter() + } + /// Gets the event storage for a given component. pub fn get( &self, From aa477028ef4b234217565f751048f8021225cfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Sagaert?= Date: Mon, 1 Apr 2024 23:45:47 +0200 Subject: [PATCH 06/12] fix previous_position / previous_force being discarded too early (#12556) # Objective Fixes #12442 ## Solution Change `process_touch_event` to not update previous_position / previous_force, and change it once per frame in `touch_screen_input_system`. --- crates/bevy_input/src/touch.rs | 79 ++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/crates/bevy_input/src/touch.rs b/crates/bevy_input/src/touch.rs index d1f357ed572a0..2344a04623886 100644 --- a/crates/bevy_input/src/touch.rs +++ b/crates/bevy_input/src/touch.rs @@ -373,8 +373,9 @@ impl Touches { } TouchPhase::Moved => { if let Some(mut new_touch) = self.pressed.get(&event.id).cloned() { - new_touch.previous_position = new_touch.position; - new_touch.previous_force = new_touch.force; + // NOTE: This does not update the previous_force / previous_position field; + // they should be updated once per frame, not once per event + // See https://github.com/bevyengine/bevy/issues/12442 new_touch.position = event.position; new_touch.force = event.force; self.pressed.insert(event.id, new_touch); @@ -427,8 +428,15 @@ pub fn touch_screen_input_system( touch_state.just_canceled.clear(); } - for event in touch_input_events.read() { - touch_state.process_touch_event(event); + if !touch_input_events.is_empty() { + for touch in touch_state.pressed.values_mut() { + touch.previous_position = touch.position; + touch.previous_force = touch.force; + } + + for event in touch_input_events.read() { + touch_state.process_touch_event(event); + } } } @@ -551,6 +559,69 @@ mod test { assert_ne!(touch.previous_position, touch.position); } + // See https://github.com/bevyengine/bevy/issues/12442 + #[test] + fn touch_process_multi_event() { + use crate::{touch::TouchPhase, TouchInput, Touches}; + use bevy_ecs::entity::Entity; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + let started_touch_event = TouchInput { + phase: TouchPhase::Started, + position: Vec2::splat(4.0), + window: Entity::PLACEHOLDER, + force: None, + id: 4, + }; + + let moved_touch_event1 = TouchInput { + phase: TouchPhase::Moved, + position: Vec2::splat(5.0), + window: Entity::PLACEHOLDER, + force: None, + id: started_touch_event.id, + }; + + let moved_touch_event2 = TouchInput { + phase: TouchPhase::Moved, + position: Vec2::splat(6.0), + window: Entity::PLACEHOLDER, + force: None, + id: started_touch_event.id, + }; + + // tick 1: touch is started during frame + for touch in touches.pressed.values_mut() { + // update ONCE, at start of frame + touch.previous_position = touch.position; + } + touches.process_touch_event(&started_touch_event); + touches.process_touch_event(&moved_touch_event1); + touches.process_touch_event(&moved_touch_event2); + + { + let touch = touches.get_pressed(started_touch_event.id).unwrap(); + assert_eq!(touch.previous_position, started_touch_event.position); + assert_eq!(touch.position, moved_touch_event2.position); + } + + // tick 2: touch was started before frame + for touch in touches.pressed.values_mut() { + touch.previous_position = touch.position; + } + touches.process_touch_event(&moved_touch_event1); + touches.process_touch_event(&moved_touch_event2); + touches.process_touch_event(&moved_touch_event1); + + { + let touch = touches.get_pressed(started_touch_event.id).unwrap(); + assert_eq!(touch.previous_position, moved_touch_event2.position); + assert_eq!(touch.position, moved_touch_event1.position); + } + } + #[test] fn touch_pressed() { use crate::{touch::TouchPhase, TouchInput, Touches}; From 20ee56e719c669a5012a4810e04d1610d4e0149d Mon Sep 17 00:00:00 2001 From: Jakub Marcowski <37378746+Chubercik@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:53:12 +0200 Subject: [PATCH 07/12] Add `Tetrahedron` primitive to `bevy_math::primitives` (#12688) # Objective - #10572 There is no 3D primitive available for the common shape of a tetrahedron (3-simplex). ## Solution This PR introduces a new type to the existing math primitives: - `Tetrahedron`: a polyhedron composed of four triangular faces, six straight edges, and four vertices --- ## Changelog ### Added - `Tetrahedron` primitive to the `bevy_math` crate - `Tetrahedron` tests (`area`, `volume` methods) - `impl_reflect!` declaration for `Tetrahedron` in the `bevy_reflect` crate --- crates/bevy_math/src/primitives/dim3.rs | 118 +++++++++++++++++- .../src/impls/math/primitives3d.rs | 8 ++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 47d83b8b5e66f..887e31c0d792c 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -3,7 +3,7 @@ use std::f32::consts::{FRAC_PI_3, PI}; use super::{Circle, Primitive3d}; use crate::{ bounding::{Aabb3d, Bounded3d, BoundingSphere}, - Dir3, InvalidDirectionError, Quat, Vec3, + Dir3, InvalidDirectionError, Mat3, Quat, Vec3, }; /// A sphere primitive @@ -823,6 +823,86 @@ impl Bounded3d for Triangle3d { } } +/// A tetrahedron primitive. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct Tetrahedron { + /// The vertices of the tetrahedron. + pub vertices: [Vec3; 4], +} +impl Primitive3d for Tetrahedron {} + +impl Default for Tetrahedron { + /// Returns the default [`Tetrahedron`] with the vertices + /// `[0.0, 0.5, 0.0]`, `[-0.5, -0.5, 0.0]`, `[0.5, -0.5, 0.0]` and `[0.0, 0.0, 0.5]`. + fn default() -> Self { + Self { + vertices: [ + Vec3::new(0.0, 0.5, 0.0), + Vec3::new(-0.5, -0.5, 0.0), + Vec3::new(0.5, -0.5, 0.0), + Vec3::new(0.0, 0.0, 0.5), + ], + } + } +} + +impl Tetrahedron { + /// Create a new [`Tetrahedron`] from points `a`, `b`, `c` and `d`. + #[inline(always)] + pub fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self { + Self { + vertices: [a, b, c, d], + } + } + + /// Get the surface area of the tetrahedron. + #[inline(always)] + pub fn area(&self) -> f32 { + let [a, b, c, d] = self.vertices; + let ab = b - a; + let ac = c - a; + let ad = d - a; + let bc = c - b; + let bd = d - b; + (ab.cross(ac).length() + + ab.cross(ad).length() + + ac.cross(ad).length() + + bc.cross(bd).length()) + / 2.0 + } + + /// Get the volume of the tetrahedron. + #[inline(always)] + pub fn volume(&self) -> f32 { + self.signed_volume().abs() + } + + /// Get the signed volume of the tetrahedron. + /// + /// If it's negative, the normal vector of the face defined by + /// the first three points using the right-hand rule points + /// away from the fourth vertex. + #[inline(always)] + pub fn signed_volume(&self) -> f32 { + let [a, b, c, d] = self.vertices; + let ab = b - a; + let ac = c - a; + let ad = d - a; + Mat3::from_cols(ab, ac, ad).determinant() / 6.0 + } + + /// Get the centroid of the tetrahedron. + /// + /// This function finds the geometric center of the tetrahedron + /// by averaging the vertices: `centroid = (a + b + c + d) / 4`. + #[doc(alias("center", "barycenter", "baricenter"))] + #[inline(always)] + pub fn centroid(&self) -> Vec3 { + (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0 + } +} + #[cfg(test)] mod tests { // Reference values were computed by hand and/or with external tools @@ -985,4 +1065,40 @@ mod tests { assert_relative_eq!(torus.area(), 33.16187); assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001); } + + #[test] + fn tetrahedron_math() { + let tetrahedron = Tetrahedron { + vertices: [ + Vec3::new(0.3, 1.0, 1.7), + Vec3::new(-2.0, -1.0, 0.0), + Vec3::new(1.8, 0.5, 1.0), + Vec3::new(-1.0, -2.0, 3.5), + ], + }; + assert_eq!(tetrahedron.area(), 19.251068, "incorrect area"); + assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume"); + assert_eq!( + tetrahedron.signed_volume(), + 3.2058334, + "incorrect signed volume" + ); + assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55)); + + assert_eq!(Tetrahedron::default().area(), 1.4659258, "incorrect area"); + assert_eq!( + Tetrahedron::default().volume(), + 0.083333336, + "incorrect volume" + ); + assert_eq!( + Tetrahedron::default().signed_volume(), + 0.083333336, + "incorrect signed volume" + ); + assert_relative_eq!( + Tetrahedron::default().centroid(), + Vec3::new(0.0, -0.125, 0.125) + ); + } } diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index e9b025529cfd8..e46f1ef78f79d 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -97,3 +97,11 @@ impl_reflect!( major_radius: f32, } ); + +impl_reflect!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Tetrahedron { + vertices: [Vec3; 4], + } +); From c8aa3ac7d1867c554ba77411c29347bc687a2254 Mon Sep 17 00:00:00 2001 From: Matty Date: Mon, 1 Apr 2024 17:55:49 -0400 Subject: [PATCH 08/12] Meshing for `Annulus` primitive (#12734) # Objective Related to #10572 Allow the `Annulus` primitive to be meshed. ## Solution We introduce a `Meshable` structure, `AnnulusMeshBuilder`, which allows the `Annulus` primitive to be meshed, leaving optional configuration of the number of angular sudivisions to the user. Here is a picture of the annulus's UV-mapping: Screenshot 2024-03-26 at 10 39 48 AM Other features are essentially identical to the implementations for `Circle`/`Ellipse`. --- ## Changelog - Introduced `AnnulusMeshBuilder` - Implemented `Meshable` for `Annulus` with `Output = AnnulusMeshBuilder` - Implemented `From` and `From` for `Mesh` - Added `impl_reflect!` declaration for `Annulus` and `Triangle3d` in `bevy_reflect` --- ## Discussion ### Design considerations The only interesting wrinkle here is that the existing UV-mapping of `Ellipse` (and hence of `Circle` and `RegularPolygon`) is non-radial (it's skew-free, created by situating the mesh in a bounding rectangle), so the UV-mapping of `Annulus` doesn't limit to that of `Circle` as its inner radius tends to zero, for instance. I don't see this as a real issue for `Annulus`, which should almost certainly have this kind of UV-mapping, but I think we ought to at least consider allowing mesh configuration for `Circle`/`Ellipse` that performs radial UV-mapping instead. (In these cases in particular, it would be especially easy, since we wouldn't need a different parameter set in the builder.) --------- Co-authored-by: Alice Cecile --- .../src/impls/math/primitives2d.rs | 11 +- .../src/impls/math/primitives3d.rs | 8 ++ .../bevy_render/src/mesh/primitives/dim2.rs | 122 +++++++++++++++++- 3 files changed, 139 insertions(+), 2 deletions(-) diff --git a/crates/bevy_reflect/src/impls/math/primitives2d.rs b/crates/bevy_reflect/src/impls/math/primitives2d.rs index 03364fb5f2fbd..c9d9a5b50dc09 100644 --- a/crates/bevy_reflect/src/impls/math/primitives2d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives2d.rs @@ -15,7 +15,16 @@ impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Ellipse { - pub half_size: Vec2, + half_size: Vec2, + } +); + +impl_reflect!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Annulus { + inner_circle: Circle, + outer_circle: Circle, } ); diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index e46f1ef78f79d..6b6f773a172f5 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -44,6 +44,14 @@ impl_reflect!( } ); +impl_reflect!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Triangle3d { + vertices: [Vec3; 3], + } +); + impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] diff --git a/crates/bevy_render/src/mesh/primitives/dim2.rs b/crates/bevy_render/src/mesh/primitives/dim2.rs index 40e018602670c..e10d7c845ce6a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim2.rs +++ b/crates/bevy_render/src/mesh/primitives/dim2.rs @@ -5,7 +5,9 @@ use crate::{ use super::Meshable; use bevy_math::{ - primitives::{Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder}, + primitives::{ + Annulus, Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder, + }, Vec2, }; use wgpu::PrimitiveTopology; @@ -193,6 +195,124 @@ impl From for Mesh { } } +/// A builder for creating a [`Mesh`] with an [`Annulus`] shape. +pub struct AnnulusMeshBuilder { + /// The [`Annulus`] shape. + pub annulus: Annulus, + + /// The number of vertices used in constructing each concentric circle of the annulus mesh. + /// The default is `32`. + pub resolution: usize, +} + +impl Default for AnnulusMeshBuilder { + fn default() -> Self { + Self { + annulus: Annulus::default(), + resolution: 32, + } + } +} + +impl AnnulusMeshBuilder { + /// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count. + #[inline] + pub fn new(inner_radius: f32, outer_radius: f32, resolution: usize) -> Self { + Self { + annulus: Annulus::new(inner_radius, outer_radius), + resolution, + } + } + + /// Sets the number of vertices used in constructing the concentric circles of the annulus mesh. + #[inline] + pub fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution; + self + } + + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { + let inner_radius = self.annulus.inner_circle.radius; + let outer_radius = self.annulus.outer_circle.radius; + + let num_vertices = (self.resolution + 1) * 2; + let mut indices = Vec::with_capacity(self.resolution * 6); + let mut positions = Vec::with_capacity(num_vertices); + let mut uvs = Vec::with_capacity(num_vertices); + let normals = vec![[0.0, 0.0, 1.0]; num_vertices]; + + // We have one more set of vertices than might be naïvely expected; + // the vertices at `start_angle` are duplicated for the purposes of UV + // mapping. Here, each iteration places a pair of vertices at a fixed + // angle from the center of the annulus. + let start_angle = std::f32::consts::FRAC_PI_2; + let step = std::f32::consts::TAU / self.resolution as f32; + for i in 0..=self.resolution { + let theta = start_angle + i as f32 * step; + let (sin, cos) = theta.sin_cos(); + let inner_pos = [cos * inner_radius, sin * inner_radius, 0.]; + let outer_pos = [cos * outer_radius, sin * outer_radius, 0.]; + positions.push(inner_pos); + positions.push(outer_pos); + + // The first UV direction is radial and the second is angular; + // i.e., a single UV rectangle is stretched around the annulus, with + // its top and bottom meeting as the circle closes. Lines of constant + // U map to circles, and lines of constant V map to radial line segments. + let inner_uv = [0., i as f32 / self.resolution as f32]; + let outer_uv = [1., i as f32 / self.resolution as f32]; + uvs.push(inner_uv); + uvs.push(outer_uv); + } + + // Adjacent pairs of vertices form two triangles with each other; here, + // we are just making sure that they both have the right orientation, + // which is the CCW order of + // `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner` + for i in 0..(self.resolution as u32) { + let inner_vertex = 2 * i; + let outer_vertex = 2 * i + 1; + let next_inner = inner_vertex + 2; + let next_outer = outer_vertex + 2; + indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]); + indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]); + } + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)) + } +} + +impl Meshable for Annulus { + type Output = AnnulusMeshBuilder; + + fn mesh(&self) -> Self::Output { + AnnulusMeshBuilder { + annulus: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(annulus: Annulus) -> Self { + annulus.mesh().build() + } +} + +impl From for Mesh { + fn from(builder: AnnulusMeshBuilder) -> Self { + builder.build() + } +} + impl Meshable for Triangle2d { type Output = Mesh; From 37522fd0ae240c9d01e64f4ad98bd1f20cd90feb Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 1 Apr 2024 16:58:53 -0500 Subject: [PATCH 09/12] Micro-optimize `queue_material_meshes`, primarily to remove bit manipulation. (#12791) This commit makes the following optimizations: ## `MeshPipelineKey`/`BaseMeshPipelineKey` split `MeshPipelineKey` has been split into `BaseMeshPipelineKey`, which lives in `bevy_render` and `MeshPipelineKey`, which lives in `bevy_pbr`. Conceptually, `BaseMeshPipelineKey` is a superclass of `MeshPipelineKey`. For `BaseMeshPipelineKey`, the bits start at the highest (most significant) bit and grow downward toward the lowest bit; for `MeshPipelineKey`, the bits start at the lowest bit and grow upward toward the highest bit. This prevents them from colliding. The goal of this is to avoid having to reassemble bits of the pipeline key for every mesh every frame. Instead, we can just use a bitwise or operation to combine the pieces that make up a `MeshPipelineKey`. ## `specialize_slow` Previously, all of `specialize()` was marked as `#[inline]`. This bloated `queue_material_meshes` unnecessarily, as a large chunk of it ended up being a slow path that was rarely hit. This commit refactors the function to move the slow path to `specialize_slow()`. Together, these two changes shave about 5% off `queue_material_meshes`: ![Screenshot 2024-03-29 130002](https://github.com/bevyengine/bevy/assets/157897/a7e5a994-a807-4328-b314-9003429dcdd2) ## Migration Guide - The `primitive_topology` field on `GpuMesh` is now an accessor method: `GpuMesh::primitive_topology()`. - For performance reasons, `MeshPipelineKey` has been split into `BaseMeshPipelineKey`, which lives in `bevy_render`, and `MeshPipelineKey`, which lives in `bevy_pbr`. These two should be combined with bitwise-or to produce the final `MeshPipelineKey`. --- crates/bevy_pbr/Cargo.toml | 1 + crates/bevy_pbr/src/material.rs | 45 ++++--- crates/bevy_pbr/src/prepass/mod.rs | 7 +- crates/bevy_pbr/src/render/light.rs | 13 +- crates/bevy_pbr/src/render/mesh.rs | 60 +++++---- crates/bevy_render/src/mesh/mesh/mod.rs | 55 ++++++++- .../render_resource/pipeline_specializer.rs | 115 +++++++++++------- crates/bevy_sprite/src/mesh2d/material.rs | 2 +- examples/2d/mesh2d_manual.rs | 2 +- examples/shader/shader_instancing.rs | 3 +- 10 files changed, 195 insertions(+), 108 deletions(-) diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 6944aeb5d7c32..d75be9f3c9d07 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -50,6 +50,7 @@ serde = { version = "1", features = ["derive", "rc"] } bincode = "1" range-alloc = "0.1" nonmax = "0.5" +static_assertions = "1" [lints] workspace = true diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5b758265f688b..4951d06eecf91 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -661,25 +661,9 @@ pub fn queue_material_meshes( continue; }; - let forward = match material.properties.render_method { - OpaqueRendererMethod::Forward => true, - OpaqueRendererMethod::Deferred => false, - OpaqueRendererMethod::Auto => unreachable!(), - }; - - let mut mesh_key = view_key; - - mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - - if material.properties.reads_view_transmission_texture { - mesh_key |= MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE; - } - - mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); + let mut mesh_key = view_key + | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) + | material.properties.mesh_pipeline_key_bits; let lightmap_image = render_lightmaps .render_lightmaps @@ -724,7 +708,7 @@ pub fn queue_material_meshes( batch_range: 0..1, dynamic_offset: None, }); - } else if forward { + } else if material.properties.render_method == OpaqueRendererMethod::Forward { let bin_key = Opaque3dBinKey { draw_function: draw_opaque_pbr, pipeline: pipeline_id, @@ -748,7 +732,7 @@ pub fn queue_material_meshes( batch_range: 0..1, dynamic_offset: None, }); - } else if forward { + } else if material.properties.render_method == OpaqueRendererMethod::Forward { let bin_key = OpaqueNoLightmap3dBinKey { draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, @@ -823,7 +807,7 @@ impl DefaultOpaqueRendererMethod { /// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices. /// /// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used. -#[derive(Default, Clone, Copy, Debug, Reflect)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)] pub enum OpaqueRendererMethod { #[default] Forward, @@ -838,6 +822,11 @@ pub struct MaterialProperties { pub render_method: OpaqueRendererMethod, /// The [`AlphaMode`] of this material. pub alpha_mode: AlphaMode, + /// The bits in the [`MeshPipelineKey`] for this material. + /// + /// These are precalculated so that we can just "or" them together in + /// [`queue_material_meshes`]. + pub mesh_pipeline_key_bits: MeshPipelineKey, /// Add a bias to the view depth of the mesh which can be used to force a specific render order /// for meshes with equal depth, to avoid z-fighting. /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. @@ -1061,6 +1050,14 @@ fn prepare_material( OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, OpaqueRendererMethod::Auto => default_opaque_render_method, }; + + let mut mesh_pipeline_key_bits = MeshPipelineKey::empty(); + mesh_pipeline_key_bits.set( + MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE, + material.reads_view_transmission_texture(), + ); + mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode())); + Ok(PreparedMaterial { bindings: prepared.bindings, bind_group: prepared.bind_group, @@ -1068,8 +1065,10 @@ fn prepare_material( properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), - reads_view_transmission_texture: material.reads_view_transmission_texture(), + reads_view_transmission_texture: mesh_pipeline_key_bits + .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), render_method: method, + mesh_pipeline_key_bits, }, }) } diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index b6df62aaaca1f..0536434c25eee 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -792,11 +792,8 @@ pub fn queue_prepass_material_meshes( continue; }; - let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } + let mut mesh_key = view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); + let alpha_mode = material.properties.alpha_mode; match alpha_mode { AlphaMode::Opaque => {} diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 2dacc9943c57d..e616ee250c19f 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1644,6 +1644,10 @@ pub fn queue_shadows( }; // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued + + let mut light_key = MeshPipelineKey::DEPTH_PREPASS; + light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light); + for entity in visible_entities.iter().copied() { let Some(mesh_instance) = render_mesh_instances.get(&entity) else { continue; @@ -1662,14 +1666,7 @@ pub fn queue_shadows( }; let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) - | MeshPipelineKey::DEPTH_PREPASS; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - if is_directional_light { - mesh_key |= MeshPipelineKey::DEPTH_CLAMP_ORTHO; - } + light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); // Even though we don't use the lightmap in the shadow map, the // `SetMeshBindGroup` render command will bind the data for it. So diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f6d512625be79..6ba0bf5528d71 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -31,6 +31,7 @@ use bevy_utils::{tracing::error, Entry, HashMap, Parallel}; #[cfg(debug_assertions)] use bevy_utils::warn_once; +use static_assertions::const_assert_eq; use crate::render::{ morph::{ @@ -508,7 +509,13 @@ bitflags::bitflags! { // NOTE: Apparently quadro drivers support up to 64x MSAA. /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. pub struct MeshPipelineKey: u32 { + // Nothing const NONE = 0; + + // Inherited bits + const MORPH_TARGETS = BaseMeshPipelineKey::MORPH_TARGETS.bits(); + + // Flag bits const HDR = 1 << 0; const TONEMAP_IN_SHADER = 1 << 1; const DEBAND_DITHER = 1 << 2; @@ -522,17 +529,18 @@ bitflags::bitflags! { const SCREEN_SPACE_AMBIENT_OCCLUSION = 1 << 9; const DEPTH_CLAMP_ORTHO = 1 << 10; const TEMPORAL_JITTER = 1 << 11; - const MORPH_TARGETS = 1 << 12; - const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 13; - const LIGHTMAPPED = 1 << 14; - const IRRADIANCE_VOLUME = 1 << 15; + const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12; + const LIGHTMAPPED = 1 << 13; + const IRRADIANCE_VOLUME = 1 << 14; + const LAST_FLAG = Self::IRRADIANCE_VOLUME.bits(); + + // Bitfields const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask, and can range from 0 to 3 const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; // const BLEND_MULTIPLY = 2 << Self::BLEND_SHIFT_BITS; // ← We still have room for one more value without adding more bits const BLEND_ALPHA = 3 << Self::BLEND_SHIFT_BITS; const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; - const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS; @@ -556,36 +564,38 @@ bitflags::bitflags! { const SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM = 1 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; const SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH = 2 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; const SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA = 3 << Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS; + const ALL_RESERVED_BITS = + Self::BLEND_RESERVED_BITS.bits() | + Self::MSAA_RESERVED_BITS.bits() | + Self::TONEMAP_METHOD_RESERVED_BITS.bits() | + Self::SHADOW_FILTER_METHOD_RESERVED_BITS.bits() | + Self::VIEW_PROJECTION_RESERVED_BITS.bits() | + Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_RESERVED_BITS.bits(); } } impl MeshPipelineKey { const MSAA_MASK_BITS: u32 = 0b111; - const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); - - const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; - const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = - Self::MSAA_SHIFT_BITS - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones(); + const MSAA_SHIFT_BITS: u32 = Self::LAST_FLAG.bits().trailing_zeros(); const BLEND_MASK_BITS: u32 = 0b11; - const BLEND_SHIFT_BITS: u32 = - Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::BLEND_MASK_BITS.count_ones(); + const BLEND_SHIFT_BITS: u32 = Self::MSAA_MASK_BITS.count_ones() + Self::MSAA_SHIFT_BITS; const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; const TONEMAP_METHOD_SHIFT_BITS: u32 = - Self::BLEND_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); + Self::BLEND_MASK_BITS.count_ones() + Self::BLEND_SHIFT_BITS; const SHADOW_FILTER_METHOD_MASK_BITS: u32 = 0b11; const SHADOW_FILTER_METHOD_SHIFT_BITS: u32 = - Self::TONEMAP_METHOD_SHIFT_BITS - Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones(); + Self::TONEMAP_METHOD_MASK_BITS.count_ones() + Self::TONEMAP_METHOD_SHIFT_BITS; const VIEW_PROJECTION_MASK_BITS: u32 = 0b11; const VIEW_PROJECTION_SHIFT_BITS: u32 = - Self::SHADOW_FILTER_METHOD_SHIFT_BITS - Self::VIEW_PROJECTION_MASK_BITS.count_ones(); + Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones() + Self::SHADOW_FILTER_METHOD_SHIFT_BITS; const SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS: u32 = 0b11; - const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u32 = Self::VIEW_PROJECTION_SHIFT_BITS - - Self::SCREEN_SPACE_SPECULAR_TRANSMISSION_MASK_BITS.count_ones(); + const SCREEN_SPACE_SPECULAR_TRANSMISSION_SHIFT_BITS: u32 = + Self::VIEW_PROJECTION_MASK_BITS.count_ones() + Self::VIEW_PROJECTION_SHIFT_BITS; pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = @@ -607,14 +617,15 @@ impl MeshPipelineKey { pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { let primitive_topology_bits = ((primitive_topology as u32) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) - << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS) + << BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; Self::from_bits_retain(primitive_topology_bits) } pub fn primitive_topology(&self) -> PrimitiveTopology { - let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; + let primitive_topology_bits = (self.bits() + >> BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS) + & BaseMeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS; match primitive_topology_bits { x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList, x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList, @@ -626,6 +637,13 @@ impl MeshPipelineKey { } } +// Ensure that we didn't overflow the number of bits available in `MeshPipelineKey`. +const_assert_eq!( + (((MeshPipelineKey::LAST_FLAG.bits() << 1) - 1) | MeshPipelineKey::ALL_RESERVED_BITS.bits()) + & BaseMeshPipelineKey::all().bits(), + 0 +); + fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool { layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index e5f355bc51578..f1c229f5f4230 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -1,6 +1,7 @@ mod conversions; pub mod skinning; use bevy_transform::components::Transform; +use bitflags::bitflags; pub use wgpu::PrimitiveTopology; use crate::{ @@ -1393,6 +1394,43 @@ impl From<&Indices> for IndexFormat { } } +bitflags! { + /// Our base mesh pipeline key bits start from the highest bit and go + /// downward. The PBR mesh pipeline key bits start from the lowest bit and + /// go upward. This allows the PBR bits in the downstream crate `bevy_pbr` + /// to coexist in the same field without any shifts. + #[derive(Clone, Debug)] + pub struct BaseMeshPipelineKey: u32 { + const MORPH_TARGETS = 1 << 31; + } +} + +impl BaseMeshPipelineKey { + pub const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; + pub const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = + 31 - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones(); + + pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { + let primitive_topology_bits = ((primitive_topology as u32) + & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) + << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + Self::from_bits_retain(primitive_topology_bits) + } + + pub fn primitive_topology(&self) -> PrimitiveTopology { + let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) + & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; + match primitive_topology_bits { + x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList, + x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList, + x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip, + x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList, + x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip, + _ => PrimitiveTopology::default(), + } + } +} + /// The GPU-representation of a [`Mesh`]. /// Consists of a vertex data buffer and an optional index data buffer. #[derive(Debug, Clone)] @@ -1402,10 +1440,17 @@ pub struct GpuMesh { pub vertex_count: u32, pub morph_targets: Option, pub buffer_info: GpuBufferInfo, - pub primitive_topology: PrimitiveTopology, + pub key_bits: BaseMeshPipelineKey, pub layout: MeshVertexBufferLayoutRef, } +impl GpuMesh { + #[inline] + pub fn primitive_topology(&self) -> PrimitiveTopology { + self.key_bits.primitive_topology() + } +} + /// The index/vertex buffer info of a [`GpuMesh`]. #[derive(Debug, Clone)] pub enum GpuBufferInfo { @@ -1461,11 +1506,17 @@ impl RenderAsset for Mesh { let mesh_vertex_buffer_layout = self.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); + let mut key_bits = BaseMeshPipelineKey::from_primitive_topology(self.primitive_topology()); + key_bits.set( + BaseMeshPipelineKey::MORPH_TARGETS, + self.morph_targets.is_some(), + ); + Ok(GpuMesh { vertex_buffer, vertex_count: self.count_vertices() as u32, buffer_info, - primitive_topology: self.primitive_topology(), + key_bits, layout: mesh_vertex_buffer_layout, morph_targets: self .morph_targets diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 746f7bee7afff..93c4b236c6aeb 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -8,6 +8,7 @@ use crate::{ }, }; use bevy_ecs::system::Resource; +use bevy_utils::hashbrown::hash_map::VacantEntry; use bevy_utils::{default, hashbrown::hash_map::RawEntryMut, tracing::error, Entry, HashMap}; use std::{fmt::Debug, hash::Hash}; use thiserror::Error; @@ -84,9 +85,14 @@ pub trait SpecializedMeshPipeline { #[derive(Resource)] pub struct SpecializedMeshPipelines { mesh_layout_cache: HashMap<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>, - vertex_layout_cache: HashMap>, + vertex_layout_cache: VertexLayoutCache, } +pub type VertexLayoutCache = HashMap< + VertexBufferLayout, + HashMap<::Key, CachedRenderPipelineId>, +>; + impl Default for SpecializedMeshPipelines { fn default() -> Self { Self { @@ -105,55 +111,72 @@ impl SpecializedMeshPipelines { key: S::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result { - match self.mesh_layout_cache.entry((layout.clone(), key.clone())) { + return match self.mesh_layout_cache.entry((layout.clone(), key.clone())) { Entry::Occupied(entry) => Ok(*entry.into_mut()), - Entry::Vacant(entry) => { - let descriptor = specialize_pipeline - .specialize(key.clone(), layout) - .map_err(|mut err| { - { - let SpecializedMeshPipelineError::MissingVertexAttribute(err) = - &mut err; - err.pipeline_type = Some(std::any::type_name::()); - } - err - })?; - // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout - // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them - let layout_map = match self - .vertex_layout_cache - .raw_entry_mut() - .from_key(&descriptor.vertex.buffers[0]) - { - RawEntryMut::Occupied(entry) => entry.into_mut(), - RawEntryMut::Vacant(entry) => { - entry - .insert(descriptor.vertex.buffers[0].clone(), Default::default()) - .1 + Entry::Vacant(entry) => specialize_slow( + &mut self.vertex_layout_cache, + cache, + specialize_pipeline, + key, + layout, + entry, + ), + }; + + #[cold] + fn specialize_slow( + vertex_layout_cache: &mut VertexLayoutCache, + cache: &PipelineCache, + specialize_pipeline: &S, + key: S::Key, + layout: &MeshVertexBufferLayoutRef, + entry: VacantEntry<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>, + ) -> Result + where + S: SpecializedMeshPipeline, + { + let descriptor = specialize_pipeline + .specialize(key.clone(), layout) + .map_err(|mut err| { + { + let SpecializedMeshPipelineError::MissingVertexAttribute(err) = &mut err; + err.pipeline_type = Some(std::any::type_name::()); } - }; - Ok(*entry.insert(match layout_map.entry(key) { - Entry::Occupied(entry) => { - if cfg!(debug_assertions) { - let stored_descriptor = - cache.get_render_pipeline_descriptor(*entry.get()); - if stored_descriptor != &descriptor { - error!( - "The cached pipeline descriptor for {} is not \ - equal to the generated descriptor for the given key. \ - This means the SpecializePipeline implementation uses \ - unused' MeshVertexBufferLayout information to specialize \ - the pipeline. This is not allowed because it would invalidate \ - the pipeline cache.", - std::any::type_name::() - ); - } + err + })?; + // Different MeshVertexBufferLayouts can produce the same final VertexBufferLayout + // We want compatible vertex buffer layouts to use the same pipelines, so we must "deduplicate" them + let layout_map = match vertex_layout_cache + .raw_entry_mut() + .from_key(&descriptor.vertex.buffers[0]) + { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + entry + .insert(descriptor.vertex.buffers[0].clone(), Default::default()) + .1 + } + }; + Ok(*entry.insert(match layout_map.entry(key) { + Entry::Occupied(entry) => { + if cfg!(debug_assertions) { + let stored_descriptor = cache.get_render_pipeline_descriptor(*entry.get()); + if stored_descriptor != &descriptor { + error!( + "The cached pipeline descriptor for {} is not \ + equal to the generated descriptor for the given key. \ + This means the SpecializePipeline implementation uses \ + unused' MeshVertexBufferLayout information to specialize \ + the pipeline. This is not allowed because it would invalidate \ + the pipeline cache.", + std::any::type_name::() + ); } - *entry.into_mut() } - Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)), - })) - } + *entry.into_mut() + } + Entry::Vacant(entry) => *entry.insert(cache.queue_render_pipeline(descriptor)), + })) } } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4481dc2bb5c02..ad05b7fe61737 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -426,7 +426,7 @@ pub fn queue_material2d_meshes( continue; }; let mesh_key = - view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); let pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 860bea5d60670..d8a5524aa8938 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -383,7 +383,7 @@ pub fn queue_colored_mesh2d( let mut mesh2d_key = mesh_key; if let Some(mesh) = render_meshes.get(mesh2d_handle) { mesh2d_key |= - Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); } let pipeline_id = diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 575f287962336..44a01c75845d4 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -133,7 +133,8 @@ fn queue_custom( let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = + view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); let pipeline = pipelines .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) .unwrap(); From d0a5ddacd9bee2aee304c85fe7e2291da54c356a Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 2 Apr 2024 00:05:52 +0200 Subject: [PATCH 10/12] many_cubes: Add no automatic batching and generation of different meshes (#12363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Enable stressing of more of the material mesh entity draw code paths ## Solution - Support generation of a number of different mesh assets from the built-in primitives, and select randomly from them. This breaks batches based on different meshes. - Support disabling automatic batching. This skips the batching cost at the expense of stressing render pass draw command encoding. - Support enabling directional light cascaded shadow mapping - this is commonly a big source of slow down in normal scenes. --------- Co-authored-by: Alice Cecile Co-authored-by: François Mockers --- examples/stress_tests/many_cubes.rs | 185 +++++++++++++++++++++++++--- 1 file changed, 167 insertions(+), 18 deletions(-) diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index d0079d4eff7e3..510a83612a6d1 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -14,8 +14,10 @@ use argh::FromArgs; use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, math::{DVec2, DVec3}, + pbr::NotShadowCaster, prelude::*, render::{ + batching::NoAutomaticBatching, render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, view::NoFrustumCulling, @@ -39,15 +41,27 @@ struct Args { /// whether to vary the material data in each instance. #[argh(switch)] - vary_per_instance: bool, + vary_material_data_per_instance: bool, /// the number of different textures from which to randomly select the material base color. 0 means no textures. #[argh(option, default = "0")] material_texture_count: usize, - /// whether to disable frustum culling, for stress testing purposes + /// the number of different meshes from which to randomly select. Clamped to at least 1. + #[argh(option, default = "1")] + mesh_count: usize, + + /// whether to disable frustum culling. Stresses queuing and batching as all mesh material entities in the scene are always drawn. #[argh(switch)] no_frustum_culling: bool, + + /// whether to disable automatic batching. Skips batching resulting in heavy stress on render pass draw command encoding. + #[argh(switch)] + no_automatic_batching: bool, + + /// whether to enable directional light cascaded shadow mapping. + #[argh(switch)] + shadows: bool, } #[derive(Default, Clone)] @@ -109,7 +123,7 @@ const HEIGHT: usize = 200; fn setup( mut commands: Commands, args: Res, - mut meshes: ResMut>, + mesh_assets: ResMut>, material_assets: ResMut>, images: ResMut>, ) { @@ -118,8 +132,9 @@ fn setup( let args = args.into_inner(); let images = images.into_inner(); let material_assets = material_assets.into_inner(); + let mesh_assets = mesh_assets.into_inner(); - let mesh = meshes.add(Cuboid::default()); + let meshes = init_meshes(args, mesh_assets); let material_textures = init_textures(args, images); let materials = init_materials(args, &material_textures, material_assets); @@ -139,23 +154,40 @@ fn setup( let spherical_polar_theta_phi = fibonacci_spiral_on_sphere(golden_ratio, i, N_POINTS); let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi); + let (mesh, transform) = meshes.choose(&mut material_rng).unwrap(); let mut cube = commands.spawn(PbrBundle { mesh: mesh.clone(), material: materials.choose(&mut material_rng).unwrap().clone(), - transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()), + transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()) + .looking_at(Vec3::ZERO, Vec3::Y) + .mul_transform(*transform), ..default() }); if args.no_frustum_culling { cube.insert(NoFrustumCulling); } + if args.no_automatic_batching { + cube.insert(NoAutomaticBatching); + } } // camera commands.spawn(Camera3dBundle::default()); + // Inside-out box around the meshes onto which shadows are cast (though you cannot see them...) + commands.spawn(( + PbrBundle { + mesh: mesh_assets.add(Cuboid::from_size(Vec3::splat(radius as f32 * 2.2))), + material: material_assets.add(StandardMaterial::from(Color::WHITE)), + transform: Transform::from_scale(-Vec3::ONE), + ..default() + }, + NotShadowCaster, + )); } _ => { // NOTE: This pattern is good for demonstrating that frustum culling is working correctly // as the number of visible meshes rises and falls depending on the viewing angle. + let scale = 2.5; for x in 0..WIDTH { for y in 0..HEIGHT { // introduce spaces to break any kind of moiré pattern @@ -164,44 +196,62 @@ fn setup( } // cube commands.spawn(PbrBundle { - mesh: mesh.clone(), + mesh: meshes.choose(&mut material_rng).unwrap().0.clone(), material: materials.choose(&mut material_rng).unwrap().clone(), - transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0), + transform: Transform::from_xyz((x as f32) * scale, (y as f32) * scale, 0.0), ..default() }); commands.spawn(PbrBundle { - mesh: mesh.clone(), + mesh: meshes.choose(&mut material_rng).unwrap().0.clone(), material: materials.choose(&mut material_rng).unwrap().clone(), transform: Transform::from_xyz( - (x as f32) * 2.5, - HEIGHT as f32 * 2.5, - (y as f32) * 2.5, + (x as f32) * scale, + HEIGHT as f32 * scale, + (y as f32) * scale, ), ..default() }); commands.spawn(PbrBundle { - mesh: mesh.clone(), + mesh: meshes.choose(&mut material_rng).unwrap().0.clone(), material: materials.choose(&mut material_rng).unwrap().clone(), - transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5), + transform: Transform::from_xyz((x as f32) * scale, 0.0, (y as f32) * scale), ..default() }); commands.spawn(PbrBundle { - mesh: mesh.clone(), + mesh: meshes.choose(&mut material_rng).unwrap().0.clone(), material: materials.choose(&mut material_rng).unwrap().clone(), - transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5), + transform: Transform::from_xyz(0.0, (x as f32) * scale, (y as f32) * scale), ..default() }); } } // camera + let center = 0.5 * scale * Vec3::new(WIDTH as f32, HEIGHT as f32, WIDTH as f32); commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(WIDTH as f32, HEIGHT as f32, WIDTH as f32), + transform: Transform::from_translation(center), ..default() }); + // Inside-out box around the meshes onto which shadows are cast (though you cannot see them...) + commands.spawn(( + PbrBundle { + mesh: mesh_assets.add(Cuboid::from_size(2.0 * 1.1 * center)), + material: material_assets.add(StandardMaterial::from(Color::WHITE)), + transform: Transform::from_scale(-Vec3::ONE).with_translation(center), + ..default() + }, + NotShadowCaster, + )); } } - commands.spawn(DirectionalLightBundle::default()); + commands.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + shadows_enabled: args.shadows, + ..default() + }, + transform: Transform::IDENTITY.looking_at(Vec3::new(0.0, -1.0, -1.0), Vec3::Y), + ..default() + }); } fn init_textures(args: &Args, images: &mut Assets) -> Vec> { @@ -234,7 +284,7 @@ fn init_materials( textures: &[Handle], assets: &mut Assets, ) -> Vec> { - let capacity = if args.vary_per_instance { + let capacity = if args.vary_material_data_per_instance { match args.layout { Layout::Cube => (WIDTH - WIDTH / 10) * (HEIGHT - HEIGHT / 10), Layout::Sphere => WIDTH * HEIGHT * 4, @@ -269,6 +319,105 @@ fn init_materials( materials } +fn init_meshes(args: &Args, assets: &mut Assets) -> Vec<(Handle, Transform)> { + let capacity = args.mesh_count.max(1); + + // We're seeding the PRNG here to make this example deterministic for testing purposes. + // This isn't strictly required in practical use unless you need your app to be deterministic. + let mut radius_rng = ChaCha8Rng::seed_from_u64(42); + let mut variant = 0; + std::iter::repeat_with(|| { + let radius = radius_rng.gen_range(0.25f32..=0.75f32); + let (handle, transform) = match variant % 15 { + 0 => ( + assets.add(Cuboid { + half_size: Vec3::splat(radius), + }), + Transform::IDENTITY, + ), + 1 => ( + assets.add(Capsule3d { + radius, + half_length: radius, + }), + Transform::IDENTITY, + ), + 2 => ( + assets.add(Circle { radius }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ), + 3 => { + let mut vertices = [Vec2::ZERO; 3]; + let dtheta = std::f32::consts::TAU / 3.0; + for (i, vertex) in vertices.iter_mut().enumerate() { + let (s, c) = (i as f32 * dtheta).sin_cos(); + *vertex = Vec2::new(c, s) * radius; + } + ( + assets.add(Triangle2d { vertices }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ) + } + 4 => ( + assets.add(Rectangle { + half_size: Vec2::splat(radius), + }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ), + v if (5..=8).contains(&v) => ( + assets.add(RegularPolygon { + circumcircle: Circle { radius }, + sides: v, + }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ), + 9 => ( + assets.add(Cylinder { + radius, + half_height: radius, + }), + Transform::IDENTITY, + ), + 10 => ( + assets.add(Ellipse { + half_size: Vec2::new(radius, 0.5 * radius), + }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ), + 11 => ( + assets.add( + Plane3d { + normal: Dir3::NEG_Z, + } + .mesh() + .size(radius, radius), + ), + Transform::IDENTITY, + ), + 12 => (assets.add(Sphere { radius }), Transform::IDENTITY), + 13 => ( + assets.add(Torus { + minor_radius: 0.5 * radius, + major_radius: radius, + }), + Transform::IDENTITY.looking_at(Vec3::Y, Vec3::Y), + ), + 14 => ( + assets.add(Capsule2d { + radius, + half_length: radius, + }), + Transform::IDENTITY.looking_at(Vec3::Z, Vec3::Y), + ), + _ => unreachable!(), + }; + variant += 1; + (handle, transform) + }) + .take(capacity) + .collect() +} + // NOTE: This epsilon value is apparently optimal for optimizing for the average // nearest-neighbor distance. See: // http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/ From cf092d45f949494d132abc0fd0abab94cc7c721b Mon Sep 17 00:00:00 2001 From: Brett Striker Date: Mon, 1 Apr 2024 18:47:34 -0400 Subject: [PATCH 11/12] [bevy_ui/layout] Extract UiSurface to its own file (#12801) This is 1 of 5 iterative PR's that affect bevy_ui/layout --- # Objective - Extract `UiSurface` into its own file to make diffs in future PR's easier to digest ## Solution - Moved `UiSurface` to its own file --- crates/bevy_ui/src/layout/debug.rs | 9 +- crates/bevy_ui/src/layout/mod.rs | 248 ++---------------------- crates/bevy_ui/src/layout/ui_surface.rs | 226 +++++++++++++++++++++ crates/bevy_ui/src/lib.rs | 1 + 4 files changed, 253 insertions(+), 231 deletions(-) create mode 100644 crates/bevy_ui/src/layout/ui_surface.rs diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 8d5f27e169eca..67e7a205b2915 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -1,10 +1,13 @@ -use crate::UiSurface; -use bevy_ecs::prelude::Entity; -use bevy_utils::HashMap; use std::fmt::Write; + use taffy::prelude::Node; use taffy::tree::LayoutTree; +use bevy_ecs::prelude::Entity; +use bevy_utils::HashMap; + +use crate::layout::ui_surface::UiSurface; + /// Prints a debug representation of the computed layout of the UI layout tree for each window. pub fn print_ui_layout_tree(ui_surface: &UiSurface) { let taffy_to_entity: HashMap = ui_surface diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 7ff8a59229fca..62d60f50cbb18 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,14 +1,12 @@ -mod convert; -pub mod debug; +use thiserror::Error; -use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityHashMap}, + entity::Entity, event::EventReader, query::{With, Without}, removal_detection::RemovedComponents, - system::{Query, Res, ResMut, Resource, SystemParam}, + system::{Query, Res, ResMut, SystemParam}, world::Ref, }; use bevy_hierarchy::{Children, Parent}; @@ -16,11 +14,15 @@ use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_transform::components::Transform; use bevy_utils::tracing::warn; -use bevy_utils::{default, HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet}; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; -use std::fmt; -use taffy::{tree::LayoutTree, Taffy}; -use thiserror::Error; +use ui_surface::UiSurface; + +use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; + +mod convert; +pub mod debug; +pub(crate) mod ui_surface; pub struct LayoutContext { pub scale_factor: f32, @@ -41,218 +43,6 @@ impl LayoutContext { } } -#[derive(Debug, Clone, PartialEq, Eq)] -struct RootNodePair { - // The implicit "viewport" node created by Bevy - implicit_viewport_node: taffy::node::Node, - // The root (parentless) node specified by the user - user_root_node: taffy::node::Node, -} - -#[derive(Resource)] -pub struct UiSurface { - entity_to_taffy: EntityHashMap, - camera_entity_to_taffy: EntityHashMap>, - camera_roots: EntityHashMap>, - taffy: Taffy, -} - -fn _assert_send_sync_ui_surface_impl_safe() { - fn _assert_send_sync() {} - _assert_send_sync::>(); - _assert_send_sync::(); - _assert_send_sync::(); -} - -impl fmt::Debug for UiSurface { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("UiSurface") - .field("entity_to_taffy", &self.entity_to_taffy) - .field("camera_roots", &self.camera_roots) - .finish() - } -} - -impl Default for UiSurface { - fn default() -> Self { - let mut taffy = Taffy::new(); - taffy.disable_rounding(); - Self { - entity_to_taffy: Default::default(), - camera_entity_to_taffy: Default::default(), - camera_roots: Default::default(), - taffy, - } - } -} - -impl UiSurface { - /// Retrieves the Taffy node associated with the given UI node entity and updates its style. - /// If no associated Taffy node exists a new Taffy node is inserted into the Taffy layout. - pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) { - let mut added = false; - let taffy = &mut self.taffy; - let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { - added = true; - taffy.new_leaf(convert::from_style(context, style)).unwrap() - }); - - if !added { - self.taffy - .set_style(*taffy_node, convert::from_style(context, style)) - .unwrap(); - } - } - - /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`] if the node exists. - pub fn try_update_measure( - &mut self, - entity: Entity, - measure_func: taffy::node::MeasureFunc, - ) -> Option<()> { - let taffy_node = self.entity_to_taffy.get(&entity)?; - - self.taffy.set_measure(*taffy_node, Some(measure_func)).ok() - } - - /// Update the children of the taffy node corresponding to the given [`Entity`]. - pub fn update_children(&mut self, entity: Entity, children: &Children) { - let mut taffy_children = Vec::with_capacity(children.len()); - for child in children { - if let Some(taffy_node) = self.entity_to_taffy.get(child) { - taffy_children.push(*taffy_node); - } else { - warn!( - "Unstyled child in a UI entity hierarchy. You are using an entity \ -without UI components as a child of an entity with UI components, results may be unexpected." - ); - } - } - - let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); - self.taffy - .set_children(*taffy_node, &taffy_children) - .unwrap(); - } - - /// Removes children from the entity's taffy node if it exists. Does nothing otherwise. - pub fn try_remove_children(&mut self, entity: Entity) { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy.set_children(*taffy_node, &[]).unwrap(); - } - } - - /// Removes the measure from the entity's taffy node if it exists. Does nothing otherwise. - pub fn try_remove_measure(&mut self, entity: Entity) { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy.set_measure(*taffy_node, None).unwrap(); - } - } - - /// Set the ui node entities without a [`Parent`] as children to the root node in the taffy layout. - pub fn set_camera_children( - &mut self, - camera_id: Entity, - children: impl Iterator, - ) { - let viewport_style = taffy::style::Style { - display: taffy::style::Display::Grid, - // Note: Taffy percentages are floats ranging from 0.0 to 1.0. - // So this is setting width:100% and height:100% - size: taffy::geometry::Size { - width: taffy::style::Dimension::Percent(1.0), - height: taffy::style::Dimension::Percent(1.0), - }, - align_items: Some(taffy::style::AlignItems::Start), - justify_items: Some(taffy::style::JustifyItems::Start), - ..default() - }; - - let camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default(); - let existing_roots = self.camera_roots.entry(camera_id).or_default(); - let mut new_roots = Vec::new(); - for entity in children { - let node = *self.entity_to_taffy.get(&entity).unwrap(); - let root_node = existing_roots - .iter() - .find(|n| n.user_root_node == node) - .cloned() - .unwrap_or_else(|| { - if let Some(previous_parent) = self.taffy.parent(node) { - // remove the root node from the previous implicit node's children - self.taffy.remove_child(previous_parent, node).unwrap(); - } - - let viewport_node = *camera_root_node_map - .entry(entity) - .or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()); - self.taffy.add_child(viewport_node, node).unwrap(); - - RootNodePair { - implicit_viewport_node: viewport_node, - user_root_node: node, - } - }); - new_roots.push(root_node); - } - - self.camera_roots.insert(camera_id, new_roots); - } - - /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_camera_layout(&mut self, camera: Entity, render_target_resolution: UVec2) { - let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { - return; - }; - - let available_space = taffy::geometry::Size { - width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32), - height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32), - }; - for root_nodes in camera_root_nodes { - self.taffy - .compute_layout(root_nodes.implicit_viewport_node, available_space) - .unwrap(); - } - } - - /// Removes each camera entity from the internal map and then removes their associated node from taffy - pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { - for entity in entities { - if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) { - for (_, node) in camera_root_node_map.iter() { - self.taffy.remove(*node).unwrap(); - } - } - } - } - - /// Removes each entity from the internal map and then removes their associated node from taffy - pub fn remove_entities(&mut self, entities: impl IntoIterator) { - for entity in entities { - if let Some(node) = self.entity_to_taffy.remove(&entity) { - self.taffy.remove(node).unwrap(); - } - } - } - - /// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`]. - /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. - pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> { - if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { - self.taffy - .layout(*taffy_node) - .map_err(LayoutError::TaffyError) - } else { - warn!( - "Styled child in a non-UI entity hierarchy. You are using an entity \ -with UI components as a child of an entity without UI components, results may be unexpected." - ); - Err(LayoutError::InvalidHierarchy) - } - } -} - #[derive(Debug, Error)] pub enum LayoutError { #[error("Invalid hierarchy")] @@ -533,12 +323,8 @@ fn round_layout_coords(value: Vec2) -> Vec2 { #[cfg(test)] mod tests { - use crate::layout::round_layout_coords; - use crate::prelude::*; - use crate::ui_layout_system; - use crate::update::update_target_camera_system; - use crate::ContentSize; - use crate::UiSurface; + use taffy::tree::LayoutTree; + use bevy_asset::AssetEvent; use bevy_asset::Assets; use bevy_core_pipeline::core_2d::Camera2dBundle; @@ -566,7 +352,13 @@ mod tests { use bevy_window::WindowResized; use bevy_window::WindowResolution; use bevy_window::WindowScaleFactorChanged; - use taffy::tree::LayoutTree; + + use crate::layout::round_layout_coords; + use crate::layout::ui_surface::UiSurface; + use crate::prelude::*; + use crate::ui_layout_system; + use crate::update::update_target_camera_system; + use crate::ContentSize; #[test] fn round_layout_coords_must_round_ties_up() { diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs new file mode 100644 index 0000000000000..74586e8786ac0 --- /dev/null +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -0,0 +1,226 @@ +use std::fmt; + +use taffy::prelude::LayoutTree; +use taffy::Taffy; + +use bevy_ecs::entity::{Entity, EntityHashMap}; +use bevy_ecs::prelude::Resource; +use bevy_hierarchy::Children; +use bevy_math::UVec2; +use bevy_utils::default; +use bevy_utils::tracing::warn; + +use crate::layout::convert; +use crate::{LayoutContext, LayoutError, Style}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RootNodePair { + // The implicit "viewport" node created by Bevy + pub(super) implicit_viewport_node: taffy::node::Node, + // The root (parentless) node specified by the user + pub(super) user_root_node: taffy::node::Node, +} + +#[derive(Resource)] +pub struct UiSurface { + pub(super) entity_to_taffy: EntityHashMap, + pub(super) camera_entity_to_taffy: EntityHashMap>, + pub(super) camera_roots: EntityHashMap>, + pub(super) taffy: Taffy, +} + +fn _assert_send_sync_ui_surface_impl_safe() { + fn _assert_send_sync() {} + _assert_send_sync::>(); + _assert_send_sync::(); + _assert_send_sync::(); +} + +impl fmt::Debug for UiSurface { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("UiSurface") + .field("entity_to_taffy", &self.entity_to_taffy) + .field("camera_roots", &self.camera_roots) + .finish() + } +} + +impl Default for UiSurface { + fn default() -> Self { + let mut taffy = Taffy::new(); + taffy.disable_rounding(); + Self { + entity_to_taffy: Default::default(), + camera_entity_to_taffy: Default::default(), + camera_roots: Default::default(), + taffy, + } + } +} + +impl UiSurface { + /// Retrieves the Taffy node associated with the given UI node entity and updates its style. + /// If no associated Taffy node exists a new Taffy node is inserted into the Taffy layout. + pub fn upsert_node(&mut self, entity: Entity, style: &Style, context: &LayoutContext) { + let mut added = false; + let taffy = &mut self.taffy; + let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { + added = true; + taffy.new_leaf(convert::from_style(context, style)).unwrap() + }); + + if !added { + self.taffy + .set_style(*taffy_node, convert::from_style(context, style)) + .unwrap(); + } + } + + /// Update the `MeasureFunc` of the taffy node corresponding to the given [`Entity`] if the node exists. + pub fn try_update_measure( + &mut self, + entity: Entity, + measure_func: taffy::node::MeasureFunc, + ) -> Option<()> { + let taffy_node = self.entity_to_taffy.get(&entity)?; + + self.taffy.set_measure(*taffy_node, Some(measure_func)).ok() + } + + /// Update the children of the taffy node corresponding to the given [`Entity`]. + pub fn update_children(&mut self, entity: Entity, children: &Children) { + let mut taffy_children = Vec::with_capacity(children.len()); + for child in children { + if let Some(taffy_node) = self.entity_to_taffy.get(child) { + taffy_children.push(*taffy_node); + } else { + warn!( + "Unstyled child in a UI entity hierarchy. You are using an entity \ +without UI components as a child of an entity with UI components, results may be unexpected." + ); + } + } + + let taffy_node = self.entity_to_taffy.get(&entity).unwrap(); + self.taffy + .set_children(*taffy_node, &taffy_children) + .unwrap(); + } + + /// Removes children from the entity's taffy node if it exists. Does nothing otherwise. + pub fn try_remove_children(&mut self, entity: Entity) { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy.set_children(*taffy_node, &[]).unwrap(); + } + } + + /// Removes the measure from the entity's taffy node if it exists. Does nothing otherwise. + pub fn try_remove_measure(&mut self, entity: Entity) { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy.set_measure(*taffy_node, None).unwrap(); + } + } + + /// Set the ui node entities without a [`bevy_hierarchy::Parent`] as children to the root node in the taffy layout. + pub fn set_camera_children( + &mut self, + camera_id: Entity, + children: impl Iterator, + ) { + let viewport_style = taffy::style::Style { + display: taffy::style::Display::Grid, + // Note: Taffy percentages are floats ranging from 0.0 to 1.0. + // So this is setting width:100% and height:100% + size: taffy::geometry::Size { + width: taffy::style::Dimension::Percent(1.0), + height: taffy::style::Dimension::Percent(1.0), + }, + align_items: Some(taffy::style::AlignItems::Start), + justify_items: Some(taffy::style::JustifyItems::Start), + ..default() + }; + + let camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default(); + let existing_roots = self.camera_roots.entry(camera_id).or_default(); + let mut new_roots = Vec::new(); + for entity in children { + let node = *self.entity_to_taffy.get(&entity).unwrap(); + let root_node = existing_roots + .iter() + .find(|n| n.user_root_node == node) + .cloned() + .unwrap_or_else(|| { + if let Some(previous_parent) = self.taffy.parent(node) { + // remove the root node from the previous implicit node's children + self.taffy.remove_child(previous_parent, node).unwrap(); + } + + let viewport_node = *camera_root_node_map + .entry(entity) + .or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()); + self.taffy.add_child(viewport_node, node).unwrap(); + + RootNodePair { + implicit_viewport_node: viewport_node, + user_root_node: node, + } + }); + new_roots.push(root_node); + } + + self.camera_roots.insert(camera_id, new_roots); + } + + /// Compute the layout for each window entity's corresponding root node in the layout. + pub fn compute_camera_layout(&mut self, camera: Entity, render_target_resolution: UVec2) { + let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { + return; + }; + + let available_space = taffy::geometry::Size { + width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32), + height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32), + }; + for root_nodes in camera_root_nodes { + self.taffy + .compute_layout(root_nodes.implicit_viewport_node, available_space) + .unwrap(); + } + } + + /// Removes each camera entity from the internal map and then removes their associated node from taffy + pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { + for entity in entities { + if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) { + for (_, node) in camera_root_node_map.iter() { + self.taffy.remove(*node).unwrap(); + } + } + } + } + + /// Removes each entity from the internal map and then removes their associated node from taffy + pub fn remove_entities(&mut self, entities: impl IntoIterator) { + for entity in entities { + if let Some(node) = self.entity_to_taffy.remove(&entity) { + self.taffy.remove(node).unwrap(); + } + } + } + + /// Get the layout geometry for the taffy node corresponding to the ui node [`Entity`]. + /// Does not compute the layout geometry, `compute_window_layouts` should be run before using this function. + pub fn get_layout(&self, entity: Entity) -> Result<&taffy::layout::Layout, LayoutError> { + if let Some(taffy_node) = self.entity_to_taffy.get(&entity) { + self.taffy + .layout(*taffy_node) + .map_err(LayoutError::TaffyError) + } else { + warn!( + "Styled child in a non-UI entity hierarchy. You are using an entity \ +with UI components as a child of an entity without UI components, results may be unexpected." + ); + Err(LayoutError::InvalidHierarchy) + } + } +} diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 32cefb8299c8e..e1eacd8d559a9 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -55,6 +55,7 @@ use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_render::RenderApp; use bevy_transform::TransformSystem; +use layout::ui_surface::UiSurface; use stack::ui_stack_system; pub use stack::UiStack; use update::{update_clipping_system, update_target_camera_system}; From 8092e2c86d32a0f83c7c7d97233130d6d7f68dc4 Mon Sep 17 00:00:00 2001 From: mamekoro <86554319+mamekoro@users.noreply.github.com> Date: Tue, 2 Apr 2024 08:02:07 +0900 Subject: [PATCH 12/12] Implement basic traits for `AspectRatio` (#12840) # Objective `AspectRatio` is a newtype of `f32`, so it can implement basic traits; `Copy`, `Clone`, `Debug`, `PartialEq` and `PartialOrd`. ## Solution Derive basic traits for `AspectRatio`. --- crates/bevy_math/src/aspect_ratio.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_math/src/aspect_ratio.rs b/crates/bevy_math/src/aspect_ratio.rs index 08e47e91ba4e1..2296c898a14d4 100644 --- a/crates/bevy_math/src/aspect_ratio.rs +++ b/crates/bevy_math/src/aspect_ratio.rs @@ -3,6 +3,7 @@ use crate::Vec2; /// An `AspectRatio` is the ratio of width to height. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] pub struct AspectRatio(f32); impl AspectRatio {