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