Skip to main content

bevy_pbr/
diagnostic.rs

1use core::{
2    any::{type_name, Any, TypeId},
3    marker::PhantomData,
4};
5
6use bevy_app::{Plugin, PreUpdate};
7use bevy_diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic};
8use bevy_ecs::{resource::Resource, system::Res};
9use bevy_platform::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
10use bevy_render::{
11    material_bind_groups::MaterialBindGroupAllocators, Extract, ExtractSchedule, RenderApp,
12};
13
14use crate::Material;
15
16pub struct MaterialAllocatorDiagnosticPlugin<M: Material> {
17    suffix: &'static str,
18    _phantom: PhantomData<M>,
19}
20
21impl<M: Material> MaterialAllocatorDiagnosticPlugin<M> {
22    pub fn new(suffix: &'static str) -> Self {
23        Self {
24            suffix,
25            _phantom: PhantomData,
26        }
27    }
28}
29
30impl<M: Material> Default for MaterialAllocatorDiagnosticPlugin<M> {
31    fn default() -> Self {
32        Self {
33            suffix: " materials",
34            _phantom: PhantomData,
35        }
36    }
37}
38
39impl<M: Material> MaterialAllocatorDiagnosticPlugin<M> {
40    /// Get the [`DiagnosticPath`] for slab count
41    pub fn slabs_diagnostic_path() -> DiagnosticPath {
42        DiagnosticPath::from_components(["material_allocator_slabs", type_name::<M>()])
43    }
44    /// Get the [`DiagnosticPath`] for total slabs size
45    pub fn slabs_size_diagnostic_path() -> DiagnosticPath {
46        DiagnosticPath::from_components(["material_allocator_slabs_size", type_name::<M>()])
47    }
48    /// Get the [`DiagnosticPath`] for material allocations
49    pub fn allocations_diagnostic_path() -> DiagnosticPath {
50        DiagnosticPath::from_components(["material_allocator_allocations", type_name::<M>()])
51    }
52}
53
54impl<M: Material> Plugin for MaterialAllocatorDiagnosticPlugin<M> {
55    fn build(&self, app: &mut bevy_app::App) {
56        app.register_diagnostic(
57            Diagnostic::new(Self::slabs_diagnostic_path()).with_suffix(" slabs"),
58        )
59        .register_diagnostic(
60            Diagnostic::new(Self::slabs_size_diagnostic_path()).with_suffix(" bytes"),
61        )
62        .register_diagnostic(
63            Diagnostic::new(Self::allocations_diagnostic_path()).with_suffix(self.suffix),
64        )
65        .init_resource::<MaterialAllocatorMeasurements<M>>()
66        .add_systems(PreUpdate, add_material_allocator_measurement::<M>);
67
68        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
69            render_app.add_systems(ExtractSchedule, measure_allocator::<M>);
70        }
71    }
72}
73
74#[derive(Debug, Resource)]
75struct MaterialAllocatorMeasurements<M: Material> {
76    slabs: AtomicUsize,
77    slabs_size: AtomicUsize,
78    allocations: AtomicU64,
79    _phantom: PhantomData<M>,
80}
81
82impl<M: Material> Default for MaterialAllocatorMeasurements<M> {
83    fn default() -> Self {
84        Self {
85            slabs: AtomicUsize::default(),
86            slabs_size: AtomicUsize::default(),
87            allocations: AtomicU64::default(),
88            _phantom: PhantomData,
89        }
90    }
91}
92
93fn add_material_allocator_measurement<M: Material>(
94    mut diagnostics: Diagnostics,
95    measurements: Res<MaterialAllocatorMeasurements<M>>,
96) {
97    diagnostics.add_measurement(
98        &MaterialAllocatorDiagnosticPlugin::<M>::slabs_diagnostic_path(),
99        || measurements.slabs.load(Ordering::Relaxed) as f64,
100    );
101    diagnostics.add_measurement(
102        &MaterialAllocatorDiagnosticPlugin::<M>::slabs_size_diagnostic_path(),
103        || measurements.slabs_size.load(Ordering::Relaxed) as f64,
104    );
105    diagnostics.add_measurement(
106        &MaterialAllocatorDiagnosticPlugin::<M>::allocations_diagnostic_path(),
107        || measurements.allocations.load(Ordering::Relaxed) as f64,
108    );
109}
110
111fn measure_allocator<M: Material + Any>(
112    measurements: Extract<Res<MaterialAllocatorMeasurements<M>>>,
113    allocators: Res<MaterialBindGroupAllocators>,
114) {
115    if let Some(allocator) = allocators.get(&TypeId::of::<M>()) {
116        measurements
117            .slabs
118            .store(allocator.slab_count(), Ordering::Relaxed);
119        measurements
120            .slabs_size
121            .store(allocator.slabs_size(), Ordering::Relaxed);
122        measurements
123            .allocations
124            .store(allocator.allocations(), Ordering::Relaxed);
125    }
126}