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 pub fn slabs_diagnostic_path() -> DiagnosticPath {
42 DiagnosticPath::from_components(["material_allocator_slabs", type_name::<M>()])
43 }
44 pub fn slabs_size_diagnostic_path() -> DiagnosticPath {
46 DiagnosticPath::from_components(["material_allocator_slabs_size", type_name::<M>()])
47 }
48 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}