diff --git a/crates/bevy_pbr/src/diagnostic.rs b/crates/bevy_pbr/src/diagnostic.rs new file mode 100644 index 0000000000000..79affaaea3ddf --- /dev/null +++ b/crates/bevy_pbr/src/diagnostic.rs @@ -0,0 +1,124 @@ +use core::{ + any::{type_name, Any, TypeId}, + 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, MaterialBindGroupAllocators}; + +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 { + 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(self.suffix), + ) + .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>>, + allocators: Res, +) { + 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); + } +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f0e6fa90d724d..5f86e6a759e6e 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 780ac8e10c235..aff3ab04c9336 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -590,6 +590,45 @@ impl MaterialBindGroupAllocator { } } } + + /// 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/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..cd0b70dbc502c --- /dev/null +++ b/crates/bevy_render/src/diagnostic/mesh_allocator_diagnostic_plugin.rs @@ -0,0 +1,91 @@ +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 +static MESH_ALLOCATOR_SLABS: DiagnosticPath = DiagnosticPath::const_new("mesh_allocator_slabs"); + +/// Total size of all slabs +static MESH_ALLOCATOR_SLABS_SIZE: DiagnosticPath = + DiagnosticPath::const_new("mesh_allocator_slabs_size"); + +/// Number of meshes allocated into slabs +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.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); + } + } +} + +#[derive(Debug, Default, Resource)] +struct MeshAllocatorMeasurements { + slabs: AtomicUsize, + slabs_size: AtomicU64, + allocations: AtomicUsize, +} + +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 + }); + diagnostics.add_measurement(&MESH_ALLOCATOR_ALLOCATIONS, || { + 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_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 197b9f4e7f2a3..e973a49fe1ff2 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -3,6 +3,8 @@ //! For more info, see [`RenderDiagnosticsPlugin`]. pub(crate) mod internal; +mod mesh_allocator_diagnostic_plugin; +mod render_asset_diagnostic_plugin; #[cfg(feature = "tracing-tracy")] mod tracy_gpu; @@ -16,6 +18,10 @@ use crate::{renderer::RenderAdapterInfo, RenderApp}; use self::internal::{ sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, }; +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..347bae7338797 --- /dev/null +++ b/crates/bevy_render/src/diagnostic/render_asset_diagnostic_plugin.rs @@ -0,0 +1,77 @@ +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, + _phantom: PhantomData, +} + +impl RenderAssetDiagnosticPlugin { + pub fn new(suffix: &'static str) -> Self { + Self { + suffix, + _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::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::); + } + } +} + +#[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( + &RenderAssetDiagnosticPlugin::::render_asset_diagnostic_path(), + || measurements.assets.load(Ordering::Relaxed) as f64, + ); +} + +fn measure_render_asset( + measurements: Extract>>, + assets: Res>, +) { + measurements + .assets + .store(assets.iter().count(), Ordering::Relaxed); +} diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index c171cf3957d96..96e6c625beebd 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -172,6 +172,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 @@ -409,6 +418,20 @@ 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() + } + + 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( diff --git a/release-content/release-notes/render-assets-diagnostics.md b/release-content/release-notes/render-assets-diagnostics.md new file mode 100644 index 0000000000000..707bfe9f7e095 --- /dev/null +++ b/release-content/release-notes/render-assets-diagnostics.md @@ -0,0 +1,16 @@ +--- +title: Render Assets diagnostics +authors: ["@hukasu"] +pull_requests: [19311] +--- + +## Goals + +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 +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`. 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.", )]