1mod erased_render_asset_diagnostic_plugin;
6pub(crate) mod internal;
7mod mesh_allocator_diagnostic_plugin;
8mod render_asset_diagnostic_plugin;
9#[cfg(feature = "tracing-tracy")]
10mod tracy_gpu;
11
12use alloc::{borrow::Cow, sync::Arc};
13use bevy_ecs::{
14 schedule::IntoScheduleConfigs,
15 system::{Res, ResMut},
16 world::{FromWorld, World},
17};
18use core::marker::PhantomData;
19use wgpu::{BufferSlice, CommandEncoder};
20
21use bevy_app::{App, Plugin, PreUpdate};
22
23use crate::{
24 renderer::{PendingCommandBuffers, RenderGraph, RenderGraphSystems},
25 GpuResourceAppExt, Render, RenderApp, RenderSystems,
26};
27
28use self::internal::{sync_diagnostics, Pass, RenderDiagnosticsMutex, WriteTimestamp};
29pub use self::{
30 erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin,
31 internal::DiagnosticsRecorder, mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin,
32 render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin,
33};
34
35use crate::renderer::RenderDevice;
36
37#[derive(Default)]
64pub struct RenderDiagnosticsPlugin;
65
66impl Plugin for RenderDiagnosticsPlugin {
67 fn build(&self, app: &mut App) {
68 let render_diagnostics_mutex = RenderDiagnosticsMutex::default();
69 app.insert_resource(render_diagnostics_mutex.clone())
70 .add_systems(PreUpdate, sync_diagnostics);
71
72 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
73 render_app.insert_resource(render_diagnostics_mutex);
74 }
75 }
76
77 fn finish(&self, app: &mut App) {
78 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
79 return;
80 };
81
82 render_app.init_gpu_resource::<DiagnosticsRecorder>();
83
84 render_app
85 .add_systems(
86 Render,
87 begin_diagnostics_frame
88 .after(RenderSystems::ExtractCommands)
89 .before(RenderSystems::PrepareAssets),
90 )
91 .add_systems(
92 RenderGraph,
93 (
94 resolve_encoder
95 .after(RenderGraphSystems::Render)
96 .before(RenderGraphSystems::Submit),
97 finish_diagnostics_frame.in_set(RenderGraphSystems::Finish),
98 ),
99 );
100 }
101}
102
103impl FromWorld for DiagnosticsRecorder {
104 fn from_world(world: &mut World) -> Self {
105 DiagnosticsRecorder::new(world.resource(), world.resource(), world.resource())
106 }
107}
108
109pub fn begin_diagnostics_frame(mut recorder: ResMut<DiagnosticsRecorder>) {
111 recorder.begin_frame();
112}
113
114pub fn resolve_encoder(
116 mut recorder: ResMut<DiagnosticsRecorder>,
117 render_device: Res<RenderDevice>,
118 mut pending_buffers: ResMut<PendingCommandBuffers>,
119) {
120 let mut encoder =
121 render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
122 recorder.resolve(&mut encoder);
123 pending_buffers.push_encoder(encoder, "resolve_diagnostics");
124}
125
126fn finish_diagnostics_frame(
128 mut recorder: ResMut<DiagnosticsRecorder>,
129 render_device: Res<RenderDevice>,
130 mutex: Res<RenderDiagnosticsMutex>,
131) {
132 let mutex = mutex.0.clone();
133 recorder.finish_frame(&render_device, move |diagnostics| {
134 *mutex.lock().unwrap() = Some(diagnostics);
135 });
136}
137
138pub trait RecordDiagnostics: Send + Sync {
140 fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>
144 where
145 E: WriteTimestamp,
146 N: Into<Cow<'static, str>>,
147 {
148 self.begin_time_span(encoder, name.into());
149 TimeSpanGuard {
150 recorder: self,
151 marker: PhantomData,
152 }
153 }
154
155 fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>
160 where
161 P: Pass,
162 N: Into<Cow<'static, str>>,
163 {
164 let name = name.into();
165 self.begin_pass_span(pass, name.clone());
166 PassSpanGuard {
167 recorder: self,
168 name,
169 marker: PhantomData,
170 }
171 }
172
173 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
177 where
178 N: Into<Cow<'static, str>>;
179
180 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
184 where
185 N: Into<Cow<'static, str>>;
186
187 #[doc(hidden)]
188 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);
189
190 #[doc(hidden)]
191 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);
192
193 #[doc(hidden)]
194 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);
195
196 #[doc(hidden)]
197 fn end_pass_span<P: Pass>(&self, pass: &mut P);
198}
199
200pub struct TimeSpanGuard<'a, R: ?Sized, E> {
204 recorder: &'a R,
205 marker: PhantomData<E>,
206}
207
208impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {
209 pub fn end(self, encoder: &mut E) {
211 self.recorder.end_time_span(encoder);
212 core::mem::forget(self);
213 }
214}
215
216impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {
217 fn drop(&mut self) {
218 bevy_log::error!("TimeSpanScope::end was never called");
219 }
220}
221
222pub struct PassSpanGuard<'a, R: ?Sized, P> {
226 recorder: &'a R,
227 name: Cow<'static, str>,
228 marker: PhantomData<P>,
229}
230
231impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
232 pub fn end(self, pass: &mut P) {
234 self.recorder.end_pass_span(pass);
235 core::mem::forget(self);
236 }
237}
238
239impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
240 fn drop(&mut self) {
241 panic!("PassSpanGuard::end was never called for {}", self.name)
242 }
243}
244
245impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {
246 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
247 where
248 N: Into<Cow<'static, str>>,
249 {
250 if let Some(recorder) = &self {
251 recorder.record_f32(command_encoder, buffer, name);
252 }
253 }
254
255 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
256 where
257 N: Into<Cow<'static, str>>,
258 {
259 if let Some(recorder) = &self {
260 recorder.record_u32(command_encoder, buffer, name);
261 }
262 }
263
264 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
265 if let Some(recorder) = &self {
266 recorder.begin_time_span(encoder, name);
267 }
268 }
269
270 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
271 if let Some(recorder) = &self {
272 recorder.end_time_span(encoder);
273 }
274 }
275
276 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
277 if let Some(recorder) = &self {
278 recorder.begin_pass_span(pass, name);
279 }
280 }
281
282 fn end_pass_span<P: Pass>(&self, pass: &mut P) {
283 if let Some(recorder) = &self {
284 recorder.end_pass_span(pass);
285 }
286 }
287}
288
289impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> {
290 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
291 where
292 N: Into<Cow<'static, str>>,
293 {
294 if let Some(recorder) = self {
295 recorder.record_f32(command_encoder, buffer, name);
296 }
297 }
298
299 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
300 where
301 N: Into<Cow<'static, str>>,
302 {
303 if let Some(recorder) = self {
304 recorder.record_u32(command_encoder, buffer, name);
305 }
306 }
307
308 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
309 if let Some(recorder) = self {
310 recorder.begin_time_span(encoder, name);
311 }
312 }
313
314 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
315 if let Some(recorder) = self {
316 recorder.end_time_span(encoder);
317 }
318 }
319
320 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
321 if let Some(recorder) = self {
322 recorder.begin_pass_span(pass, name);
323 }
324 }
325
326 fn end_pass_span<P: Pass>(&self, pass: &mut P) {
327 if let Some(recorder) = self {
328 recorder.end_pass_span(pass);
329 }
330 }
331}