From 7fe08535dfe99fb2ff999009bb497db61b5196ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Tue, 18 Jul 2023 10:15:47 +0200 Subject: [PATCH 01/26] example showcase: switch default api to webgpu (#9193) # Objective - in #9168 I did some change to the showcase script, introducing the notion of web api and setting the default Web API to webgl2 - that script was actually only called for webgpu example, so that should have been the default value --- tools/example-showcase/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs index cd1e0084aa..e0b225958c 100644 --- a/tools/example-showcase/src/main.rs +++ b/tools/example-showcase/src/main.rs @@ -53,7 +53,7 @@ enum Action { /// Path to the folder where the content should be created content_folder: String, - #[arg(value_enum, long, default_value_t = WebApi::Webgl2)] + #[arg(value_enum, long, default_value_t = WebApi::Webgpu)] /// Which API to use for rendering api: WebApi, }, From 6f8089d35cbd79fa1ae7d7baf7b8c19b27a88eb6 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 19 Jul 2023 08:29:14 +0100 Subject: [PATCH 02/26] Fix UI corruption for AMD gpus with Vulkan (#9169) # Objective Fixes #8894 Fixes #7944 ## Solution The UI pipeline's `MultisampleState::count` is set to 1 whereas the `MultisampleState::count` for the camera's ViewTarget is taken from the `Msaa` resource, and corruption occurs when these two values are different. This PR solves the problem by setting `MultisampleState::count` for the UI pipeline to the value from the Msaa resource too. I don't know much about Bevy's rendering internals or graphics hardware, so maybe there is a better solution than this. UI MSAA was probably disabled for a good reason (performance?). ## Changelog * Enabled multisampling for the UI pipeline. --- crates/bevy_ui/src/render/mod.rs | 7 ++++++- crates/bevy_ui/src/render/pipeline.rs | 3 ++- crates/bevy_ui/src/render/render_pass.rs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index fba6ba1af1..f54ae28cb3 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -3,6 +3,7 @@ mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_hierarchy::Parent; +use bevy_render::view::Msaa; use bevy_render::{ExtractSchedule, Render}; use bevy_window::{PrimaryWindow, Window}; pub use pipeline::*; @@ -801,6 +802,7 @@ pub fn queue_uinodes( ui_batches: Query<(Entity, &UiBatch)>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, events: Res, + msaa: Res, ) { // If an image has changed, the GpuImage has (probably) changed for event in &events.images { @@ -826,7 +828,10 @@ pub fn queue_uinodes( let pipeline = pipelines.specialize( &pipeline_cache, &ui_pipeline, - UiPipelineKey { hdr: view.hdr }, + UiPipelineKey { + hdr: view.hdr, + msaa_samples: msaa.samples(), + }, ); for (entity, batch) in &ui_batches { image_bind_groups diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index f6b4b0cc3c..a61a4a68a7 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -62,6 +62,7 @@ impl FromWorld for UiPipeline { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey { pub hdr: bool, + pub msaa_samples: u32, } impl SpecializedRenderPipeline for UiPipeline { @@ -117,7 +118,7 @@ impl SpecializedRenderPipeline for UiPipeline { }, depth_stencil: None, multisample: MultisampleState { - count: 1, + count: key.msaa_samples, mask: !0, alpha_to_coverage_enabled: false, }, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 90e7b6059c..d31088a5d8 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -72,7 +72,7 @@ impl Node for UiPassNode { }; let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("ui_pass"), - color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { + color_attachments: &[Some(target.get_color_attachment(Operations { load: LoadOp::Load, store: true, }))], From fd35e582dc939a8e55f41471c3b397f797206191 Mon Sep 17 00:00:00 2001 From: FlippinBerger Date: Wed, 19 Jul 2023 05:54:40 -0600 Subject: [PATCH 03/26] Add the Has world query to bevy_ecs::prelude (#9204) # Objective Addresses #9196 by adding query::Has to the bevy_ecs::prelude. --- crates/bevy_ecs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index c239634440..cb91dae603 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -37,7 +37,7 @@ pub mod prelude { component::Component, entity::Entity, event::{Event, EventReader, EventWriter, Events}, - query::{Added, AnyOf, Changed, Or, QueryState, With, Without}, + query::{Added, AnyOf, Changed, Has, Or, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ apply_deferred, apply_state_transition, common_conditions::*, Condition, From a0972c2b1bae30f705b2559026d1d4267e97abfd Mon Sep 17 00:00:00 2001 From: Chip Collier Date: Wed, 19 Jul 2023 14:05:04 +0200 Subject: [PATCH 04/26] Add some more helpful errors to BevyManifest when it doesn't find Cargo.toml (#9207) When building Bevy using Bazel, you don't need a 'Cargo.toml'... except Bevy requires it currently. Hopefully this can help illuminate the requirement. # Objective I recently started exploring Bazel and Buck2. Currently Bazel has some great advantages over Cargo for me and I was pretty happy to find that things generally work quite well! Once I added a target to my test project that depended on bevy but didn't use Cargo, I didn't create a Cargo.toml file for it and things appeared to work, but as soon as I went to derive from Component the build failed with the cryptic error: ``` ERROR: /Users/photex/workspaces/personal/mb-rogue/scratch/BUILD:24:12: Compiling Rust bin hello_bevy (0 files) failed: (Exit 1): process_wrapper failed: error executing command (from target //scratch:hello_bevy) bazel-out/darwin_arm64-opt-exec-2B5CBBC6/bin/external/rules_rust/util/process_wrapper/process_wrapper --arg-file ... (remaining 312 arguments skipped) error: proc-macro derive panicked --> scratch/hello_bevy.rs:5:10 | 5 | #[derive(Component)] | ^^^^^^^^^ | = help: message: called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } error: proc-macro derive panicked --> scratch/hello_bevy.rs:8:10 | 8 | #[derive(Component)] | ^^^^^^^^^ | = help: message: called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` ## Solution After poking around I realized that the proc macros in Bevy all use bevy_macro_utils::BevyManifest, which was attempting to load a Cargo manifest that doesn't exist. This PR doesn't address the Cargo requirement (I'd love to see if there was a way to support more than Cargo transparently), but it *does* replace some calls to unwrap with expect and hopefully the error messages will be more helpful for other folks like me hoping to pat down a new trail: ``` ERROR: /Users/photex/workspaces/personal/mb-rogue/scratch/BUILD:23:12: Compiling Rust bin hello_bevy (0 files) failed: (Exit 1): process_wrapper failed: error executing command (from target //scratch:hello_bevy) bazel-out/darwin_arm64-opt-exec-2B5CBBC6/bin/external/rules_rust/util/process_wrapper/process_wrapper --arg-file ... (remaining 312 arguments skipped) error: proc-macro derive panicked --> scratch/hello_bevy.rs:5:10 | 5 | #[derive(Component)] | ^^^^^^^^^ | = help: message: Unable to read cargo manifest: /private/var/tmp/_bazel_photex/135f23dc56826c24d6c3c9f6b688b2fe/execroot/__main__/scratch/Cargo.toml: Os { code: 2, kind: NotFound, message: "No such file or directory" } error: proc-macro derive panicked --> scratch/hello_bevy.rs:8:10 | 8 | #[derive(Component)] | ^^^^^^^^^ | = help: message: Unable to read cargo manifest: /private/var/tmp/_bazel_photex/135f23dc56826c24d6c3c9f6b688b2fe/execroot/__main__/scratch/Cargo.toml: Os { code: 2, kind: NotFound, message: "No such file or directory" } ``` Co-authored-by: Chip Collier --- crates/bevy_macro_utils/src/lib.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 6b994ff9ba..59e224d511 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -28,10 +28,20 @@ impl Default for BevyManifest { .map(PathBuf::from) .map(|mut path| { path.push("Cargo.toml"); - let manifest = std::fs::read_to_string(path).unwrap(); - manifest.parse::().unwrap() + if !path.exists() { + panic!( + "No Cargo manifest found for crate. Expected: {}", + path.display() + ); + } + let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { + panic!("Unable to read cargo manifest: {}", path.display()) + }); + manifest.parse::().unwrap_or_else(|_| { + panic!("Failed to parse cargo manifest: {}", path.display()) + }) }) - .unwrap(), + .expect("CARGO_MANIFEST_DIR is not defined."), } } } From b7cda3293f865ed5df15099f2a45f64a60b857a2 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Thu, 20 Jul 2023 19:20:34 +0200 Subject: [PATCH 05/26] Fix path reference to contributors example (#9219) # Objective Fix in incorrect reference to another example. ## Solution Fix the reference. :-) --- examples/2d/text2d.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index 8fe1b4b05b..ad783173d1 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -3,7 +3,7 @@ //! Note that this uses [`Text2dBundle`] to display text alongside your other entities in a 2D scene. //! //! For an example on how to render text as part of a user interface, independent from the world -//! viewport, you may want to look at `2d/contributors.rs` or `ui/text.rs`. +//! viewport, you may want to look at `games/contributors.rs` or `ui/text.rs`. use bevy::{ prelude::*, From 9b92de9e350d9c100f08dd732c7917ea7d872eb0 Mon Sep 17 00:00:00 2001 From: Sludge <96552222+SludgePhD@users.noreply.github.com> Date: Thu, 20 Jul 2023 23:26:03 +0200 Subject: [PATCH 06/26] Register `AlphaMode` type (#9222) # Objective - `AlphaMode` derives `Reflect`, but wasn't registered with the app and type registry ## Solution - `app.register_type::()` --- crates/bevy_pbr/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 2460a0e0f6..32dee04f34 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -166,6 +166,7 @@ impl Plugin for PbrPlugin { ); app.register_asset_reflect::() + .register_type::() .register_type::() .register_type::() .register_type::() From 264195ed772e1fc964c34b6f1e64a476805e1766 Mon Sep 17 00:00:00 2001 From: Ryan Devenney Date: Thu, 20 Jul 2023 17:41:07 -0400 Subject: [PATCH 07/26] replace parens with square brackets when referencing _mut on `Query` docs #9200 (#9223) # Objective Fixes #9200 Switches ()'s to []'s when talking about the optional `_mut` suffix in the ECS Query Struct page to have more idiomatic docs. ## Solution Replace `()` with `[]` in appropriate doc pages. --- crates/bevy_ecs/src/system/query.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 50ea3fde79..2cf3273fa7 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -235,13 +235,13 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug}; /// /// |Query methods|Effect| /// |:---:|---| -/// |[`iter`]\([`_mut`][`iter_mut`])|Returns an iterator over all query items.| -/// |[`for_each`]\([`_mut`][`for_each_mut`]),
[`par_iter`]\([`_mut`][`par_iter_mut`])|Runs a specified function for each query item.| -/// |[`iter_many`]\([`_mut`][`iter_many_mut`])|Iterates or runs a specified function over query items generated by a list of entities.| -/// |[`iter_combinations`]\([`_mut`][`iter_combinations_mut`])|Returns an iterator over all combinations of a specified number of query items.| -/// |[`get`]\([`_mut`][`get_mut`])|Returns the query item for the specified entity.| -/// |[`many`]\([`_mut`][`many_mut`]),
[`get_many`]\([`_mut`][`get_many_mut`])|Returns the query items for the specified entities.| -/// |[`single`]\([`_mut`][`single_mut`]),
[`get_single`]\([`_mut`][`get_single_mut`])|Returns the query item while verifying that there aren't others.| +/// |[`iter`]\[[`_mut`][`iter_mut`]]|Returns an iterator over all query items.| +/// |[`for_each`]\[[`_mut`][`for_each_mut`]],
[`par_iter`]\[[`_mut`][`par_iter_mut`]]|Runs a specified function for each query item.| +/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|Iterates or runs a specified function over query items generated by a list of entities.| +/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|Returns an iterator over all combinations of a specified number of query items.| +/// |[`get`]\[[`_mut`][`get_mut`]]|Returns the query item for the specified entity.| +/// |[`many`]\[[`_mut`][`many_mut`]],
[`get_many`]\[[`_mut`][`get_many_mut`]]|Returns the query items for the specified entities.| +/// |[`single`]\[[`_mut`][`single_mut`]],
[`get_single`]\[[`_mut`][`get_single_mut`]]|Returns the query item while verifying that there aren't others.| /// /// There are two methods for each type of query operation: immutable and mutable (ending with `_mut`). /// When using immutable methods, the query items returned are of type [`ROQueryItem`], a read-only version of the query item. @@ -271,14 +271,14 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug}; /// /// |Query operation|Computational complexity| /// |:---:|:---:| -/// |[`iter`]\([`_mut`][`iter_mut`])|O(n)| -/// |[`for_each`]\([`_mut`][`for_each_mut`]),
[`par_iter`]\([`_mut`][`par_iter_mut`])|O(n)| -/// |[`iter_many`]\([`_mut`][`iter_many_mut`])|O(k)| -/// |[`iter_combinations`]\([`_mut`][`iter_combinations_mut`])|O(nCr)| -/// |[`get`]\([`_mut`][`get_mut`])|O(1)| +/// |[`iter`]\[[`_mut`][`iter_mut`]]|O(n)| +/// |[`for_each`]\[[`_mut`][`for_each_mut`]],
[`par_iter`]\[[`_mut`][`par_iter_mut`]]|O(n)| +/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|O(k)| +/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|O(nCr)| +/// |[`get`]\[[`_mut`][`get_mut`]]|O(1)| /// |([`get_`][`get_many`])[`many`]|O(k)| /// |([`get_`][`get_many_mut`])[`many_mut`]|O(k2)| -/// |[`single`]\([`_mut`][`single_mut`]),
[`get_single`]\([`_mut`][`get_single_mut`])|O(a)| +/// |[`single`]\[[`_mut`][`single_mut`]],
[`get_single`]\[[`_mut`][`get_single_mut`]]|O(a)| /// |Archetype based filtering ([`With`], [`Without`], [`Or`])|O(a)| /// |Change detection filtering ([`Added`], [`Changed`])|O(a + n)| /// From ad011d04552bd6015f9aae2f5f2e3d3ef53c6f77 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Jul 2023 09:46:56 -0700 Subject: [PATCH 08/26] Add GpuArrayBuffer and BatchedUniformBuffer (#8204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Add a type for uploading a Rust `Vec` to a GPU `array`. - Makes progress towards https://github.com/bevyengine/bevy/issues/89. ## Solution - Port @superdump's `BatchedUniformBuffer` to bevy main, as a fallback for WebGL2, which doesn't support storage buffers. - Rather than getting an `array` in a shader, you get an `array`, and have to rebind every N elements via dynamic offsets. - Add `GpuArrayBuffer` to abstract over `StorageBuffer>`/`BatchedUniformBuffer`. ## Future Work Add a shader macro kinda thing to abstract over the following automatically: https://github.com/bevyengine/bevy/pull/8204#pullrequestreview-1396911727 --- ## Changelog * Added `GpuArrayBuffer`, `GpuComponentArrayBufferPlugin`, `GpuArrayBufferable`, and `GpuArrayBufferIndex` types. * Added `DynamicUniformBuffer::new_with_alignment()`. --------- Co-authored-by: Robert Swain Co-authored-by: François Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com> Co-authored-by: IceSentry Co-authored-by: Vincent <9408210+konsolas@users.noreply.github.com> Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com> --- .../src/gpu_component_array_buffer.rs | 55 +++++++ crates/bevy_render/src/lib.rs | 1 + .../render_resource/batched_uniform_buffer.rs | 152 ++++++++++++++++++ .../src/render_resource/buffer_vec.rs | 2 + .../src/render_resource/gpu_array_buffer.rs | 129 +++++++++++++++ crates/bevy_render/src/render_resource/mod.rs | 3 + .../src/render_resource/storage_buffer.rs | 2 + .../src/render_resource/uniform_buffer.rs | 16 +- 8 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_render/src/gpu_component_array_buffer.rs create mode 100644 crates/bevy_render/src/render_resource/batched_uniform_buffer.rs create mode 100644 crates/bevy_render/src/render_resource/gpu_array_buffer.rs diff --git a/crates/bevy_render/src/gpu_component_array_buffer.rs b/crates/bevy_render/src/gpu_component_array_buffer.rs new file mode 100644 index 0000000000..6076049c7f --- /dev/null +++ b/crates/bevy_render/src/gpu_component_array_buffer.rs @@ -0,0 +1,55 @@ +use crate::{ + render_resource::{GpuArrayBuffer, GpuArrayBufferable}, + renderer::{RenderDevice, RenderQueue}, + Render, RenderApp, RenderSet, +}; +use bevy_app::{App, Plugin}; +use bevy_ecs::{ + prelude::{Component, Entity}, + schedule::IntoSystemConfigs, + system::{Commands, Query, Res, ResMut}, +}; +use std::marker::PhantomData; + +/// This plugin prepares the components of the corresponding type for the GPU +/// by storing them in a [`GpuArrayBuffer`]. +pub struct GpuComponentArrayBufferPlugin(PhantomData); + +impl Plugin for GpuComponentArrayBufferPlugin { + fn build(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .insert_resource(GpuArrayBuffer::::new( + render_app.world.resource::(), + )) + .add_systems( + Render, + prepare_gpu_component_array_buffers::.in_set(RenderSet::Prepare), + ); + } + } +} + +impl Default for GpuComponentArrayBufferPlugin { + fn default() -> Self { + Self(PhantomData::) + } +} + +fn prepare_gpu_component_array_buffers( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut gpu_array_buffer: ResMut>, + components: Query<(Entity, &C)>, +) { + gpu_array_buffer.clear(); + + let entities = components + .iter() + .map(|(entity, component)| (entity, gpu_array_buffer.push(component.clone()))) + .collect::>(); + commands.insert_or_spawn_batch(entities); + + gpu_array_buffer.write_buffer(&render_device, &render_queue); +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 61e814e9bc..62be6fe3ad 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -11,6 +11,7 @@ pub mod extract_component; mod extract_param; pub mod extract_resource; pub mod globals; +pub mod gpu_component_array_buffer; pub mod mesh; pub mod pipelined_rendering; pub mod primitives; diff --git a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs new file mode 100644 index 0000000000..a9fba2ac7f --- /dev/null +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -0,0 +1,152 @@ +use super::{GpuArrayBufferIndex, GpuArrayBufferable}; +use crate::{ + render_resource::DynamicUniformBuffer, + renderer::{RenderDevice, RenderQueue}, +}; +use encase::{ + private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer}, + ShaderType, +}; +use std::{marker::PhantomData, num::NonZeroU64}; +use wgpu::{BindingResource, Limits}; + +// 1MB else we will make really large arrays on macOS which reports very large +// `max_uniform_buffer_binding_size`. On macOS this ends up being the minimum +// size of the uniform buffer as well as the size of each chunk of data at a +// dynamic offset. +#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))] +const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 20; + +// WebGL2 quirk: using uniform buffers larger than 4KB will cause extremely +// long shader compilation times, so the limit needs to be lower on WebGL2. +// This is due to older shader compilers/GPUs that don't support dynamically +// indexing uniform buffers, and instead emulate it with large switch statements +// over buffer indices that take a long time to compile. +#[cfg(all(feature = "webgl", target_arch = "wasm32"))] +const MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE: u32 = 1 << 12; + +/// Similar to [`DynamicUniformBuffer`], except every N elements (depending on size) +/// are grouped into a batch as an `array` in WGSL. +/// +/// This reduces the number of rebindings required due to having to pass dynamic +/// offsets to bind group commands, and if indices into the array can be passed +/// in via other means, it enables batching of draw commands. +pub struct BatchedUniformBuffer { + // Batches of fixed-size arrays of T are written to this buffer so that + // each batch in a fixed-size array can be bound at a dynamic offset. + uniforms: DynamicUniformBuffer>>, + // A batch of T are gathered into this `MaxCapacityArray` until it is full, + // then it is written into the `DynamicUniformBuffer`, cleared, and new T + // are gathered here, and so on for each batch. + temp: MaxCapacityArray>, + current_offset: u32, + dynamic_offset_alignment: u32, +} + +impl BatchedUniformBuffer { + pub fn batch_size(limits: &Limits) -> usize { + (limits + .max_uniform_buffer_binding_size + .min(MAX_REASONABLE_UNIFORM_BUFFER_BINDING_SIZE) as u64 + / T::min_size().get()) as usize + } + + pub fn new(limits: &Limits) -> Self { + let capacity = Self::batch_size(limits); + let alignment = limits.min_uniform_buffer_offset_alignment; + + Self { + uniforms: DynamicUniformBuffer::new_with_alignment(alignment as u64), + temp: MaxCapacityArray(Vec::with_capacity(capacity), capacity), + current_offset: 0, + dynamic_offset_alignment: alignment, + } + } + + #[inline] + pub fn size(&self) -> NonZeroU64 { + self.temp.size() + } + + pub fn clear(&mut self) { + self.uniforms.clear(); + self.current_offset = 0; + self.temp.0.clear(); + } + + pub fn push(&mut self, component: T) -> GpuArrayBufferIndex { + let result = GpuArrayBufferIndex { + index: self.temp.0.len() as u32, + dynamic_offset: Some(self.current_offset), + element_type: PhantomData, + }; + self.temp.0.push(component); + if self.temp.0.len() == self.temp.1 { + self.flush(); + } + result + } + + pub fn flush(&mut self) { + self.uniforms.push(self.temp.clone()); + + self.current_offset += + align_to_next(self.temp.size().get(), self.dynamic_offset_alignment as u64) as u32; + + self.temp.0.clear(); + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + if !self.temp.0.is_empty() { + self.flush(); + } + self.uniforms.write_buffer(device, queue); + } + + #[inline] + pub fn binding(&self) -> Option { + let mut binding = self.uniforms.binding(); + if let Some(BindingResource::Buffer(binding)) = &mut binding { + // MaxCapacityArray is runtime-sized so can't use T::min_size() + binding.size = Some(self.size()); + } + binding + } +} + +#[inline] +fn align_to_next(value: u64, alignment: u64) -> u64 { + debug_assert!(alignment & (alignment - 1) == 0); + ((value - 1) | (alignment - 1)) + 1 +} + +// ---------------------------------------------------------------------------- +// MaxCapacityArray was implemented by Teodor Tanasoaia for encase. It was +// copied here as it was not yet included in an encase release and it is +// unclear if it is the correct long-term solution for encase. + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct MaxCapacityArray(T, usize); + +impl ShaderType for MaxCapacityArray +where + T: ShaderType, +{ + type ExtraMetadata = ArrayMetadata; + + const METADATA: Metadata = T::METADATA; + + fn size(&self) -> ::core::num::NonZeroU64 { + Self::METADATA.stride().mul(self.1.max(1) as u64).0 + } +} + +impl WriteInto for MaxCapacityArray +where + T: WriteInto + RuntimeSizedArray, +{ + fn write_into(&self, writer: &mut Writer) { + debug_assert!(self.0.len() <= self.1); + self.0.write_into(writer); + } +} diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 07440a27e9..002e8f8bd3 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -21,9 +21,11 @@ use wgpu::BufferUsages; /// from system RAM to VRAM. /// /// Other options for storing GPU-accessible data are: +/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) pub struct BufferVec { diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs new file mode 100644 index 0000000000..45eaba4f73 --- /dev/null +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -0,0 +1,129 @@ +use super::StorageBuffer; +use crate::{ + render_resource::batched_uniform_buffer::BatchedUniformBuffer, + renderer::{RenderDevice, RenderQueue}, +}; +use bevy_ecs::{prelude::Component, system::Resource}; +use encase::{private::WriteInto, ShaderSize, ShaderType}; +use std::{marker::PhantomData, mem}; +use wgpu::{BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ShaderStages}; + +/// Trait for types able to go in a [`GpuArrayBuffer`]. +pub trait GpuArrayBufferable: ShaderType + ShaderSize + WriteInto + Clone {} +impl GpuArrayBufferable for T {} + +/// Stores an array of elements to be transferred to the GPU and made accessible to shaders as a read-only array. +/// +/// On platforms that support storage buffers, this is equivalent to [`StorageBuffer>`]. +/// Otherwise, this falls back to a dynamic offset uniform buffer with the largest +/// array of T that fits within a uniform buffer binding (within reasonable limits). +/// +/// Other options for storing GPU-accessible data are: +/// * [`StorageBuffer`](crate::render_resource::StorageBuffer) +/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) +/// * [`UniformBuffer`](crate::render_resource::UniformBuffer) +/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`BufferVec`](crate::render_resource::BufferVec) +/// * [`Texture`](crate::render_resource::Texture) +#[derive(Resource)] +pub enum GpuArrayBuffer { + Uniform(BatchedUniformBuffer), + Storage((StorageBuffer>, Vec)), +} + +impl GpuArrayBuffer { + pub fn new(device: &RenderDevice) -> Self { + let limits = device.limits(); + if limits.max_storage_buffers_per_shader_stage == 0 { + GpuArrayBuffer::Uniform(BatchedUniformBuffer::new(&limits)) + } else { + GpuArrayBuffer::Storage((StorageBuffer::default(), Vec::new())) + } + } + + pub fn clear(&mut self) { + match self { + GpuArrayBuffer::Uniform(buffer) => buffer.clear(), + GpuArrayBuffer::Storage((_, buffer)) => buffer.clear(), + } + } + + pub fn push(&mut self, value: T) -> GpuArrayBufferIndex { + match self { + GpuArrayBuffer::Uniform(buffer) => buffer.push(value), + GpuArrayBuffer::Storage((_, buffer)) => { + let index = buffer.len() as u32; + buffer.push(value); + GpuArrayBufferIndex { + index, + dynamic_offset: None, + element_type: PhantomData, + } + } + } + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + match self { + GpuArrayBuffer::Uniform(buffer) => buffer.write_buffer(device, queue), + GpuArrayBuffer::Storage((buffer, vec)) => { + buffer.set(mem::take(vec)); + buffer.write_buffer(device, queue); + } + } + } + + pub fn binding_layout( + binding: u32, + visibility: ShaderStages, + device: &RenderDevice, + ) -> BindGroupLayoutEntry { + BindGroupLayoutEntry { + binding, + visibility, + ty: if device.limits().max_storage_buffers_per_shader_stage == 0 { + BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // BatchedUniformBuffer uses a MaxCapacityArray that is runtime-sized, so we use + // None here and let wgpu figure out the size. + min_binding_size: None, + } + } else { + BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: Some(T::min_size()), + } + }, + count: None, + } + } + + pub fn binding(&self) -> Option { + match self { + GpuArrayBuffer::Uniform(buffer) => buffer.binding(), + GpuArrayBuffer::Storage((buffer, _)) => buffer.binding(), + } + } + + pub fn batch_size(device: &RenderDevice) -> Option { + let limits = device.limits(); + if limits.max_storage_buffers_per_shader_stage == 0 { + Some(BatchedUniformBuffer::::batch_size(&limits) as u32) + } else { + None + } + } +} + +/// An index into a [`GpuArrayBuffer`] for a given element. +#[derive(Component)] +pub struct GpuArrayBufferIndex { + /// The index to use in a shader into the array. + pub index: u32, + /// The dynamic offset to use when setting the bind group in a pass. + /// Only used on platforms that don't support storage buffers. + pub dynamic_offset: Option, + pub element_type: PhantomData, +} diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 91440cf55c..f16f5f1269 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -1,7 +1,9 @@ +mod batched_uniform_buffer; mod bind_group; mod bind_group_layout; mod buffer; mod buffer_vec; +mod gpu_array_buffer; mod pipeline; mod pipeline_cache; mod pipeline_specializer; @@ -15,6 +17,7 @@ pub use bind_group::*; pub use bind_group_layout::*; pub use buffer::*; pub use buffer_vec::*; +pub use gpu_array_buffer::*; pub use pipeline::*; pub use pipeline_cache::*; pub use pipeline_specializer::*; diff --git a/crates/bevy_render/src/render_resource/storage_buffer.rs b/crates/bevy_render/src/render_resource/storage_buffer.rs index 26d3797ede..2c73b322d7 100644 --- a/crates/bevy_render/src/render_resource/storage_buffer.rs +++ b/crates/bevy_render/src/render_resource/storage_buffer.rs @@ -25,6 +25,7 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// @@ -154,6 +155,7 @@ impl StorageBuffer { /// * [`StorageBuffer`](crate::render_resource::StorageBuffer) /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index 137432c062..4c1ad61b2a 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -22,9 +22,10 @@ use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsa /// (vectors), or structures with fields that are vectors. /// /// Other options for storing GPU-accessible data are: -/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) /// * [`StorageBuffer`](crate::render_resource::StorageBuffer) /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) +/// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) /// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// @@ -151,6 +152,8 @@ impl UniformBuffer { /// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer) /// * [`UniformBuffer`](crate::render_resource::UniformBuffer) /// * [`DynamicUniformBuffer`](crate::render_resource::DynamicUniformBuffer) +/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer) +/// * [`BufferVec`](crate::render_resource::BufferVec) /// * [`Texture`](crate::render_resource::Texture) /// /// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform @@ -177,6 +180,17 @@ impl Default for DynamicUniformBuffer { } impl DynamicUniformBuffer { + pub fn new_with_alignment(alignment: u64) -> Self { + Self { + scratch: DynamicUniformBufferWrapper::new_with_alignment(Vec::new(), alignment), + buffer: None, + label: None, + changed: false, + buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, + _marker: PhantomData, + } + } + #[inline] pub fn buffer(&self) -> Option<&Buffer> { self.buffer.as_ref() From 6093385b31851bc0758f38d46125c8446d0d9800 Mon Sep 17 00:00:00 2001 From: VitalyR Date: Sat, 22 Jul 2023 02:40:08 +0800 Subject: [PATCH 09/26] Update `bevy_window::PresentMode` to mirror `wgpu::PresentMode` (#9230) # Objective - Update `bevy_window::PresentMode` to mirror `wgpu::PresentMode`, Fixes #9151. ## Solution Add `bevy_window::PresentMode::FifoRelaxed` to `bevy_window::PresentMode`, add documents. --- ## Changelog ### Added - Add `bevy_window::PresentMode::FifoRelaxed` to `bevy_window::PresentMode`. ## Migration Guide - Handle `bevy_window::PresentMode::FifoRelaxed` when tweaking window present mode manually. --- crates/bevy_render/src/view/window.rs | 1 + crates/bevy_window/src/window.rs | 72 ++++++++++++++++++++------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index e76097004a..b8e77f5197 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -266,6 +266,7 @@ pub fn prepare_windows( usage: wgpu::TextureUsages::RENDER_ATTACHMENT, present_mode: match window.present_mode { PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed, PresentMode::Mailbox => wgpu::PresentMode::Mailbox, PresentMode::Immediate => wgpu::PresentMode::Immediate, PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index b513575184..8c310a8aee 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -801,6 +801,7 @@ pub enum MonitorSelection { /// [`Immediate`] or [`Mailbox`] will panic if not supported by the platform. /// /// [`Fifo`]: PresentMode::Fifo +/// [`FifoRelaxed`]: PresentMode::FifoRelaxed /// [`Immediate`]: PresentMode::Immediate /// [`Mailbox`]: PresentMode::Mailbox /// [`AutoVsync`]: PresentMode::AutoVsync @@ -819,30 +820,67 @@ pub enum PresentMode { /// Chooses FifoRelaxed -> Fifo based on availability. /// /// Because of the fallback behavior, it is supported everywhere. - AutoVsync = 0, + AutoVsync = 0, // NOTE: The explicit ordinal values mirror wgpu. /// Chooses Immediate -> Mailbox -> Fifo (on web) based on availability. /// /// Because of the fallback behavior, it is supported everywhere. AutoNoVsync = 1, - /// The presentation engine does **not** wait for a vertical blanking period and - /// the request is presented immediately. This is a low-latency presentation mode, - /// but visible tearing may be observed. Not optimal for mobile. + /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames + /// long. Every vertical blanking period, the presentation engine will pop a frame + /// off the queue to display. If there is no frame to display, it will present the same + /// frame again until the next vblank. /// - /// Selecting this variant will panic if not supported, it is preferred to use - /// [`PresentMode::AutoNoVsync`]. - Immediate = 2, - /// The presentation engine waits for the next vertical blanking period to update - /// the current image, but frames may be submitted without delay. This is a low-latency - /// presentation mode and visible tearing will **not** be observed. Not optimal for mobile. + /// When a present command is executed on the gpu, the presented image is added on the queue. /// - /// Selecting this variant will panic if not supported, it is preferred to use - /// [`PresentMode::AutoNoVsync`]. - Mailbox = 3, - /// The presentation engine waits for the next vertical blanking period to update - /// the current image. The framerate will be capped at the display refresh rate, - /// corresponding to the `VSync`. Tearing cannot be observed. Optimal for mobile. + /// No tearing will be observed. + /// + /// Calls to get_current_texture will block until there is a spot in the queue. + /// + /// Supported on all platforms. + /// + /// If you don't know what mode to choose, choose this mode. This is traditionally called "Vsync On". #[default] - Fifo = 4, // NOTE: The explicit ordinal values mirror wgpu. + Fifo = 2, + /// Presentation frames are kept in a First-In-First-Out queue approximately 3 frames + /// long. Every vertical blanking period, the presentation engine will pop a frame + /// off the queue to display. If there is no frame to display, it will present the + /// same frame until there is a frame in the queue. The moment there is a frame in the + /// queue, it will immediately pop the frame off the queue. + /// + /// When a present command is executed on the gpu, the presented image is added on the queue. + /// + /// Tearing will be observed if frames last more than one vblank as the front buffer. + /// + /// Calls to get_current_texture will block until there is a spot in the queue. + /// + /// Supported on AMD on Vulkan. + /// + /// This is traditionally called "Adaptive Vsync" + FifoRelaxed = 3, + /// Presentation frames are not queued at all. The moment a present command + /// is executed on the GPU, the presented image is swapped onto the front buffer + /// immediately. + /// + /// Tearing can be observed. + /// + /// Supported on most platforms except older DX12 and Wayland. + /// + /// This is traditionally called "Vsync Off". + Immediate = 4, + /// Presentation frames are kept in a single-frame queue. Every vertical blanking period, + /// the presentation engine will pop a frame from the queue. If there is no frame to display, + /// it will present the same frame again until the next vblank. + /// + /// When a present command is executed on the gpu, the frame will be put into the queue. + /// If there was already a frame in the queue, the new frame will _replace_ the old frame + /// on the queue. + /// + /// No tearing will be observed. + /// + /// Supported on DX11/12 on Windows 10, NVidia on Vulkan and Wayland on Vulkan. + /// + /// This is traditionally called "Fast Vsync" + Mailbox = 5, } /// Specifies how the alpha channel of the textures should be handled during compositing, for a [`Window`]. From cd92405dbd2f5c4f4b955073a9717fa8b503c79c Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Fri, 21 Jul 2023 22:12:38 +0200 Subject: [PATCH 10/26] Replace AHash with a good sequence for entity AABB colors (#9175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - #8960 isn't optimal for very distinct AABB colors, it can be improved ## Solution We want a function that maps sequential values (entities concurrently living in a scene _usually_ have ids that are sequential) into very different colors (the hue component of the color, to be specific) What we are looking for is a [so-called "low discrepancy" sequence](https://en.wikipedia.org/wiki/Low-discrepancy_sequence). ie: a function `f` such as for integers in a given range (eg: 101, 102, 103…), `f(i)` returns a rational number in the [0..1] range, such as `|f(i) - f(i±1)| ≈ 0.5` (maximum difference of images for neighboring preimages) AHash is a good random hasher, but it has relatively high discrepancy, so we need something else. Known good low discrepancy sequences are: #### The [Van Der Corput sequence](https://en.wikipedia.org/wiki/Van_der_Corput_sequence)
Rust implementation ```rust fn van_der_corput(bits: u64) -> f32 { let leading_zeros = if bits == 0 { 0 } else { bits.leading_zeros() }; let nominator = bits.reverse_bits() >> leading_zeros; let denominator = bits.next_power_of_two(); nominator as f32 / denominator as f32 } ```
#### The [Gold Kronecker sequence](https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/)
Rust implementation Note that the implementation suggested in the linked post assumes floats, we have integers ```rust fn gold_kronecker(bits: u64) -> f32 { const U64_MAX_F: f32 = u64::MAX as f32; // (u64::MAX / Φ) rounded down const FRAC_U64MAX_GOLDEN_RATIO: u64 = 11400714819323198485; bits.wrapping_mul(FRAC_U64MAX_GOLDEN_RATIO) as f32 / U64_MAX_F } ```
### Comparison of the sequences So they are both pretty good. Both only have a single (!) division and two `u32 as f32` conversions. - Kronecker is resilient to regular sequence (eg: 100, 102, 104, 106) while this kills Van Der Corput (consider that potentially one entity out of two spawned might be a mesh) I made a small app to compare the two sequences, available at: https://gist.github.com/nicopap/5dd9bd6700c6a9a9cf90c9199941883e At the top, we have Van Der Corput, at the bottom we have the Gold Kronecker. In the video, we spawn a vertical line at the position on screen where the x coordinate is the image of the sequence. The preimages are 1,2,3,4,… The ideal algorithm would always have the largest possible gap between each line (imagine the screen x coordinate as the color hue): https://github.com/bevyengine/bevy/assets/26321040/349aa8f8-f669-43ba-9842-f9a46945e25c Here, we repeat the experiment, but with with `entity.to_bits()` instead of a sequence: https://github.com/bevyengine/bevy/assets/26321040/516cea27-7135-4daa-a4e7-edfd1781d119 Notice how Van Der Corput tend to bunch the lines on a single side of the screen. This is because we always skip odd-numbered entities. Gold Kronecker seems always worse than Van Der Corput, but it is resilient to finicky stuff like entity indices being multiples of a number rather than purely sequential, so I prefer it over Van Der Corput, since we can't really predict how distributed the entity indices will be. ### Chosen implementation You'll notice this PR's implementation is not the Golden ratio-based Kronecker sequence as described in [tueoqs](https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/). Why? tueoqs R function multiplies a rational/float and takes the fractional part of the result `(x/Φ) % 1`. We start with an integer `u32`. So instead of converting into float and dividing by Φ (mod 1) we directly divide by Φ as integer (mod 2³²) both operations are equivalent, the integer division (which is actually a multiplication by `u32::MAX / Φ`) is probably faster. ## Acknowledgements - `inspi` on discord linked me to https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ and the wikipedia article. - [this blog post](https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/) for the idea of multiplying the `u32` rather than the `f32`. - `nakedible` for suggesting the `index()` over `to_bits()` which considerably reduces generated code (goes from 50 to 11 instructions) --- crates/bevy_gizmos/src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 15e9965109..3faf6c6305 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -233,13 +233,17 @@ fn draw_all_aabbs( } fn color_from_entity(entity: Entity) -> Color { - use bevy_utils::RandomState; - const U64_TO_DEGREES: f32 = 360.0 / u64::MAX as f32; - const STATE: RandomState = - RandomState::with_seeds(5952553601252303067, 16866614500153072625, 0, 0); + let index = entity.index(); + + // from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ + // + // See https://en.wikipedia.org/wiki/Low-discrepancy_sequence + // Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range, + // so that the closer the numbers are, the larger the difference of their image. + const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up + const RATIO_360: f32 = 360.0 / u32::MAX as f32; + let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360; - let hash = STATE.hash_one(entity); - let hue = hash as f32 * U64_TO_DEGREES; Color::hsl(hue, 1., 0.5) } From eb485b1acc619baaae88d5daca0a311b95886281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 21 Jul 2023 22:15:13 +0200 Subject: [PATCH 11/26] use AutoNoVsync in stress tests (#9229) # Objective - Some stress tests use `Immediate` which is not supported everywhere ## Solution - Use `AutoNoVsync` instead --- examples/stress_tests/many_glyphs.rs | 2 +- examples/stress_tests/text_pipeline.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/stress_tests/many_glyphs.rs b/examples/stress_tests/many_glyphs.rs index 7e5899a7c2..0b095c170b 100644 --- a/examples/stress_tests/many_glyphs.rs +++ b/examples/stress_tests/many_glyphs.rs @@ -17,7 +17,7 @@ fn main() { app.add_plugins(( DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { - present_mode: PresentMode::Immediate, + present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() diff --git a/examples/stress_tests/text_pipeline.rs b/examples/stress_tests/text_pipeline.rs index cd0e77a9b1..da31326005 100644 --- a/examples/stress_tests/text_pipeline.rs +++ b/examples/stress_tests/text_pipeline.rs @@ -14,7 +14,7 @@ fn main() { .add_plugins(( DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { - present_mode: PresentMode::Immediate, + present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() From 593bebf930f88863a9fc763fc242828a40067d91 Mon Sep 17 00:00:00 2001 From: William Pederzoli Date: Sun, 23 Jul 2023 03:01:45 +0200 Subject: [PATCH 12/26] gizmo plugin lag bugfix (#9166) # Objective Fixes #9156 --- crates/bevy_gizmos/src/lib.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 3faf6c6305..34c1ce4262 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -18,7 +18,7 @@ use std::mem; -use bevy_app::{Last, Plugin, Update}; +use bevy_app::{Last, Plugin, PostUpdate}; use bevy_asset::{load_internal_asset, AddAsset, Assets, Handle, HandleUntyped}; use bevy_core::cast_slice; use bevy_ecs::{ @@ -50,7 +50,10 @@ use bevy_render::{ view::RenderLayers, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_transform::components::{GlobalTransform, Transform}; +use bevy_transform::{ + components::{GlobalTransform, Transform}, + TransformSystem, +}; pub mod gizmos; @@ -85,11 +88,12 @@ impl Plugin for GizmoPlugin { .init_resource::() .add_systems(Last, update_gizmo_meshes) .add_systems( - Update, + PostUpdate, ( draw_aabbs, draw_all_aabbs.run_if(|config: Res| config.aabb.draw_all), - ), + ) + .after(TransformSystem::TransformPropagate), ); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; From bc8e2746d73b30577bc1a7eb49e22c5032c4070c Mon Sep 17 00:00:00 2001 From: Arnav Mummineni <45217840+RCoder01@users.noreply.github.com> Date: Sat, 22 Jul 2023 21:02:00 -0400 Subject: [PATCH 13/26] Add reflect impls to IRect and URect (#9191) # Objective This attempts to make the new IRect and URect structs in bevy_math more similar to the existing Rect struct. ## Solution Add reflect implementations for IRect and URect, since one already exists for Rect. --- crates/bevy_math/src/rects/irect.rs | 2 +- crates/bevy_math/src/rects/urect.rs | 2 +- crates/bevy_reflect/src/impls/rect.rs | 20 +++++++++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/rects/irect.rs b/crates/bevy_math/src/rects/irect.rs index 05498cea15..b8a1b21a79 100644 --- a/crates/bevy_math/src/rects/irect.rs +++ b/crates/bevy_math/src/rects/irect.rs @@ -9,7 +9,7 @@ use crate::{IVec2, Rect, URect}; /// methods instead, which will ensure this invariant is met, unless you already have /// the minimum and maximum corners. #[repr(C)] -#[derive(Default, Clone, Copy, Debug, PartialEq)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct IRect { /// The minimum corner point of the rect. diff --git a/crates/bevy_math/src/rects/urect.rs b/crates/bevy_math/src/rects/urect.rs index 1f04882d16..fb74a6183e 100644 --- a/crates/bevy_math/src/rects/urect.rs +++ b/crates/bevy_math/src/rects/urect.rs @@ -9,7 +9,7 @@ use crate::{IRect, Rect, UVec2}; /// methods instead, which will ensure this invariant is met, unless you already have /// the minimum and maximum corners. #[repr(C)] -#[derive(Default, Clone, Copy, Debug, PartialEq)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct URect { /// The minimum corner point of the rect. diff --git a/crates/bevy_reflect/src/impls/rect.rs b/crates/bevy_reflect/src/impls/rect.rs index b215a58599..8f882eab27 100644 --- a/crates/bevy_reflect/src/impls/rect.rs +++ b/crates/bevy_reflect/src/impls/rect.rs @@ -1,9 +1,18 @@ use crate as bevy_reflect; use crate::prelude::ReflectDefault; use crate::{ReflectDeserialize, ReflectSerialize}; -use bevy_math::{Rect, Vec2}; +use bevy_math::{IRect, IVec2, Rect, URect, UVec2, Vec2}; use bevy_reflect_derive::impl_reflect_struct; +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)] + #[type_path = "bevy_math"] + struct IRect { + min: IVec2, + max: IVec2, + } +); + impl_reflect_struct!( #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] #[type_path = "bevy_math"] @@ -12,3 +21,12 @@ impl_reflect_struct!( max: Vec2, } ); + +impl_reflect_struct!( + #[reflect(Debug, PartialEq, Hash, Serialize, Deserialize, Default)] + #[type_path = "bevy_math"] + struct URect { + min: UVec2, + max: UVec2, + } +); From 453bd058fe3c172cf2a8090ccda5408ef9cbc6e6 Mon Sep 17 00:00:00 2001 From: 0xc0001a2040 Date: Sun, 23 Jul 2023 03:02:20 +0200 Subject: [PATCH 14/26] Add `track_caller` to `App::add_plugins` (#9174) # Objective Currently the panic message if a duplicate plugin is added isn't really helpful or at least can be made more useful if it includes the location where the plugin was added a second time. ## Solution Add `track_caller` to `add_plugins` and it's called dependencies. --- crates/bevy_app/src/app.rs | 1 + crates/bevy_app/src/plugin.rs | 3 +++ crates/bevy_app/src/plugin_group.rs | 1 + 3 files changed, 5 insertions(+) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9b20116ad5..39ed373a78 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -686,6 +686,7 @@ impl App { /// Panics if one of the plugins was already added to the application. /// /// [`PluginGroup`]:super::PluginGroup + #[track_caller] pub fn add_plugins(&mut self, plugins: impl Plugins) -> &mut Self { plugins.add_to_app(self); self diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 12e7d1f1c8..e20e536ca9 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -89,6 +89,7 @@ mod sealed { pub struct PluginsTupleMarker; impl Plugins for P { + #[track_caller] fn add_to_app(self, app: &mut App) { if let Err(AppError::DuplicatePlugin { plugin_name }) = app.add_boxed_plugin(Box::new(self)) @@ -101,6 +102,7 @@ mod sealed { } impl Plugins for P { + #[track_caller] fn add_to_app(self, app: &mut App) { self.build().finish(app); } @@ -113,6 +115,7 @@ mod sealed { $($plugins: Plugins<$param>),* { #[allow(non_snake_case, unused_variables)] + #[track_caller] fn add_to_app(self, app: &mut App) { let ($($plugins,)*) = self; $($plugins.add_to_app(app);)* diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index 5b512ab0a4..a299c0a4f1 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -172,6 +172,7 @@ impl PluginGroupBuilder { /// # Panics /// /// Panics if one of the plugin in the group was already added to the application. + #[track_caller] pub fn finish(mut self, app: &mut App) { for ty in &self.order { if let Some(entry) = self.plugins.remove(ty) { From 5e8ee108cb02d74bae5478f2ac743f45cc807e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksa=20Pavlovi=C4=87?= Date: Sun, 23 Jul 2023 03:02:40 +0200 Subject: [PATCH 15/26] Add option to toggle window control buttons (#9083) # Objective Implements #9082 but with an option to toggle minimize and close buttons too. ## Solution - Added an `enabled_buttons` member to the `Window` struct through which users can enable or disable specific window control buttons. --- ## Changelog - Added an `enabled_buttons` member to the `Window` struct through which users can enable or disable specific window control buttons. - Added a new system to the `window_settings` example which demonstrates the toggling functionality. --- ## Migration guide - Added an `enabled_buttons` member to the `Window` struct through which users can enable or disable specific window control buttons. --- crates/bevy_window/src/lib.rs | 3 +- crates/bevy_window/src/window.rs | 54 ++++++++++++++++++++++++-- crates/bevy_winit/src/converters.rs | 16 +++++++- crates/bevy_winit/src/system.rs | 9 ++++- crates/bevy_winit/src/winit_windows.rs | 3 +- examples/window/window_settings.rs | 30 ++++++++++++++ 6 files changed, 108 insertions(+), 7 deletions(-) diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 3ae1c083c8..853fae1516 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -141,7 +141,8 @@ impl Plugin for WindowPlugin { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::(); // Register `PathBuf` as it's used by `FileDragAndDrop` app.register_type::(); diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 8c310a8aee..579441b843 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -126,13 +126,21 @@ pub struct Window { /// Note: This does not stop the program from fullscreening/setting /// the size programmatically. pub resizable: bool, + /// Specifies which window control buttons should be enabled. + /// + /// ## Platform-specific + /// + /// **`iOS`**, **`Android`**, and the **`Web`** do not have window control buttons. + /// + /// On some **`Linux`** environments these values have no effect. + pub enabled_buttons: EnabledButtons, /// Should the window have decorations enabled? /// /// (Decorations are the minimize, maximize, and close buttons on desktop apps) /// - // ## Platform-specific - // - // **`iOS`**, **`Android`**, and the **`Web`** do not have decorations. + /// ## Platform-specific + /// + /// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations. pub decorations: bool, /// Should the window be transparent? /// @@ -221,6 +229,7 @@ impl Default for Window { ime_enabled: Default::default(), ime_position: Default::default(), resizable: true, + enabled_buttons: Default::default(), decorations: true, transparent: false, focused: true, @@ -1001,3 +1010,42 @@ pub enum WindowTheme { /// Use the dark variant. Dark, } + +/// Specifies which [`Window`] control buttons should be enabled. +/// +/// ## Platform-specific +/// +/// **`iOS`**, **`Android`**, and the **`Web`** do not have window control buttons. +/// +/// On some **`Linux`** environments these values have no effect. +#[derive(Debug, Copy, Clone, PartialEq, Reflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq, Default)] +pub struct EnabledButtons { + /// Enables the functionality of the minimize button. + pub minimize: bool, + /// Enables the functionality of the maximize button. + /// + /// macOS note: When [`Window`] `resizable` member is set to `false` + /// the maximize button will be disabled regardless of this value. + /// Additionaly, when `resizable` is set to `true` the window will + /// be maximized when its bar is double-clicked regardless of whether + /// the maximize button is enabled or not. + pub maximize: bool, + /// Enables the functionality of the close button. + pub close: bool, +} + +impl Default for EnabledButtons { + fn default() -> Self { + Self { + minimize: true, + maximize: true, + close: true, + } + } +} diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index dba71438e0..85302ecb0d 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -6,7 +6,7 @@ use bevy_input::{ ButtonState, }; use bevy_math::Vec2; -use bevy_window::{CursorIcon, WindowLevel, WindowTheme}; +use bevy_window::{CursorIcon, EnabledButtons, WindowLevel, WindowTheme}; pub fn convert_keyboard_input( keyboard_input: &winit::event::KeyboardInput, @@ -293,3 +293,17 @@ pub fn convert_window_theme(theme: WindowTheme) -> winit::window::Theme { WindowTheme::Dark => winit::window::Theme::Dark, } } + +pub fn convert_enabled_buttons(enabled_buttons: EnabledButtons) -> winit::window::WindowButtons { + let mut window_buttons = winit::window::WindowButtons::empty(); + if enabled_buttons.minimize { + window_buttons.insert(winit::window::WindowButtons::MINIMIZE); + } + if enabled_buttons.maximize { + window_buttons.insert(winit::window::WindowButtons::MAXIMIZE); + } + if enabled_buttons.close { + window_buttons.insert(winit::window::WindowButtons::CLOSE); + } + window_buttons +} diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 92a3ec2ca9..a39aa4d560 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -23,7 +23,10 @@ use winit::{ use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR}; use crate::{ accessibility::{AccessKitAdapters, WinitActionHandlers}, - converters::{self, convert_window_level, convert_window_theme, convert_winit_theme}, + converters::{ + self, convert_enabled_buttons, convert_window_level, convert_window_theme, + convert_winit_theme, + }, get_best_videomode, get_fitting_videomode, WinitWindows, }; @@ -222,6 +225,10 @@ pub(crate) fn changed_window( winit_window.set_resizable(window.resizable); } + if window.enabled_buttons != cache.window.enabled_buttons { + winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); + } + if window.resize_constraints != cache.window.resize_constraints { let constraints = window.resize_constraints.check_constraints(); let min_inner_size = LogicalSize { diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 00c5870668..0a0cd1ac01 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -18,7 +18,7 @@ use winit::{ use crate::{ accessibility::{AccessKitAdapters, WinitActionHandler, WinitActionHandlers}, - converters::{convert_window_level, convert_window_theme}, + converters::{convert_enabled_buttons, convert_window_level, convert_window_theme}, }; /// A resource which maps window entities to [`winit`] library windows. @@ -94,6 +94,7 @@ impl WinitWindows { .with_window_level(convert_window_level(window.window_level)) .with_theme(window.window_theme.map(convert_window_theme)) .with_resizable(window.resizable) + .with_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)) .with_decorations(window.decorations) .with_transparent(window.transparent); diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 158c1d9b78..3a7687ee92 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -20,6 +20,10 @@ fn main() { // Tells wasm not to override default event handling, like F5, Ctrl+R etc. prevent_default_event_handling: false, window_theme: Some(WindowTheme::Dark), + enabled_buttons: bevy::window::EnabledButtons { + maximize: false, + ..Default::default() + }, ..default() }), ..default() @@ -34,6 +38,7 @@ fn main() { toggle_theme, toggle_cursor, toggle_vsync, + toggle_window_controls, cycle_cursor_icon, switch_level, ), @@ -76,6 +81,31 @@ fn switch_level(input: Res>, mut windows: Query<&mut Window>) { } } +/// This system toggles the window controls when pressing buttons 1, 2 and 3 +/// +/// This feature only works on some platforms. Please check the +/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html#structfield.enabled_buttons) +/// for more details. +fn toggle_window_controls(input: Res>, mut windows: Query<&mut Window>) { + let toggle_minimize = input.just_pressed(KeyCode::Key1); + let toggle_maximize = input.just_pressed(KeyCode::Key2); + let toggle_close = input.just_pressed(KeyCode::Key3); + + if toggle_minimize || toggle_maximize || toggle_close { + let mut window = windows.single_mut(); + + if toggle_minimize { + window.enabled_buttons.minimize = !window.enabled_buttons.minimize; + } + if toggle_maximize { + window.enabled_buttons.maximize = !window.enabled_buttons.maximize; + } + if toggle_close { + window.enabled_buttons.close = !window.enabled_buttons.close; + } + } +} + /// This system will then change the title during execution fn change_title(mut windows: Query<&mut Window>, time: Res