From b6250439dd699fbe686d3e365064cacd6b2ef8a4 Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 21:13:02 -0400 Subject: [PATCH 1/4] remove fast_sqrt in favor of sqrt (#19995) # Objective - the fast inverse sqrt trick hasnt been useful on modern hardware for over a decade now ## Solution - just use sqrt, modern hardware has a dedicated instruction which will outperform approximations both in efficiency and accuracy ## Testing - ran `atmosphere` --- crates/bevy_render/src/maths.wgsl | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/bevy_render/src/maths.wgsl b/crates/bevy_render/src/maths.wgsl index b492dd6bb2..d1e35523dc 100644 --- a/crates/bevy_render/src/maths.wgsl +++ b/crates/bevy_render/src/maths.wgsl @@ -104,17 +104,11 @@ fn project_onto(lhs: vec3, rhs: vec3) -> vec3 { // are likely most useful when raymarching, for example, where complete numeric // accuracy can be sacrificed for greater sample count. -fn fast_sqrt(x: f32) -> f32 { - let n = bitcast(0x1fbd1df5 + (bitcast(x) >> 1u)); - // One Newton's method iteration for better precision - return 0.5 * (n + x / n); -} - // Slightly less accurate than fast_acos_4, but much simpler. fn fast_acos(in_x: f32) -> f32 { let x = abs(in_x); var res = -0.156583 * x + HALF_PI; - res *= fast_sqrt(1.0 - x); + res *= sqrt(1.0 - x); return select(PI - res, res, in_x >= 0.0); } @@ -131,7 +125,7 @@ fn fast_acos_4(x: f32) -> f32 { s = -0.2121144 * x1 + 1.5707288; s = 0.0742610 * x2 + s; s = -0.0187293 * x3 + s; - s = fast_sqrt(1.0 - x1) * s; + s = sqrt(1.0 - x1) * s; // acos function mirroring return select(PI - s, s, x >= 0.0); From 2c6cf9a597ef3d4b732e23534eec0cca8661a9f1 Mon Sep 17 00:00:00 2001 From: andriyDev Date: Sun, 6 Jul 2025 18:31:40 -0700 Subject: [PATCH 2/4] Run `RenderStartup` in/before extract instead of after it. (#19926) # Objective - Fixes #19910. ## Solution - First, allow extraction function to be FnMut instead of Fn. FnMut is a superset of Fn anyway, and we only ever call this function once at a time (we would never call this in parallel for different pairs of worlds or something). - Run the `RenderStartup` schedule in the extract function with a flag to only do it once. - Remove all the `MainRender` stuff. One sad part here is that now the `RenderStartup` blocks extraction. So for pipelined rendering, our simulation will be blocked on the first frame while we set up all the rendering resources. I don't see this as a big loss though since A) that is fundamentally what we want here - extraction **has to** run after `RenderStartup`, and the only way to do better is to somehow run `RenderStartup` in parallel with the first simulation frame, and B) without `RenderStartup` the **entire** app was blocked on initializing render resources during Plugin construction - so we're not really losing anything here. ## Testing - I ran the `custom_post_processing` example (which was ported to use `RenderStartup` in #19886) and it still works. --- crates/bevy_app/src/sub_app.rs | 8 ++--- crates/bevy_render/src/lib.rs | 33 ++++++++----------- .../migration-guides/extract_fn_is_mut.md | 10 ++++++ 3 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 release-content/migration-guides/extract_fn_is_mut.md diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 56d6b43d38..56a496f2b5 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -12,7 +12,7 @@ use core::fmt::Debug; #[cfg(feature = "trace")] use tracing::info_span; -type ExtractFn = Box; +type ExtractFn = Box; /// A secondary application with its own [`World`]. These can run independently of each other. /// @@ -160,7 +160,7 @@ impl SubApp { /// The first argument is the `World` to extract data from, the second argument is the app `World`. pub fn set_extract(&mut self, extract: F) -> &mut Self where - F: Fn(&mut World, &mut World) + Send + 'static, + F: FnMut(&mut World, &mut World) + Send + 'static, { self.extract = Some(Box::new(extract)); self @@ -177,13 +177,13 @@ impl SubApp { /// ``` /// # use bevy_app::SubApp; /// # let mut app = SubApp::new(); - /// let default_fn = app.take_extract(); + /// let mut default_fn = app.take_extract(); /// app.set_extract(move |main, render| { /// // Do pre-extract custom logic /// // [...] /// /// // Call Bevy's default, which executes the Extract phase - /// if let Some(f) = default_fn.as_ref() { + /// if let Some(f) = default_fn.as_mut() { /// f(main, render); /// } /// diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 471a672540..9b8ef6fb37 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -221,22 +221,6 @@ pub enum RenderSystems { PostCleanup, } -/// The schedule that contains the app logic that is evaluated each tick -/// -/// This is highly inspired by [`bevy_app::Main`] -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct MainRender; -impl MainRender { - pub fn run(world: &mut World, mut run_at_least_once: Local) { - if !*run_at_least_once { - let _ = world.try_run_schedule(RenderStartup); - *run_at_least_once = true; - } - - let _ = world.try_run_schedule(Render); - } -} - /// Deprecated alias for [`RenderSystems`]. #[deprecated(since = "0.17.0", note = "Renamed to `RenderSystems`.")] pub type RenderSet = RenderSystems; @@ -561,7 +545,7 @@ unsafe fn initialize_render_app(app: &mut App) { app.init_resource::(); let mut render_app = SubApp::new(); - render_app.update_schedule = Some(MainRender.intern()); + render_app.update_schedule = Some(Render.intern()); let mut extract_schedule = Schedule::new(ExtractSchedule); // We skip applying any commands during the ExtractSchedule @@ -576,7 +560,6 @@ unsafe fn initialize_render_app(app: &mut App) { .add_schedule(extract_schedule) .add_schedule(Render::base_schedule()) .init_resource::() - .add_systems(MainRender, MainRender::run) .insert_resource(app.world().resource::().clone()) .add_systems(ExtractSchedule, PipelineCache::extract_shaders) .add_systems( @@ -592,7 +575,19 @@ unsafe fn initialize_render_app(app: &mut App) { ), ); - render_app.set_extract(|main_world, render_world| { + // We want the closure to have a flag to only run the RenderStartup schedule once, but the only + // way to have the closure store this flag is by capturing it. This variable is otherwise + // unused. + let mut should_run_startup = true; + render_app.set_extract(move |main_world, render_world| { + if should_run_startup { + // Run the `RenderStartup` if it hasn't run yet. This does mean `RenderStartup` blocks + // the rest of the app extraction, but this is necessary since extraction itself can + // depend on resources initialized in `RenderStartup`. + render_world.run_schedule(RenderStartup); + should_run_startup = false; + } + { #[cfg(feature = "trace")] let _stage_span = tracing::info_span!("entity_sync").entered(); diff --git a/release-content/migration-guides/extract_fn_is_mut.md b/release-content/migration-guides/extract_fn_is_mut.md new file mode 100644 index 0000000000..a27db69fc0 --- /dev/null +++ b/release-content/migration-guides/extract_fn_is_mut.md @@ -0,0 +1,10 @@ +--- +title: `take_extract` now returns `dyn FnMut` instead of `dyn Fn`. +pull_requests: [19926] +--- + +Previously, `set_extract` accepted any `Fn`. Now we accept any `FnMut`. For callers of +`set_extract`, there is no difference since `Fn: FnMut`. + +However, callers of `take_extract` will now be returned +`Option>` instead. From 905965b842c284ce0636259af2dcda9405c83af8 Mon Sep 17 00:00:00 2001 From: Connor Dalrymple <33494124+kerstop@users.noreply.github.com> Date: Sun, 6 Jul 2025 21:32:51 -0500 Subject: [PATCH 3/4] Skip allocation of zero size meshes (#19938) # Objective Fixes #16525 Fixes #19710 ## Solution Not allocating a mesh if it is empty. ## Testing I tested using the following minimum repro from #16525 ```rust use bevy::{asset::RenderAssetUsages, prelude::*, render::mesh::PrimitiveTopology}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .run(); } fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { commands.spawn(Camera2d); let mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ); commands.spawn(( Mesh2d(meshes.add(mesh)), MeshMaterial2d(materials.add(Color::hsl(180.0, 0.95, 0.7))), )); } ``` I was able to test on webgl2 and windows native and the issue seems to be resolved. I am not familiar with how mesh rendering works and feel like just skipping meshes should cause issues but I did not notice any. --- crates/bevy_render/src/mesh/allocator.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index c171cf3957..bbdb543116 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -452,13 +452,17 @@ impl MeshAllocator { // Allocate. for (mesh_id, mesh) in &extracted_meshes.extracted { + let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64; + if vertex_buffer_size == 0 { + continue; + } // Allocate vertex data. Note that we can only pack mesh vertex data // together if the platform supports it. let vertex_element_layout = ElementLayout::vertex(mesh_vertex_buffer_layouts, mesh); if self.general_vertex_slabs_supported { self.allocate( mesh_id, - mesh.get_vertex_buffer_size() as u64, + vertex_buffer_size, vertex_element_layout, &mut slabs_to_grow, mesh_allocator_settings, From 1fb5a622973fb5850c4b85f293fe25d1de06040a Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 23:47:14 -0400 Subject: [PATCH 4/4] fix meshlets with charlotte (#19996) # Objective - fix meshlets not finding entrypoint and crashing # Solution - remove faulty ifdefs --- crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl index 4b114cbfc8..1309c7884c 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl @@ -15,7 +15,6 @@ fn vertex(@builtin(vertex_index) vertex_input: u32) -> @builtin(position) vec4) -> @location(0) vec4 { let vertex_output = resolve_vertex_output(frag_coord); @@ -23,7 +22,6 @@ fn fragment(@builtin(position) frag_coord: vec4) -> @location(0) vec4 let color = vec3(rand_f(&rng), rand_f(&rng), rand_f(&rng)); return vec4(color, 1.0); } -#endif #ifdef PREPASS_FRAGMENT @fragment