From 4e9374661b051e74f4b6d1cf1d38a14ba3764d9b Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 10:49:39 -0300 Subject: [PATCH 01/32] Create `MeshAllocatorDiagnosticPlugin` --- .../mesh_allocator_diagnostic_plugin.rs | 58 +++++++++++++++++++ crates/bevy_render/src/diagnostic/mod.rs | 2 + crates/bevy_render/src/mesh/allocator.rs | 19 ++++++ 3 files changed, 79 insertions(+) create mode 100644 crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs diff --git a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs new file mode 100644 index 0000000000000..6d730382a2e31 --- /dev/null +++ b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs @@ -0,0 +1,58 @@ +use bevy_app::{Plugin, PreUpdate}; +use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; +use bevy_ecs::{resource::Resource, system::Res}; +use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; + +use crate::{mesh::allocator::MeshAllocator, Extract, ExtractSchedule, RenderApp}; + +/// Number of meshes allocated by the allocator +const MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); + +/// Number of meshes allocated by the allocator +const MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = + DiagnosticPath::const_new("mesh_allocator_slabs_size"); + +pub struct MeshAllocatorDiagnosticPlugin; + +impl Plugin for MeshAllocatorDiagnosticPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS).with_suffix(" slabs")) + .register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS_SIZE).with_suffix(" bytes")) + .init_resource::() + .add_systems(PreUpdate, add_mesh_allocator_measurement); + + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems(ExtractSchedule, measure_allocator); + } + } +} + +#[derive(Debug, Default, Resource)] +struct MeshAllocatorMeasurements { + slabs: AtomicUsize, + slabs_size: AtomicU64, +} + +fn add_mesh_allocator_measurement( + mut diagnostics: Diagnostics, + measurements: Res, +) { + diagnostics.add_measurement(&MESH_ALLOCATOR_SLABS, || { + measurements.slabs.load(Ordering::Relaxed) as f64 + }); + diagnostics.add_measurement(&MESH_ALLOCATOR_SLABS_SIZE, || { + measurements.slabs_size.load(Ordering::Relaxed) as f64 + }); +} + +fn measure_allocator( + measurements: Extract>, + allocator: Res, +) { + measurements + .slabs + .store(allocator.slab_count(), Ordering::Relaxed); + measurements + .slabs_size + .store(allocator.slabs_size(), Ordering::Relaxed); +} diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 7f046036a9be5..9e0347e414c37 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -3,6 +3,7 @@ //! For more info, see [`RenderDiagnosticsPlugin`]. pub(crate) mod internal; +mod mesh_allocator_diagnostic_plugin; #[cfg(feature = "tracing-tracy")] mod tracy_gpu; @@ -16,6 +17,7 @@ use crate::{renderer::RenderAdapterInfo, RenderApp}; use self::internal::{ sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, }; +pub use mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin; use super::{RenderDevice, RenderQueue}; diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index eb2d4de626e1a..3a4f684e72b68 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -169,6 +169,15 @@ enum Slab { LargeObject(LargeObjectSlab), } +impl Slab { + pub fn buffer_size(&self) -> u64 { + match self { + Self::General(gs) => gs.buffer.as_ref().map(|buffer| buffer.size()).unwrap_or(0), + Self::LargeObject(lo) => lo.buffer.as_ref().map(|buffer| buffer.size()).unwrap_or(0), + } + } +} + /// A resizable slab that can contain multiple objects. /// /// This is the normal type of slab used for objects that are below the @@ -405,6 +414,16 @@ impl MeshAllocator { ) } + /// Get the number of allocated slabs + pub fn slab_count(&self) -> usize { + self.slabs.len() + } + + /// Get the total size of all allocated slabs + pub fn slabs_size(&self) -> u64 { + self.slabs.iter().map(|slab| slab.1.buffer_size()).sum() + } + /// Given a slab and a mesh with data located with it, returns the buffer /// and range of that mesh data within the slab. fn mesh_slice_in_slab( From 415dc7043fbd3a8ed9d4b4ed06c0143cc31d7827 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 11:16:16 -0300 Subject: [PATCH 02/32] Fix doc --- .../src/diagnostic/mesh_allocator_diagnostic_plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs index 6d730382a2e31..7a190b8e44f67 100644 --- a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs +++ b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs @@ -8,7 +8,7 @@ use crate::{mesh::allocator::MeshAllocator, Extract, ExtractSchedule, RenderApp} /// Number of meshes allocated by the allocator const MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); -/// Number of meshes allocated by the allocator +/// Total size of all slabs const MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs_size"); From da21313122de3a431ea716e7f42818febcb99453 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 11:20:20 -0300 Subject: [PATCH 03/32] Add more metrics to `MeshAllocatorDiagnosticPlugin` --- .../diagnostic/mesh_allocator_diagnostic_plugin.rs | 12 ++++++++++++ crates/bevy_render/src/mesh/allocator.rs | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs index 7a190b8e44f67..f8ebae1414645 100644 --- a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs +++ b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs @@ -12,12 +12,17 @@ const MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_all const MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs_size"); +/// Number of meshes allocated into slabs +const MESH_ALLOCATOR_ALLOCATIONS: DiagnosticPath = + DiagnosticPath::const_new("mesh_allocator_allocations"); + pub struct MeshAllocatorDiagnosticPlugin; impl Plugin for MeshAllocatorDiagnosticPlugin { fn build(&self, app: &mut bevy_app::App) { app.register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS).with_suffix(" slabs")) .register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS_SIZE).with_suffix(" bytes")) + .register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_ALLOCATIONS).with_suffix(" meshes")) .init_resource::() .add_systems(PreUpdate, add_mesh_allocator_measurement); @@ -31,6 +36,7 @@ impl Plugin for MeshAllocatorDiagnosticPlugin { struct MeshAllocatorMeasurements { slabs: AtomicUsize, slabs_size: AtomicU64, + allocations: AtomicUsize, } fn add_mesh_allocator_measurement( @@ -43,6 +49,9 @@ fn add_mesh_allocator_measurement( diagnostics.add_measurement(&MESH_ALLOCATOR_SLABS_SIZE, || { measurements.slabs_size.load(Ordering::Relaxed) as f64 }); + diagnostics.add_measurement(&MESH_ALLOCATOR_ALLOCATIONS, || { + measurements.allocations.load(Ordering::Relaxed) as f64 + }); } fn measure_allocator( @@ -55,4 +64,7 @@ fn measure_allocator( measurements .slabs_size .store(allocator.slabs_size(), Ordering::Relaxed); + measurements + .allocations + .store(allocator.allocations(), Ordering::Relaxed); } diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 3a4f684e72b68..66ad6eda99102 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -424,6 +424,10 @@ impl MeshAllocator { self.slabs.iter().map(|slab| slab.1.buffer_size()).sum() } + pub fn allocations(&self) -> usize { + self.mesh_id_to_index_slab.len() + } + /// Given a slab and a mesh with data located with it, returns the buffer /// and range of that mesh data within the slab. fn mesh_slice_in_slab( From 36a66839e9a73cb077cb03906c3b76b5c37f5c23 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 11:24:21 -0300 Subject: [PATCH 04/32] Add `RenderAssetDiagnosticPlugin` --- crates/bevy_render/src/diagnostic/mod.rs | 6 +- .../render_asset_diagnostic_plugin.rs | 73 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 9e0347e414c37..821929a095e26 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod internal; mod mesh_allocator_diagnostic_plugin; +mod render_asset_diagnostic_plugin; #[cfg(feature = "tracing-tracy")] mod tracy_gpu; @@ -17,7 +18,10 @@ use crate::{renderer::RenderAdapterInfo, RenderApp}; use self::internal::{ sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, }; -pub use mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin; +pub use self::{ + mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin, + render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin, +}; use super::{RenderDevice, RenderQueue}; diff --git a/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs new file mode 100644 index 0000000000000..48c46558595ff --- /dev/null +++ b/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs @@ -0,0 +1,73 @@ +use core::{any::type_name, marker::PhantomData}; + +use bevy_app::{Plugin, PreUpdate}; +use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; +use bevy_ecs::{resource::Resource, system::Res}; +use bevy_platform::sync::atomic::{AtomicUsize, Ordering}; + +use crate::{ + render_asset::{RenderAsset, RenderAssets}, + Extract, ExtractSchedule, RenderApp, +}; + +pub struct RenderAssetDiagnosticPlugin { + suffix: &'static str, + path: DiagnosticPath, + _phantom: PhantomData, +} + +impl RenderAssetDiagnosticPlugin { + pub fn new(suffix: &'static str) -> Self { + Self { + suffix, + path: DiagnosticPath::from_components(["render_asset", type_name::()]), + _phantom: PhantomData, + } + } +} + +impl Plugin for RenderAssetDiagnosticPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_diagnostic(Diagnostic::new(self.path.clone()).with_suffix(self.suffix)) + .init_resource::>() + .add_systems(PreUpdate, add_render_asset_measurement::); + + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems(ExtractSchedule, measure_render_asset::); + } + } +} + +#[derive(Debug, Resource)] +struct RenderAssetMeasurements { + assets: AtomicUsize, + _phantom: PhantomData, +} + +impl Default for RenderAssetMeasurements { + fn default() -> Self { + Self { + assets: AtomicUsize::default(), + _phantom: PhantomData, + } + } +} + +fn add_render_asset_measurement( + mut diagnostics: Diagnostics, + measurements: Res>, +) { + diagnostics.add_measurement( + &DiagnosticPath::from_components(["render_asset", type_name::()]), + || measurements.assets.load(Ordering::Relaxed) as f64, + ); +} + +fn measure_render_asset( + measurements: Extract>>, + assets: Res>, +) { + measurements + .assets + .store(assets.iter().count(), Ordering::Relaxed); +} From eca3de2fa2582976951f3e46134a64d7f551ca4c Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 11:44:01 -0300 Subject: [PATCH 05/32] Expose `DiagnosticPath` though the plugins --- .../mesh_allocator_diagnostic_plugin.rs | 37 +++++++++++++++---- .../render_asset_diagnostic_plugin.rs | 16 +++++--- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs index f8ebae1414645..cd0b70dbc502c 100644 --- a/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs +++ b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs @@ -6,25 +6,46 @@ use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use crate::{mesh::allocator::MeshAllocator, Extract, ExtractSchedule, RenderApp}; /// Number of meshes allocated by the allocator -const MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); +static MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); /// Total size of all slabs -const MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = +static MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs_size"); /// Number of meshes allocated into slabs -const MESH_ALLOCATOR_ALLOCATIONS: DiagnosticPath = +static MESH_ALLOCATOR_ALLOCATIONS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_allocations"); pub struct MeshAllocatorDiagnosticPlugin; +impl MeshAllocatorDiagnosticPlugin { + /// Get the [`DiagnosticPath`] for slab count + pub fn slabs_diagnostic_path() -> &'static DiagnosticPath { + &MESH_ALLOCATOR_SLABS + } + /// Get the [`DiagnosticPath`] for total slabs size + pub fn slabs_size_diagnostic_path() -> &'static DiagnosticPath { + &MESH_ALLOCATOR_SLABS_SIZE + } + /// Get the [`DiagnosticPath`] for mesh allocations + pub fn allocations_diagnostic_path() -> &'static DiagnosticPath { + &MESH_ALLOCATOR_ALLOCATIONS + } +} + impl Plugin for MeshAllocatorDiagnosticPlugin { fn build(&self, app: &mut bevy_app::App) { - app.register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS).with_suffix(" slabs")) - .register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_SLABS_SIZE).with_suffix(" bytes")) - .register_diagnostic(Diagnostic::new(MESH_ALLOCATOR_ALLOCATIONS).with_suffix(" meshes")) - .init_resource::() - .add_systems(PreUpdate, add_mesh_allocator_measurement); + app.register_diagnostic( + Diagnostic::new(MESH_ALLOCATOR_SLABS.clone()).with_suffix(" slabs"), + ) + .register_diagnostic( + Diagnostic::new(MESH_ALLOCATOR_SLABS_SIZE.clone()).with_suffix(" bytes"), + ) + .register_diagnostic( + Diagnostic::new(MESH_ALLOCATOR_ALLOCATIONS.clone()).with_suffix(" meshes"), + ) + .init_resource::() + .add_systems(PreUpdate, add_mesh_allocator_measurement); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems(ExtractSchedule, measure_allocator); diff --git a/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs b/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs index 48c46558595ff..347bae7338797 100644 --- a/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs +++ b/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs @@ -12,7 +12,6 @@ use crate::{ pub struct RenderAssetDiagnosticPlugin { suffix: &'static str, - path: DiagnosticPath, _phantom: PhantomData, } @@ -20,17 +19,22 @@ impl RenderAssetDiagnosticPlugin { pub fn new(suffix: &'static str) -> Self { Self { suffix, - path: DiagnosticPath::from_components(["render_asset", type_name::()]), _phantom: PhantomData, } } + + pub fn render_asset_diagnostic_path() -> DiagnosticPath { + DiagnosticPath::from_components(["render_asset", type_name::()]) + } } impl Plugin for RenderAssetDiagnosticPlugin { fn build(&self, app: &mut bevy_app::App) { - app.register_diagnostic(Diagnostic::new(self.path.clone()).with_suffix(self.suffix)) - .init_resource::>() - .add_systems(PreUpdate, add_render_asset_measurement::); + app.register_diagnostic( + Diagnostic::new(Self::render_asset_diagnostic_path()).with_suffix(self.suffix), + ) + .init_resource::>() + .add_systems(PreUpdate, add_render_asset_measurement::); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems(ExtractSchedule, measure_render_asset::); @@ -58,7 +62,7 @@ fn add_render_asset_measurement( measurements: Res>, ) { diagnostics.add_measurement( - &DiagnosticPath::from_components(["render_asset", type_name::()]), + &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), || measurements.assets.load(Ordering::Relaxed) as f64, ); } From fb41396733bc07226a840f1798807208a4ff1dce Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 11:47:11 -0300 Subject: [PATCH 06/32] First attempt at allocator leak detection --- tests/get_mut_leak.rs | 95 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/get_mut_leak.rs diff --git a/tests/get_mut_leak.rs b/tests/get_mut_leak.rs new file mode 100644 index 0000000000000..6ec5f4d84ab71 --- /dev/null +++ b/tests/get_mut_leak.rs @@ -0,0 +1,95 @@ +//! Tests if touching mutably a asset that gets extracted to the render world +//! causes a leak + +use std::time::Duration; + +use bevy::{ + app::{App, PluginGroup, Startup, Update}, + asset::{Asset, Assets, Handle}, + audio::AudioPlugin, + color::Color, + diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, + ecs::system::{Commands, Local, Res, ResMut}, + math::primitives::Sphere, + pbr::{MeshMaterial3d, StandardMaterial}, + render::{ + diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, + mesh::{Mesh, Mesh3d, Meshable, RenderMesh}, + }, + window::WindowPlugin, + winit::WinitPlugin, + DefaultPlugins, +}; + +#[test] +fn check_mesh_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::::new(" meshes"), + MeshAllocatorDiagnosticPlugin, + )) + .add_systems(Startup, mesh_setup) + .add_systems( + Update, + (touch_mutably::, crash_on_mesh_leak_detection), + ); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + +fn mesh_setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut mesh_leaker: Local>>, + mut material_leaker: Local>>, +) { + bevy::log::info!("Mesh setup"); + commands.spawn(( + Mesh3d(meshes.add(Sphere::new(1.).mesh().ico(79).unwrap())), + MeshMaterial3d(materials.add(Color::WHITE)), + )); + + for _ in 0..16 { + mesh_leaker.push(meshes.add(Sphere::new(1.).mesh().ico(79).unwrap())); + } + for _ in 0..1000 { + material_leaker.push(materials.add(Color::WHITE)); + } +} + +fn touch_mutably(mut assets: ResMut>) { + for _ in assets.iter_mut() {} +} + +fn crash_on_mesh_leak_detection(diagnostic_store: Res) { + if let (Some(render_meshes), Some(allocations)) = ( + diagnostic_store + .get_measurement( + &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), + ) + .filter(|diag| diag.value > 0.), + diagnostic_store + .get_measurement(MeshAllocatorDiagnosticPlugin::allocations_diagnostic_path()) + .filter(|diag| diag.value > 0.), + ) { + assert!( + render_meshes.value < allocations.value * 10., + "Detected leak" + ); + } +} From f229b3dcd056ec3d7d2ee681391299e873fdcd74 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 12:21:11 -0300 Subject: [PATCH 07/32] Create diagnostics for material allocations --- crates/bevy_pbr/src/diagnostic.rs | 101 ++++++++++++++++++++ crates/bevy_pbr/src/lib.rs | 1 + crates/bevy_pbr/src/material_bind_groups.rs | 39 ++++++++ tests/get_mut_leak.rs | 53 +++++++++- 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_pbr/src/diagnostic.rs diff --git a/crates/bevy_pbr/src/diagnostic.rs b/crates/bevy_pbr/src/diagnostic.rs new file mode 100644 index 0000000000000..44f517c02131b --- /dev/null +++ b/crates/bevy_pbr/src/diagnostic.rs @@ -0,0 +1,101 @@ +use core::{any::type_name, marker::PhantomData}; + +use bevy_app::{Plugin, PreUpdate}; +use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; +use bevy_ecs::{resource::Resource, system::Res}; +use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; +use bevy_render::{Extract, ExtractSchedule, RenderApp}; + +use crate::{Material, MaterialBindGroupAllocator}; + +#[derive(Default)] +pub struct MaterialAllocatorDiagnosticPlugin { + _phantom: PhantomData, +} + +impl MaterialAllocatorDiagnosticPlugin { + /// Get the [`DiagnosticPath`] for slab count + pub fn slabs_diagnostic_path() -> DiagnosticPath { + DiagnosticPath::from_components(["material_allocator_slabs", type_name::()]) + } + /// Get the [`DiagnosticPath`] for total slabs size + pub fn slabs_size_diagnostic_path() -> DiagnosticPath { + DiagnosticPath::from_components(["material_allocator_slabs_size", type_name::()]) + } + /// Get the [`DiagnosticPath`] for material allocations + pub fn allocations_diagnostic_path() -> DiagnosticPath { + DiagnosticPath::from_components(["material_allocator_allocations", type_name::()]) + } +} + +impl Plugin for MaterialAllocatorDiagnosticPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_diagnostic( + Diagnostic::new(Self::slabs_diagnostic_path()).with_suffix(" slabs"), + ) + .register_diagnostic( + Diagnostic::new(Self::slabs_size_diagnostic_path()).with_suffix(" bytes"), + ) + .register_diagnostic( + Diagnostic::new(Self::allocations_diagnostic_path()).with_suffix(" meshes"), + ) + .init_resource::>() + .add_systems(PreUpdate, add_material_allocator_measurement::); + + if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_systems(ExtractSchedule, measure_allocator::); + } + } +} + +#[derive(Debug, Resource)] +struct MaterialAllocatorMeasurements { + slabs: AtomicUsize, + slabs_size: AtomicUsize, + allocations: AtomicU64, + _phantom: PhantomData, +} + +impl Default for MaterialAllocatorMeasurements { + fn default() -> Self { + Self { + slabs: AtomicUsize::default(), + slabs_size: AtomicUsize::default(), + allocations: AtomicU64::default(), + _phantom: PhantomData, + } + } +} + +fn add_material_allocator_measurement( + mut diagnostics: Diagnostics, + measurements: Res>, +) { + diagnostics.add_measurement( + &MaterialAllocatorDiagnosticPlugin::::slabs_diagnostic_path(), + || measurements.slabs.load(Ordering::Relaxed) as f64, + ); + diagnostics.add_measurement( + &MaterialAllocatorDiagnosticPlugin::::slabs_size_diagnostic_path(), + || measurements.slabs_size.load(Ordering::Relaxed) as f64, + ); + diagnostics.add_measurement( + &MaterialAllocatorDiagnosticPlugin::::allocations_diagnostic_path(), + || measurements.allocations.load(Ordering::Relaxed) as f64, + ); +} + +fn measure_allocator( + measurements: Extract>>, + allocator: Res>, +) { + measurements + .slabs + .store(allocator.slab_count(), Ordering::Relaxed); + measurements + .slabs_size + .store(allocator.slabs_size(), Ordering::Relaxed); + measurements + .allocations + .store(allocator.allocations(), Ordering::Relaxed); +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 12785f3e78607..3888ab3cf1b20 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -29,6 +29,7 @@ mod cluster; mod components; pub mod decal; pub mod deferred; +pub mod diagnostic; mod extended_material; mod fog; mod light; diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index b539d2098f1e3..bd74342b1a697 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -611,6 +611,45 @@ where } } } + + /// Get number of allocated slabs for bindless material, returns 0 if it is + /// [`Self::NonBindless`]. + pub fn slab_count(&self) -> usize { + match self { + Self::Bindless(bless) => bless.slabs.len(), + Self::NonBindless(_) => 0, + } + } + + /// Get total size of slabs allocated for bindless material, returns 0 if it is + /// [`Self::NonBindless`]. + pub fn slabs_size(&self) -> usize { + match self { + Self::Bindless(bless) => bless + .slabs + .iter() + .flat_map(|slab| { + slab.data_buffers + .iter() + .map(|(_, buffer)| buffer.buffer.len()) + }) + .sum(), + Self::NonBindless(_) => 0, + } + } + + /// Get number of bindless material allocations in slabs, returns 0 if it is + /// [`Self::NonBindless`]. + pub fn allocations(&self) -> u64 { + match self { + Self::Bindless(bless) => bless + .slabs + .iter() + .map(|slab| u64::from(slab.allocated_resource_count)) + .sum(), + Self::NonBindless(_) => 0, + } + } } impl MaterialBindlessIndexTable diff --git a/tests/get_mut_leak.rs b/tests/get_mut_leak.rs index 6ec5f4d84ab71..39e76bcda28c1 100644 --- a/tests/get_mut_leak.rs +++ b/tests/get_mut_leak.rs @@ -11,7 +11,10 @@ use bevy::{ diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, ecs::system::{Commands, Local, Res, ResMut}, math::primitives::Sphere, - pbr::{MeshMaterial3d, StandardMaterial}, + pbr::{ + diagnostic::MaterialAllocatorDiagnosticPlugin, Material, MeshMaterial3d, PreparedMaterial, + StandardMaterial, + }, render::{ diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, mesh::{Mesh, Mesh3d, Meshable, RenderMesh}, @@ -51,6 +54,39 @@ fn check_mesh_leak() { } } +#[test] +fn check_standard_material_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::>::new(" materials"), + MaterialAllocatorDiagnosticPlugin::::default(), + )) + .add_systems(Startup, mesh_setup) + .add_systems( + Update, + ( + touch_mutably::, + crash_on_material_leak_detection::, + ), + ); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + fn mesh_setup( mut commands: Commands, mut meshes: ResMut>, @@ -93,3 +129,18 @@ fn crash_on_mesh_leak_detection(diagnostic_store: Res) { ); } } + +fn crash_on_material_leak_detection(diagnostic_store: Res) { + if let (Some(materials), Some(allocations)) = ( + diagnostic_store + .get_measurement( + &RenderAssetDiagnosticPlugin::>::render_asset_diagnostic_path(), + ) + .filter(|diag| diag.value > 0.), + diagnostic_store + .get_measurement(&MaterialAllocatorDiagnosticPlugin::::allocations_diagnostic_path()) + .filter(|diag| diag.value > 0.), + ) { + assert!(materials.value < allocations.value * 10., "Detected leak"); + } +} From a4054a96ad43d0bd4c07ff9519ce365b6c294b11 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 12:24:30 -0300 Subject: [PATCH 08/32] Rename render asset leak test --- tests/{get_mut_leak.rs => render_asset_leaks.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{get_mut_leak.rs => render_asset_leaks.rs} (100%) diff --git a/tests/get_mut_leak.rs b/tests/render_asset_leaks.rs similarity index 100% rename from tests/get_mut_leak.rs rename to tests/render_asset_leaks.rs From 08c567701b6cacc4299f1897d559adf6f5ccb046 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 13:05:43 -0300 Subject: [PATCH 09/32] Add release note --- .../release-notes/release_notes_template.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 release-content/release-notes/release_notes_template.md diff --git a/release-content/release-notes/release_notes_template.md b/release-content/release-notes/release_notes_template.md new file mode 100644 index 0000000000000..bd2f2a9707521 --- /dev/null +++ b/release-content/release-notes/release_notes_template.md @@ -0,0 +1,16 @@ +--- +title: Render Assets diagnostics +authors: ["@hukasu"] +pull_requests: [19311] +--- + +## Goals + +Create diagnostics plugins `MeshAllocatorDiagnosticPlugin`, `MaterialDiagnosticPlugin` and `RenderAssetDiagnosticPlugin` +that collect measurements related to `MeshAllocator`s, `MaterialBindGroupAllocator`, and `RenderAssets` respectively. + +`MeshAllocatorDiagnosticPlugin` and `MaterialDiagnosticPlugin` measure the number of slabs, the total size of memory +allocated by the slabs, and the number of objects allocated in the slabs. Only bindless materials use slabs for their +allocations, non-bindless materials return 0 for all of them. + +`RenderAssetDiagnosticsPlugin` measure the number of assets in `RenderAssets`. From ef7fa484c4137dd7f0cbd54248c9e41ad14ca657 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 13:39:58 -0300 Subject: [PATCH 10/32] Fix wrong settings for test --- tests/render_asset_leaks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 39e76bcda28c1..d5c1cfe73c436 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -74,7 +74,7 @@ fn check_standard_material_leak() { .add_systems( Update, ( - touch_mutably::, + touch_mutably::, crash_on_material_leak_detection::, ), ); @@ -124,7 +124,7 @@ fn crash_on_mesh_leak_detection(diagnostic_store: Res) { .filter(|diag| diag.value > 0.), ) { assert!( - render_meshes.value < allocations.value * 10., + allocations.value < render_meshes.value * 10., "Detected leak" ); } @@ -141,6 +141,6 @@ fn crash_on_material_leak_detection(diagnostic_store: Res::allocations_diagnostic_path()) .filter(|diag| diag.value > 0.), ) { - assert!(materials.value < allocations.value * 10., "Detected leak"); + assert!(allocations.value < materials.value * 10., "Detected leak"); } } From e924106a85b7c1f756595147b9eecd28967932b7 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 13:51:14 -0300 Subject: [PATCH 11/32] Add test to check leaks through churn --- tests/render_asset_leaks.rs | 91 +++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index d5c1cfe73c436..c46b9a1761b96 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -9,20 +9,20 @@ use bevy::{ audio::AudioPlugin, color::Color, diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, - ecs::system::{Commands, Local, Res, ResMut}, + ecs::system::{Res, ResMut}, math::primitives::Sphere, pbr::{ - diagnostic::MaterialAllocatorDiagnosticPlugin, Material, MeshMaterial3d, PreparedMaterial, - StandardMaterial, + diagnostic::MaterialAllocatorDiagnosticPlugin, Material, PreparedMaterial, StandardMaterial, }, render::{ diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, - mesh::{Mesh, Mesh3d, Meshable, RenderMesh}, + mesh::{Mesh, Meshable, RenderMesh}, }, window::WindowPlugin, winit::WinitPlugin, DefaultPlugins, }; +use bevy_ecs::{resource::Resource, system::Commands}; #[test] fn check_mesh_leak() { @@ -54,6 +54,7 @@ fn check_mesh_leak() { } } +#[ignore = "FIXME Failing test"] #[test] fn check_standard_material_leak() { let mut app = App::new(); @@ -87,31 +88,101 @@ fn check_standard_material_leak() { } } +#[test] +fn check_mesh_churn_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::::new(" meshes"), + MeshAllocatorDiagnosticPlugin, + )) + .add_systems(Startup, mesh_setup) + .add_systems(Update, (churn::, crash_on_mesh_leak_detection)); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + +#[ignore = "FIXME Failing test"] +#[test] +fn check_standard_material_churn_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::>::new(" materials"), + MaterialAllocatorDiagnosticPlugin::::default(), + )) + .add_systems(Startup, mesh_setup) + .add_systems( + Update, + ( + churn::, + crash_on_material_leak_detection::, + ), + ); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + +#[derive(Resource)] +struct Leaker(Vec>); + fn mesh_setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - mut mesh_leaker: Local>>, - mut material_leaker: Local>>, ) { bevy::log::info!("Mesh setup"); - commands.spawn(( - Mesh3d(meshes.add(Sphere::new(1.).mesh().ico(79).unwrap())), - MeshMaterial3d(materials.add(Color::WHITE)), - )); + let mut mesh_leaker = Vec::with_capacity(16); for _ in 0..16 { mesh_leaker.push(meshes.add(Sphere::new(1.).mesh().ico(79).unwrap())); } + commands.insert_resource(Leaker(mesh_leaker)); + let mut material_leaker = Vec::with_capacity(1000); for _ in 0..1000 { material_leaker.push(materials.add(Color::WHITE)); } + commands.insert_resource(Leaker(material_leaker)); } fn touch_mutably(mut assets: ResMut>) { for _ in assets.iter_mut() {} } +fn churn(mut assets: ResMut>, mut leaker: ResMut>) { + let all_ids = leaker.0.drain(..).collect::>(); + for id in all_ids { + let asset = assets.remove(id.id()).unwrap(); + leaker.0.push(assets.add(asset)); + } +} + fn crash_on_mesh_leak_detection(diagnostic_store: Res) { if let (Some(render_meshes), Some(allocations)) = ( diagnostic_store From 0bc7dd1c82e8cce38039cb81e1ca9adb27a5017a Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 14:05:09 -0300 Subject: [PATCH 12/32] Fix internal imports --- tests/render_asset_leaks.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index c46b9a1761b96..7ad616eae89d5 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -9,7 +9,10 @@ use bevy::{ audio::AudioPlugin, color::Color, diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, - ecs::system::{Res, ResMut}, + ecs::{ + resource::Resource, + system::{Commands, Res, ResMut}, + }, math::primitives::Sphere, pbr::{ diagnostic::MaterialAllocatorDiagnosticPlugin, Material, PreparedMaterial, StandardMaterial, @@ -22,7 +25,6 @@ use bevy::{ winit::WinitPlugin, DefaultPlugins, }; -use bevy_ecs::{resource::Resource, system::Commands}; #[test] fn check_mesh_leak() { From d04208a9f64f9dbfc8a1ddd666973cecd8375f99 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 14:59:17 -0300 Subject: [PATCH 13/32] Add test that reproduces #18808 --- tests/render_asset_leaks.rs | 90 ++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 7ad616eae89d5..18c16b4f3111a 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -151,6 +151,71 @@ fn check_standard_material_churn_leak() { } } +#[ignore = "FIXME Failing test"] +#[test] +fn check_mesh_churn_insert_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::::new(" meshes"), + MeshAllocatorDiagnosticPlugin, + )) + .add_systems(Startup, mesh_setup) + .add_systems( + Update, + (churn_using_insert::, crash_on_mesh_leak_detection), + ); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + +#[ignore = "FIXME Failing test"] +#[test] +fn check_standard_material_churn_insert_leak() { + let mut app = App::new(); + app.add_plugins(( + DefaultPlugins + .build() + .disable::() + .disable::() + .disable::(), + LogDiagnosticsPlugin { + wait_duration: Duration::ZERO, + ..Default::default() + }, + RenderAssetDiagnosticPlugin::>::new(" materials"), + MaterialAllocatorDiagnosticPlugin::::default(), + )) + .add_systems(Startup, mesh_setup) + .add_systems( + Update, + ( + churn_using_insert::, + crash_on_material_leak_detection::, + ), + ); + + app.finish(); + app.cleanup(); + + for _ in 0..100 { + app.update(); + } +} + #[derive(Resource)] struct Leaker(Vec>); @@ -185,13 +250,23 @@ fn churn(mut assets: ResMut>, mut leaker: ResMut>) } } +fn churn_using_insert(mut assets: ResMut>, leaker: Res>) { + for id in &leaker.0 { + let asset = assets.remove(id.id()).unwrap(); + assets.insert(id.id(), asset); + } +} + fn crash_on_mesh_leak_detection(diagnostic_store: Res) { - if let (Some(render_meshes), Some(allocations)) = ( + if let (Some(render_meshes), Some(slab_size), Some(allocations)) = ( diagnostic_store .get_measurement( &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), ) .filter(|diag| diag.value > 0.), + diagnostic_store + .get_measurement(MeshAllocatorDiagnosticPlugin::slabs_size_diagnostic_path()) + .filter(|diag| diag.value > 0.), diagnostic_store .get_measurement(MeshAllocatorDiagnosticPlugin::allocations_diagnostic_path()) .filter(|diag| diag.value > 0.), @@ -200,20 +275,31 @@ fn crash_on_mesh_leak_detection(diagnostic_store: Res) { allocations.value < render_meshes.value * 10., "Detected leak" ); + assert!( + slab_size.value < (1 << 30) as f64, + "Exceeded 1GB of allocations." + ); } } fn crash_on_material_leak_detection(diagnostic_store: Res) { - if let (Some(materials), Some(allocations)) = ( + if let (Some(materials), Some(slab_size), Some(allocations)) = ( diagnostic_store .get_measurement( &RenderAssetDiagnosticPlugin::>::render_asset_diagnostic_path(), ) .filter(|diag| diag.value > 0.), + diagnostic_store + .get_measurement(&MaterialAllocatorDiagnosticPlugin::::slabs_size_diagnostic_path()) + .filter(|diag| diag.value > 0.), diagnostic_store .get_measurement(&MaterialAllocatorDiagnosticPlugin::::allocations_diagnostic_path()) .filter(|diag| diag.value > 0.), ) { assert!(allocations.value < materials.value * 10., "Detected leak"); + assert!( + slab_size.value < (1 << 30) as f64, + "Exceeded 1GB of allocations." + ); } } From 4ff946f8f07148794584946fdc0b9714486e9640 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 16:05:49 -0300 Subject: [PATCH 14/32] CONTENTIOUS: Run tests on linux through xvfb --- .github/workflows/ci.yml | 13 +++++++++++++ .github/workflows/weekly.yml | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a0610cf032a8..6245b696aa2fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,11 +42,24 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install Linux dependencies uses: ./.github/actions/install-linux-deps + # At some point this may be merged into `install-linux-deps`, but for now it is its own step. + - name: Install additional Linux dependencies for Vulkan + if: ${{ runner.os == 'linux' }} + run: | + sudo add-apt-repository ppa:kisak/turtle -y + sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests + if: ${{ runner.os != 'linux' }} # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" + - name: Build & run tests through xvfb + if: ${{ runner.os == 'linux' }} + # See tools/ci/src/main.rs for the commands this runs + run: xvfb-run cargo run -p ci -- test + env: + RUSTFLAGS: "-C debuginfo=0 -D warnings" ci: runs-on: ubuntu-latest diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index b4ddffdb9dbb7..60f113eee12e1 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -43,11 +43,24 @@ jobs: - uses: dtolnay/rust-toolchain@beta - name: Install Linux dependencies uses: ./.github/actions/install-linux-deps + # At some point this may be merged into `install-linux-deps`, but for now it is its own step. + - name: Install additional Linux dependencies for Vulkan + if: ${{ runner.os == 'linux' }} + run: | + sudo add-apt-repository ppa:kisak/turtle -y + sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests + if: ${{ runner.os != 'linux' }} # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" + - name: Build & run tests through xvfb + if: ${{ runner.os == 'linux' }} + # See tools/ci/src/main.rs for the commands this runs + run: xvfb-run cargo run -p ci -- test + env: + RUSTFLAGS: "-C debuginfo=0 -D warnings" lint: runs-on: ubuntu-latest From 93f7a3f09d29c7f3820eac30429ae450e0b2aca8 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 16:20:19 -0300 Subject: [PATCH 15/32] Rename release notes file --- .../{release_notes_template.md => render-assets-diagnostics.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename release-content/release-notes/{release_notes_template.md => render-assets-diagnostics.md} (100%) diff --git a/release-content/release-notes/release_notes_template.md b/release-content/release-notes/render-assets-diagnostics.md similarity index 100% rename from release-content/release-notes/release_notes_template.md rename to release-content/release-notes/render-assets-diagnostics.md From 6a94ba406c98d655e56f6731415e0e72ecd034eb Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 18:45:39 -0300 Subject: [PATCH 16/32] Fix `MaterialDiagnosticPlugin` using `meshes` as suffix --- crates/bevy_pbr/src/diagnostic.rs | 22 ++++++++++++++++++++-- tests/render_asset_leaks.rs | 6 +++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/diagnostic.rs b/crates/bevy_pbr/src/diagnostic.rs index 44f517c02131b..3902dfb969a53 100644 --- a/crates/bevy_pbr/src/diagnostic.rs +++ b/crates/bevy_pbr/src/diagnostic.rs @@ -8,11 +8,29 @@ use bevy_render::{Extract, ExtractSchedule, RenderApp}; use crate::{Material, MaterialBindGroupAllocator}; -#[derive(Default)] pub struct MaterialAllocatorDiagnosticPlugin { + suffix: &'static str, _phantom: PhantomData, } +impl MaterialAllocatorDiagnosticPlugin { + pub fn new(suffix: &'static str) -> Self { + Self { + suffix, + _phantom: PhantomData, + } + } +} + +impl Default for MaterialAllocatorDiagnosticPlugin { + fn default() -> Self { + Self { + suffix: " materials", + _phantom: PhantomData, + } + } +} + impl MaterialAllocatorDiagnosticPlugin { /// Get the [`DiagnosticPath`] for slab count pub fn slabs_diagnostic_path() -> DiagnosticPath { @@ -37,7 +55,7 @@ impl Plugin for MaterialAllocatorDiagnosticPlugin { Diagnostic::new(Self::slabs_size_diagnostic_path()).with_suffix(" bytes"), ) .register_diagnostic( - Diagnostic::new(Self::allocations_diagnostic_path()).with_suffix(" meshes"), + Diagnostic::new(Self::allocations_diagnostic_path()).with_suffix(self.suffix), ) .init_resource::>() .add_systems(PreUpdate, add_material_allocator_measurement::); diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 18c16b4f3111a..2f862ff649304 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -71,7 +71,7 @@ fn check_standard_material_leak() { ..Default::default() }, RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::default(), + MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) .add_systems(Startup, mesh_setup) .add_systems( @@ -132,7 +132,7 @@ fn check_standard_material_churn_leak() { ..Default::default() }, RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::default(), + MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) .add_systems(Startup, mesh_setup) .add_systems( @@ -197,7 +197,7 @@ fn check_standard_material_churn_insert_leak() { ..Default::default() }, RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::default(), + MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) .add_systems(Startup, mesh_setup) .add_systems( From e9f000c0312cd087c32bea0a9aafa9f24332bbe0 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 20 May 2025 18:46:56 -0300 Subject: [PATCH 17/32] Fix wrong name on release notes --- release-content/release-notes/render-assets-diagnostics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/release-notes/render-assets-diagnostics.md b/release-content/release-notes/render-assets-diagnostics.md index bd2f2a9707521..707bfe9f7e095 100644 --- a/release-content/release-notes/render-assets-diagnostics.md +++ b/release-content/release-notes/render-assets-diagnostics.md @@ -6,7 +6,7 @@ pull_requests: [19311] ## Goals -Create diagnostics plugins `MeshAllocatorDiagnosticPlugin`, `MaterialDiagnosticPlugin` and `RenderAssetDiagnosticPlugin` +Create diagnostics plugins `MeshAllocatorDiagnosticPlugin`, `MaterialAllocatorDiagnosticPlugin` and `RenderAssetDiagnosticPlugin` that collect measurements related to `MeshAllocator`s, `MaterialBindGroupAllocator`, and `RenderAssets` respectively. `MeshAllocatorDiagnosticPlugin` and `MaterialDiagnosticPlugin` measure the number of slabs, the total size of memory From dc2d1ccbddf42d3e46e4f972e902ec1007ba4c8c Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 16:19:46 -0300 Subject: [PATCH 18/32] Skip `render_asset_leak` tests on `ci test` --- tools/ci/src/commands/test.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index bdb4b663d1429..fcaf0866725b2 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -14,14 +14,25 @@ impl Prepare for TestCommand { .then_some("--no-fail-fast") .unwrap_or_default(); - vec![PreparedCommand::new::( - cmd!( - sh, - // `--benches` runs each benchmark once in order to verify that they behave - // correctly and do not panic. - "cargo test --workspace --lib --bins --tests --benches {no_fail_fast}" + vec![ + PreparedCommand::new::( + cmd!( + sh, + // `--benches` runs each benchmark once in order to verify that they behave + // correctly and do not panic. + "cargo test --workspace --lib --bins --benches {no_fail_fast}" + ), + "Please fix failing tests in output above.", ), - "Please fix failing tests in output above.", - )] + PreparedCommand::new::( + cmd!( + sh, + // `--benches` runs each benchmark once in order to verify that they behave + // correctly and do not panic. + "cargo test --tests {no_fail_fast} -- --skip check_mesh --skip check_standard_material" + ), + "Please fix failing tests in output above.", + ), + ] } } From db9352b1c2f0e236df03592eeacdf06c0f5ebe67 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 16:25:30 -0300 Subject: [PATCH 19/32] Add CI for render asset leaks --- tools/ci/src/ci.rs | 3 +++ tools/ci/src/commands/mod.rs | 2 ++ tools/ci/src/commands/test_render_assets.rs | 22 +++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 tools/ci/src/commands/test_render_assets.rs diff --git a/tools/ci/src/ci.rs b/tools/ci/src/ci.rs index 349e74a5a0183..f21f6b2df28a6 100644 --- a/tools/ci/src/ci.rs +++ b/tools/ci/src/ci.rs @@ -73,6 +73,7 @@ impl CI { cmds.append(&mut commands::FormatCommand::default().prepare(sh, flags)); cmds.append(&mut commands::ClippyCommand::default().prepare(sh, flags)); cmds.append(&mut commands::TestCommand::default().prepare(sh, flags)); + cmds.append(&mut commands::TestRenderAssetsCommand::default().prepare(sh, flags)); cmds.append(&mut commands::TestCheckCommand::default().prepare(sh, flags)); cmds.append(&mut commands::IntegrationTestCommand::default().prepare(sh, flags)); cmds.append( @@ -105,6 +106,7 @@ enum Commands { Format(commands::FormatCommand), Clippy(commands::ClippyCommand), Test(commands::TestCommand), + TestRenderAssets(commands::TestRenderAssetsCommand), TestCheck(commands::TestCheckCommand), IntegrationTest(commands::IntegrationTestCommand), IntegrationTestCheck(commands::IntegrationTestCheckCommand), @@ -127,6 +129,7 @@ impl Prepare for Commands { Commands::Format(subcommand) => subcommand.prepare(sh, flags), Commands::Clippy(subcommand) => subcommand.prepare(sh, flags), Commands::Test(subcommand) => subcommand.prepare(sh, flags), + Commands::TestRenderAssets(subcommand) => subcommand.prepare(sh, flags), Commands::TestCheck(subcommand) => subcommand.prepare(sh, flags), Commands::IntegrationTest(subcommand) => subcommand.prepare(sh, flags), Commands::IntegrationTestCheck(subcommand) => subcommand.prepare(sh, flags), diff --git a/tools/ci/src/commands/mod.rs b/tools/ci/src/commands/mod.rs index 9247ab201627b..fffb5089afab7 100644 --- a/tools/ci/src/commands/mod.rs +++ b/tools/ci/src/commands/mod.rs @@ -14,6 +14,7 @@ pub use integration_test_clean::*; pub use lints::*; pub use test::*; pub use test_check::*; +pub use test_render_assets::*; mod bench_check; mod clippy; @@ -31,3 +32,4 @@ mod integration_test_clean; mod lints; mod test; mod test_check; +mod test_render_assets; diff --git a/tools/ci/src/commands/test_render_assets.rs b/tools/ci/src/commands/test_render_assets.rs new file mode 100644 index 0000000000000..0e8001cdd8158 --- /dev/null +++ b/tools/ci/src/commands/test_render_assets.rs @@ -0,0 +1,22 @@ +use crate::{Flag, Prepare, PreparedCommand}; +use argh::FromArgs; +use xshell::cmd; + +/// Runs all tests (except for doc tests). +#[derive(FromArgs, Default)] +#[argh(subcommand, name = "test-render-assets")] +pub struct TestRenderAssetsCommand {} + +impl Prepare for TestRenderAssetsCommand { + fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec> { + let no_fail_fast = flags + .contains(Flag::KEEP_GOING) + .then_some("--no-fail-fast") + .unwrap_or_default(); + + vec![PreparedCommand::new::( + cmd!(sh, "cargo test --test render_asset_leaks {no_fail_fast}"), + "Please fix failing tests in output above.", + )] + } +} From 8c7d72a3134b2951761989be42ffa4aca4dc3f52 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 16:26:11 -0300 Subject: [PATCH 20/32] Remove duplicated comment --- tools/ci/src/commands/test.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index fcaf0866725b2..b1342b199adee 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -27,8 +27,6 @@ impl Prepare for TestCommand { PreparedCommand::new::( cmd!( sh, - // `--benches` runs each benchmark once in order to verify that they behave - // correctly and do not panic. "cargo test --tests {no_fail_fast} -- --skip check_mesh --skip check_standard_material" ), "Please fix failing tests in output above.", From 24f1fca97a9a508447b3c1198139984c403febf3 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 16:37:31 -0300 Subject: [PATCH 21/32] Update CI to run render asset tests --- .github/workflows/ci.yml | 12 +++++++++--- .github/workflows/weekly.yml | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6245b696aa2fa..db5586559b09b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,15 +49,21 @@ jobs: sudo add-apt-repository ppa:kisak/turtle -y sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests - if: ${{ runner.os != 'linux' }} # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run tests through xvfb + - name: Build & run render assets tests through xvfb if: ${{ runner.os == 'linux' }} # See tools/ci/src/main.rs for the commands this runs - run: xvfb-run cargo run -p ci -- test + run: xvfb-run cargo run -p ci -- test-render-assets + env: + RUSTFLAGS: "-C debuginfo=0 -D warnings" + - name: Build & run render assets tests + # Windows crashes on this due to headless app + if: ${{ runner.os == 'macOS' }} + # See tools/ci/src/main.rs for the commands this runs + run: cargo run -p ci -- test-render-assets env: RUSTFLAGS: "-C debuginfo=0 -D warnings" diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index 60f113eee12e1..9e5b0ff227148 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -50,15 +50,21 @@ jobs: sudo add-apt-repository ppa:kisak/turtle -y sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests - if: ${{ runner.os != 'linux' }} # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run tests through xvfb + - name: Build & run render assets tests through xvfb if: ${{ runner.os == 'linux' }} # See tools/ci/src/main.rs for the commands this runs - run: xvfb-run cargo run -p ci -- test + run: xvfb-run cargo run -p ci -- test-render-assets + env: + RUSTFLAGS: "-C debuginfo=0 -D warnings" + - name: Build & run render assets tests + # Windows crashes on this due to headless app + if: ${{ runner.os == 'macOS' }} + # See tools/ci/src/main.rs for the commands this runs + run: cargo run -p ci -- test-render-assets env: RUSTFLAGS: "-C debuginfo=0 -D warnings" From bf85333a0d2e2f146f3261a1b090e39a9d2e85dc Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 23:11:05 -0300 Subject: [PATCH 22/32] Enable `WindowPlugin` but with no window --- tests/render_asset_leaks.rs | 57 ++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 2f862ff649304..dfcf558c0f175 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -6,7 +6,6 @@ use std::time::Duration; use bevy::{ app::{App, PluginGroup, Startup, Update}, asset::{Asset, Assets, Handle}, - audio::AudioPlugin, color::Color, diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, ecs::{ @@ -21,7 +20,7 @@ use bevy::{ diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, mesh::{Mesh, Meshable, RenderMesh}, }, - window::WindowPlugin, + window::{ExitCondition, WindowPlugin}, winit::WinitPlugin, DefaultPlugins, }; @@ -32,9 +31,12 @@ fn check_mesh_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() @@ -63,9 +65,12 @@ fn check_standard_material_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() @@ -96,9 +101,12 @@ fn check_mesh_churn_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() @@ -124,9 +132,12 @@ fn check_standard_material_churn_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() @@ -158,9 +169,12 @@ fn check_mesh_churn_insert_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() @@ -189,9 +203,12 @@ fn check_standard_material_churn_insert_leak() { app.add_plugins(( DefaultPlugins .build() - .disable::() - .disable::() - .disable::(), + .set(WindowPlugin { + primary_window: None, + exit_condition: ExitCondition::DontExit, + ..Default::default() + }) + .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, ..Default::default() From 326b0ab7af9ff03a7e1066a79abe525c8451b9c2 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 24 May 2025 23:27:39 -0300 Subject: [PATCH 23/32] Refactor shared code into method --- tests/render_asset_leaks.rs | 81 +++++++------------------------------ 1 file changed, 14 insertions(+), 67 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index dfcf558c0f175..4a61a06515541 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -25,8 +25,7 @@ use bevy::{ DefaultPlugins, }; -#[test] -fn check_mesh_leak() { +fn base_app() -> App { let mut app = App::new(); app.add_plugins(( DefaultPlugins @@ -41,6 +40,14 @@ fn check_mesh_leak() { wait_duration: Duration::ZERO, ..Default::default() }, + )); + app +} + +#[test] +fn check_mesh_leak() { + let mut app = base_app(); + app.add_plugins(( RenderAssetDiagnosticPlugin::::new(" meshes"), MeshAllocatorDiagnosticPlugin, )) @@ -61,20 +68,8 @@ fn check_mesh_leak() { #[ignore = "FIXME Failing test"] #[test] fn check_standard_material_leak() { - let mut app = App::new(); + let mut app = base_app(); app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..Default::default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..Default::default() - }, RenderAssetDiagnosticPlugin::>::new(" materials"), MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) @@ -97,20 +92,8 @@ fn check_standard_material_leak() { #[test] fn check_mesh_churn_leak() { - let mut app = App::new(); + let mut app = base_app(); app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..Default::default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..Default::default() - }, RenderAssetDiagnosticPlugin::::new(" meshes"), MeshAllocatorDiagnosticPlugin, )) @@ -128,20 +111,8 @@ fn check_mesh_churn_leak() { #[ignore = "FIXME Failing test"] #[test] fn check_standard_material_churn_leak() { - let mut app = App::new(); + let mut app = base_app(); app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..Default::default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..Default::default() - }, RenderAssetDiagnosticPlugin::>::new(" materials"), MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) @@ -165,20 +136,8 @@ fn check_standard_material_churn_leak() { #[ignore = "FIXME Failing test"] #[test] fn check_mesh_churn_insert_leak() { - let mut app = App::new(); + let mut app = base_app(); app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..Default::default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..Default::default() - }, RenderAssetDiagnosticPlugin::::new(" meshes"), MeshAllocatorDiagnosticPlugin, )) @@ -199,20 +158,8 @@ fn check_mesh_churn_insert_leak() { #[ignore = "FIXME Failing test"] #[test] fn check_standard_material_churn_insert_leak() { - let mut app = App::new(); + let mut app = base_app(); app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..Default::default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..Default::default() - }, RenderAssetDiagnosticPlugin::>::new(" materials"), MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), )) From a8faad757d8e6555617302a9e9a963c2fd788a33 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sun, 25 May 2025 17:59:25 -0300 Subject: [PATCH 24/32] Add issues to ignore reason --- tests/render_asset_leaks.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 4a61a06515541..bc7f850654458 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -65,7 +65,7 @@ fn check_mesh_leak() { } } -#[ignore = "FIXME Failing test"] +#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_leak() { let mut app = base_app(); @@ -108,7 +108,7 @@ fn check_mesh_churn_leak() { } } -#[ignore = "FIXME Failing test"] +#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_churn_leak() { let mut app = base_app(); @@ -133,7 +133,7 @@ fn check_standard_material_churn_leak() { } } -#[ignore = "FIXME Failing test"] +#[ignore = "FIXME Issue #18808"] #[test] fn check_mesh_churn_insert_leak() { let mut app = base_app(); @@ -155,7 +155,7 @@ fn check_mesh_churn_insert_leak() { } } -#[ignore = "FIXME Failing test"] +#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_churn_insert_leak() { let mut app = base_app(); From 39eb0747f018270dd8f06808877bb1adee2dc2e7 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Sat, 7 Jun 2025 10:02:47 -0300 Subject: [PATCH 25/32] Remove `ignore` from fixed tests --- tests/render_asset_leaks.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index bc7f850654458..1fca11985a303 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -65,7 +65,6 @@ fn check_mesh_leak() { } } -#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_leak() { let mut app = base_app(); @@ -108,7 +107,6 @@ fn check_mesh_churn_leak() { } } -#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_churn_leak() { let mut app = base_app(); @@ -155,7 +153,6 @@ fn check_mesh_churn_insert_leak() { } } -#[ignore = "FIXME Issue #18882, #19035"] #[test] fn check_standard_material_churn_insert_leak() { let mut app = base_app(); From ec22af4eaa9b65facba6f1cb820a4506e70aa676 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Fri, 27 Jun 2025 15:22:31 -0300 Subject: [PATCH 26/32] First attempt to use `NOOP` --- tests/render_asset_leaks.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs index 1fca11985a303..5f4a42efac7fc 100644 --- a/tests/render_asset_leaks.rs +++ b/tests/render_asset_leaks.rs @@ -20,10 +20,15 @@ use bevy::{ diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, mesh::{Mesh, Meshable, RenderMesh}, }, + utils::default, window::{ExitCondition, WindowPlugin}, winit::WinitPlugin, DefaultPlugins, }; +use bevy_render::{ + settings::{Backends, RenderCreation, WgpuSettings}, + RenderPlugin, +}; fn base_app() -> App { let mut app = App::new(); @@ -33,12 +38,19 @@ fn base_app() -> App { .set(WindowPlugin { primary_window: None, exit_condition: ExitCondition::DontExit, - ..Default::default() + ..default() + }) + .set(RenderPlugin { + render_creation: RenderCreation::Automatic(WgpuSettings { + backends: Some(Backends::NOOP), + ..default() + }), + ..default() }) .disable::(), LogDiagnosticsPlugin { wait_duration: Duration::ZERO, - ..Default::default() + ..default() }, )); app From 370041f25c7090cf4af07db91ff8d85ba31172b6 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Fri, 27 Jun 2025 15:30:32 -0300 Subject: [PATCH 27/32] Revert CI changes --- .github/workflows/ci.yml | 19 -- .github/workflows/weekly.yml | 19 -- tests/render_asset_leaks.rs | 278 -------------------- tools/ci/src/ci.rs | 3 - tools/ci/src/commands/mod.rs | 2 - tools/ci/src/commands/test_render_assets.rs | 23 -- 6 files changed, 344 deletions(-) delete mode 100644 tests/render_asset_leaks.rs delete mode 100644 tools/ci/src/commands/test_render_assets.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11ad51c82d491..28a3c16a5b87c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,30 +42,11 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install Linux dependencies uses: ./.github/actions/install-linux-deps - # At some point this may be merged into `install-linux-deps`, but for now it is its own step. - - name: Install additional Linux dependencies for Vulkan - if: ${{ runner.os == 'linux' }} - run: | - sudo add-apt-repository ppa:kisak/turtle -y - sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run render assets tests through xvfb - if: ${{ runner.os == 'linux' }} - # See tools/ci/src/main.rs for the commands this runs - run: xvfb-run cargo run -p ci -- test-render-assets - env: - RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run render assets tests - # Windows crashes on this due to headless app - if: ${{ runner.os == 'macOS' }} - # See tools/ci/src/main.rs for the commands this runs - run: cargo run -p ci -- test-render-assets - env: - RUSTFLAGS: "-C debuginfo=0 -D warnings" ci: runs-on: ubuntu-latest diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index 9e5b0ff227148..b4ddffdb9dbb7 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -43,30 +43,11 @@ jobs: - uses: dtolnay/rust-toolchain@beta - name: Install Linux dependencies uses: ./.github/actions/install-linux-deps - # At some point this may be merged into `install-linux-deps`, but for now it is its own step. - - name: Install additional Linux dependencies for Vulkan - if: ${{ runner.os == 'linux' }} - run: | - sudo add-apt-repository ppa:kisak/turtle -y - sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: Build & run tests # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- test env: RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run render assets tests through xvfb - if: ${{ runner.os == 'linux' }} - # See tools/ci/src/main.rs for the commands this runs - run: xvfb-run cargo run -p ci -- test-render-assets - env: - RUSTFLAGS: "-C debuginfo=0 -D warnings" - - name: Build & run render assets tests - # Windows crashes on this due to headless app - if: ${{ runner.os == 'macOS' }} - # See tools/ci/src/main.rs for the commands this runs - run: cargo run -p ci -- test-render-assets - env: - RUSTFLAGS: "-C debuginfo=0 -D warnings" lint: runs-on: ubuntu-latest diff --git a/tests/render_asset_leaks.rs b/tests/render_asset_leaks.rs deleted file mode 100644 index 5f4a42efac7fc..0000000000000 --- a/tests/render_asset_leaks.rs +++ /dev/null @@ -1,278 +0,0 @@ -//! Tests if touching mutably a asset that gets extracted to the render world -//! causes a leak - -use std::time::Duration; - -use bevy::{ - app::{App, PluginGroup, Startup, Update}, - asset::{Asset, Assets, Handle}, - color::Color, - diagnostic::{DiagnosticsStore, LogDiagnosticsPlugin}, - ecs::{ - resource::Resource, - system::{Commands, Res, ResMut}, - }, - math::primitives::Sphere, - pbr::{ - diagnostic::MaterialAllocatorDiagnosticPlugin, Material, PreparedMaterial, StandardMaterial, - }, - render::{ - diagnostic::{MeshAllocatorDiagnosticPlugin, RenderAssetDiagnosticPlugin}, - mesh::{Mesh, Meshable, RenderMesh}, - }, - utils::default, - window::{ExitCondition, WindowPlugin}, - winit::WinitPlugin, - DefaultPlugins, -}; -use bevy_render::{ - settings::{Backends, RenderCreation, WgpuSettings}, - RenderPlugin, -}; - -fn base_app() -> App { - let mut app = App::new(); - app.add_plugins(( - DefaultPlugins - .build() - .set(WindowPlugin { - primary_window: None, - exit_condition: ExitCondition::DontExit, - ..default() - }) - .set(RenderPlugin { - render_creation: RenderCreation::Automatic(WgpuSettings { - backends: Some(Backends::NOOP), - ..default() - }), - ..default() - }) - .disable::(), - LogDiagnosticsPlugin { - wait_duration: Duration::ZERO, - ..default() - }, - )); - app -} - -#[test] -fn check_mesh_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::::new(" meshes"), - MeshAllocatorDiagnosticPlugin, - )) - .add_systems(Startup, mesh_setup) - .add_systems( - Update, - (touch_mutably::, crash_on_mesh_leak_detection), - ); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[test] -fn check_standard_material_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), - )) - .add_systems(Startup, mesh_setup) - .add_systems( - Update, - ( - touch_mutably::, - crash_on_material_leak_detection::, - ), - ); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[test] -fn check_mesh_churn_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::::new(" meshes"), - MeshAllocatorDiagnosticPlugin, - )) - .add_systems(Startup, mesh_setup) - .add_systems(Update, (churn::, crash_on_mesh_leak_detection)); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[test] -fn check_standard_material_churn_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), - )) - .add_systems(Startup, mesh_setup) - .add_systems( - Update, - ( - churn::, - crash_on_material_leak_detection::, - ), - ); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[ignore = "FIXME Issue #18808"] -#[test] -fn check_mesh_churn_insert_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::::new(" meshes"), - MeshAllocatorDiagnosticPlugin, - )) - .add_systems(Startup, mesh_setup) - .add_systems( - Update, - (churn_using_insert::, crash_on_mesh_leak_detection), - ); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[test] -fn check_standard_material_churn_insert_leak() { - let mut app = base_app(); - app.add_plugins(( - RenderAssetDiagnosticPlugin::>::new(" materials"), - MaterialAllocatorDiagnosticPlugin::::new(" standard materials"), - )) - .add_systems(Startup, mesh_setup) - .add_systems( - Update, - ( - churn_using_insert::, - crash_on_material_leak_detection::, - ), - ); - - app.finish(); - app.cleanup(); - - for _ in 0..100 { - app.update(); - } -} - -#[derive(Resource)] -struct Leaker(Vec>); - -fn mesh_setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - bevy::log::info!("Mesh setup"); - - let mut mesh_leaker = Vec::with_capacity(16); - for _ in 0..16 { - mesh_leaker.push(meshes.add(Sphere::new(1.).mesh().ico(79).unwrap())); - } - commands.insert_resource(Leaker(mesh_leaker)); - let mut material_leaker = Vec::with_capacity(1000); - for _ in 0..1000 { - material_leaker.push(materials.add(Color::WHITE)); - } - commands.insert_resource(Leaker(material_leaker)); -} - -fn touch_mutably(mut assets: ResMut>) { - for _ in assets.iter_mut() {} -} - -fn churn(mut assets: ResMut>, mut leaker: ResMut>) { - let all_ids = leaker.0.drain(..).collect::>(); - for id in all_ids { - let asset = assets.remove(id.id()).unwrap(); - leaker.0.push(assets.add(asset)); - } -} - -fn churn_using_insert(mut assets: ResMut>, leaker: Res>) { - for id in &leaker.0 { - let asset = assets.remove(id.id()).unwrap(); - assets.insert(id.id(), asset); - } -} - -fn crash_on_mesh_leak_detection(diagnostic_store: Res) { - if let (Some(render_meshes), Some(slab_size), Some(allocations)) = ( - diagnostic_store - .get_measurement( - &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), - ) - .filter(|diag| diag.value > 0.), - diagnostic_store - .get_measurement(MeshAllocatorDiagnosticPlugin::slabs_size_diagnostic_path()) - .filter(|diag| diag.value > 0.), - diagnostic_store - .get_measurement(MeshAllocatorDiagnosticPlugin::allocations_diagnostic_path()) - .filter(|diag| diag.value > 0.), - ) { - assert!( - allocations.value < render_meshes.value * 10., - "Detected leak" - ); - assert!( - slab_size.value < (1 << 30) as f64, - "Exceeded 1GB of allocations." - ); - } -} - -fn crash_on_material_leak_detection(diagnostic_store: Res) { - if let (Some(materials), Some(slab_size), Some(allocations)) = ( - diagnostic_store - .get_measurement( - &RenderAssetDiagnosticPlugin::>::render_asset_diagnostic_path(), - ) - .filter(|diag| diag.value > 0.), - diagnostic_store - .get_measurement(&MaterialAllocatorDiagnosticPlugin::::slabs_size_diagnostic_path()) - .filter(|diag| diag.value > 0.), - diagnostic_store - .get_measurement(&MaterialAllocatorDiagnosticPlugin::::allocations_diagnostic_path()) - .filter(|diag| diag.value > 0.), - ) { - assert!(allocations.value < materials.value * 10., "Detected leak"); - assert!( - slab_size.value < (1 << 30) as f64, - "Exceeded 1GB of allocations." - ); - } -} diff --git a/tools/ci/src/ci.rs b/tools/ci/src/ci.rs index f34a8bf6e4803..8b8556d90ac05 100644 --- a/tools/ci/src/ci.rs +++ b/tools/ci/src/ci.rs @@ -76,7 +76,6 @@ impl CI { cmds.append(&mut commands::FormatCommand::default().prepare(sh, args)); cmds.append(&mut commands::ClippyCommand::default().prepare(sh, args)); cmds.append(&mut commands::TestCommand::default().prepare(sh, args)); - cmds.append(&mut commands::TestRenderAssetsCommand::default().prepare(sh, args)); cmds.append(&mut commands::TestCheckCommand::default().prepare(sh, args)); cmds.append(&mut commands::IntegrationTestCommand::default().prepare(sh, args)); cmds.append( @@ -110,7 +109,6 @@ enum Commands { Format(commands::FormatCommand), Clippy(commands::ClippyCommand), Test(commands::TestCommand), - TestRenderAssets(commands::TestRenderAssetsCommand), TestCheck(commands::TestCheckCommand), IntegrationTest(commands::IntegrationTestCommand), IntegrationTestCheck(commands::IntegrationTestCheckCommand), @@ -133,7 +131,6 @@ impl Prepare for Commands { Commands::Format(subcommand) => subcommand.prepare(sh, args), Commands::Clippy(subcommand) => subcommand.prepare(sh, args), Commands::Test(subcommand) => subcommand.prepare(sh, args), - Commands::TestRenderAssets(subcommand) => subcommand.prepare(sh, args), Commands::TestCheck(subcommand) => subcommand.prepare(sh, args), Commands::IntegrationTest(subcommand) => subcommand.prepare(sh, args), Commands::IntegrationTestCheck(subcommand) => subcommand.prepare(sh, args), diff --git a/tools/ci/src/commands/mod.rs b/tools/ci/src/commands/mod.rs index fffb5089afab7..9247ab201627b 100644 --- a/tools/ci/src/commands/mod.rs +++ b/tools/ci/src/commands/mod.rs @@ -14,7 +14,6 @@ pub use integration_test_clean::*; pub use lints::*; pub use test::*; pub use test_check::*; -pub use test_render_assets::*; mod bench_check; mod clippy; @@ -32,4 +31,3 @@ mod integration_test_clean; mod lints; mod test; mod test_check; -mod test_render_assets; diff --git a/tools/ci/src/commands/test_render_assets.rs b/tools/ci/src/commands/test_render_assets.rs deleted file mode 100644 index 877655ed86615..0000000000000 --- a/tools/ci/src/commands/test_render_assets.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{args::Args, Prepare, PreparedCommand}; -use argh::FromArgs; -use xshell::cmd; - -/// Runs all tests (except for doc tests). -#[derive(FromArgs, Default)] -#[argh(subcommand, name = "test-render-assets")] -pub struct TestRenderAssetsCommand {} - -impl Prepare for TestRenderAssetsCommand { - fn prepare<'a>(&self, sh: &'a xshell::Shell, args: Args) -> Vec> { - let no_fail_fast = args.keep_going(); - let jobs = args.build_jobs(); - let test_threads = args.test_threads(); - let jobs_ref = jobs.as_ref(); - let test_threads_ref = test_threads.as_ref(); - - vec![PreparedCommand::new::( - cmd!(sh, "cargo test --test render_asset_leaks {no_fail_fast...} {jobs_ref...} -- {test_threads_ref...}"), - "Please fix failing tests in output above.", - )] - } -} From e740b003082021a1cb399fcf22cd2036a996641b Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Fri, 27 Jun 2025 15:32:29 -0300 Subject: [PATCH 28/32] Revert one last CI change --- tools/ci/src/commands/test.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index 94876298c4ad6..86057d977696f 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -19,17 +19,10 @@ impl Prepare for TestCommand { sh, // `--benches` runs each benchmark once in order to verify that they behave // correctly and do not panic. - "cargo test --workspace --lib --bins --benches {no_fail_fast...} {jobs...} -- {test_threads...}" + "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" ), "Please fix failing tests in output above.", - ), - PreparedCommand::new::( - cmd!( - sh, - "cargo test --tests {no_fail_fast...} -- --skip check_mesh --skip check_standard_material" - ), - "Please fix failing tests in output above.", - ), + ) ] } } From d17b7c64c79b44d4962c9c50b943409bfa8d7697 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 2 Jul 2025 17:13:28 -0300 Subject: [PATCH 29/32] Unnecessary formatting --- tools/ci/src/commands/test.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index 86057d977696f..70c8142e815c6 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -13,8 +13,7 @@ impl Prepare for TestCommand { let jobs = args.build_jobs(); let test_threads = args.test_threads(); - vec![ - PreparedCommand::new::( + vec![PreparedCommand::new::( cmd!( sh, // `--benches` runs each benchmark once in order to verify that they behave @@ -22,7 +21,6 @@ impl Prepare for TestCommand { "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" ), "Please fix failing tests in output above.", - ) - ] + )] } } From 26a5bcfa97bfa7f01e65d9928757aef034f91a7f Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 2 Jul 2025 17:14:02 -0300 Subject: [PATCH 30/32] Too much white space --- tools/ci/src/commands/test.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index 70c8142e815c6..a904e59c46c99 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -14,13 +14,13 @@ impl Prepare for TestCommand { let test_threads = args.test_threads(); vec![PreparedCommand::new::( - cmd!( - sh, - // `--benches` runs each benchmark once in order to verify that they behave - // correctly and do not panic. - "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" - ), - "Please fix failing tests in output above.", + cmd!( + sh, + // `--benches` runs each benchmark once in order to verify that they behave + // correctly and do not panic. + "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" + ), + "Please fix failing tests in output above.", )] } } From 156f59650271b41ad46a8cb2f32ead7931beb78e Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 2 Jul 2025 19:08:34 -0300 Subject: [PATCH 31/32] Fix errors due to new dynamic materials API --- crates/bevy_pbr/src/diagnostic.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/bevy_pbr/src/diagnostic.rs b/crates/bevy_pbr/src/diagnostic.rs index 3902dfb969a53..79affaaea3ddf 100644 --- a/crates/bevy_pbr/src/diagnostic.rs +++ b/crates/bevy_pbr/src/diagnostic.rs @@ -1,4 +1,7 @@ -use core::{any::type_name, marker::PhantomData}; +use core::{ + any::{type_name, Any, TypeId}, + marker::PhantomData, +}; use bevy_app::{Plugin, PreUpdate}; use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}; @@ -6,7 +9,7 @@ use bevy_ecs::{resource::Resource, system::Res}; use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use bevy_render::{Extract, ExtractSchedule, RenderApp}; -use crate::{Material, MaterialBindGroupAllocator}; +use crate::{Material, MaterialBindGroupAllocators}; pub struct MaterialAllocatorDiagnosticPlugin { suffix: &'static str, @@ -103,17 +106,19 @@ fn add_material_allocator_measurement( ); } -fn measure_allocator( +fn measure_allocator( measurements: Extract>>, - allocator: Res>, + allocators: Res, ) { - measurements - .slabs - .store(allocator.slab_count(), Ordering::Relaxed); - measurements - .slabs_size - .store(allocator.slabs_size(), Ordering::Relaxed); - measurements - .allocations - .store(allocator.allocations(), Ordering::Relaxed); + if let Some(allocator) = allocators.get(&TypeId::of::()) { + measurements + .slabs + .store(allocator.slab_count(), Ordering::Relaxed); + measurements + .slabs_size + .store(allocator.slabs_size(), Ordering::Relaxed); + measurements + .allocations + .store(allocator.allocations(), Ordering::Relaxed); + } } From 00f06f9c51ddb15e1c6df13de4e3ff81fb601146 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 2 Jul 2025 19:23:00 -0300 Subject: [PATCH 32/32] `cargo test --benches` does not accept `--test-threads` --- tools/ci/src/commands/test.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/ci/src/commands/test.rs b/tools/ci/src/commands/test.rs index a904e59c46c99..86a0461be4b7c 100644 --- a/tools/ci/src/commands/test.rs +++ b/tools/ci/src/commands/test.rs @@ -13,12 +13,19 @@ impl Prepare for TestCommand { let jobs = args.build_jobs(); let test_threads = args.test_threads(); + let jobs_ref = &jobs; vec![PreparedCommand::new::( + cmd!( + sh, + "cargo test --workspace --lib --bins --tests {no_fail_fast...} {jobs_ref...} -- {test_threads...}" + ), + "Please fix failing tests in output above.", + ),PreparedCommand::new::( cmd!( sh, // `--benches` runs each benchmark once in order to verify that they behave // correctly and do not panic. - "cargo test --workspace --lib --bins --tests --benches {no_fail_fast...} {jobs...} -- {test_threads...}" + "cargo test --benches {no_fail_fast...} {jobs_ref...}" ), "Please fix failing tests in output above.", )]