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, RenderApp,
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.add_systems(
85 RenderGraph,
86 (
87 begin_diagnostics_frame.in_set(RenderGraphSystems::Begin),
88 resolve_encoder
89 .after(RenderGraphSystems::Render)
90 .before(RenderGraphSystems::Submit),
91 finish_diagnostics_frame.in_set(RenderGraphSystems::Finish),
92 ),
93 );
94 }
95}
96
97impl FromWorld for DiagnosticsRecorder {
98 fn from_world(world: &mut World) -> Self {
99 DiagnosticsRecorder::new(world.resource(), world.resource(), world.resource())
100 }
101}
102
103pub fn begin_diagnostics_frame(mut recorder: ResMut<DiagnosticsRecorder>) {
105 recorder.begin_frame();
106}
107
108pub fn resolve_encoder(
110 mut recorder: ResMut<DiagnosticsRecorder>,
111 render_device: Res<RenderDevice>,
112 mut pending_buffers: ResMut<PendingCommandBuffers>,
113) {
114 let mut encoder =
115 render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
116 recorder.resolve(&mut encoder);
117 pending_buffers.push_encoder(encoder);
118}
119
120fn finish_diagnostics_frame(
122 mut recorder: ResMut<DiagnosticsRecorder>,
123 render_device: Res<RenderDevice>,
124 mutex: Res<RenderDiagnosticsMutex>,
125) {
126 let mutex = mutex.0.clone();
127 recorder.finish_frame(&render_device, move |diagnostics| {
128 *mutex.lock().unwrap() = Some(diagnostics);
129 });
130}
131
132pub trait RecordDiagnostics: Send + Sync {
134 fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>
138 where
139 E: WriteTimestamp,
140 N: Into<Cow<'static, str>>,
141 {
142 self.begin_time_span(encoder, name.into());
143 TimeSpanGuard {
144 recorder: self,
145 marker: PhantomData,
146 }
147 }
148
149 fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>
154 where
155 P: Pass,
156 N: Into<Cow<'static, str>>,
157 {
158 let name = name.into();
159 self.begin_pass_span(pass, name.clone());
160 PassSpanGuard {
161 recorder: self,
162 name,
163 marker: PhantomData,
164 }
165 }
166
167 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
171 where
172 N: Into<Cow<'static, str>>;
173
174 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
178 where
179 N: Into<Cow<'static, str>>;
180
181 #[doc(hidden)]
182 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);
183
184 #[doc(hidden)]
185 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);
186
187 #[doc(hidden)]
188 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);
189
190 #[doc(hidden)]
191 fn end_pass_span<P: Pass>(&self, pass: &mut P);
192}
193
194pub struct TimeSpanGuard<'a, R: ?Sized, E> {
198 recorder: &'a R,
199 marker: PhantomData<E>,
200}
201
202impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {
203 pub fn end(self, encoder: &mut E) {
205 self.recorder.end_time_span(encoder);
206 core::mem::forget(self);
207 }
208}
209
210impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {
211 fn drop(&mut self) {
212 bevy_log::error!("TimeSpanScope::end was never called");
213 }
214}
215
216pub struct PassSpanGuard<'a, R: ?Sized, P> {
220 recorder: &'a R,
221 name: Cow<'static, str>,
222 marker: PhantomData<P>,
223}
224
225impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
226 pub fn end(self, pass: &mut P) {
228 self.recorder.end_pass_span(pass);
229 core::mem::forget(self);
230 }
231}
232
233impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
234 fn drop(&mut self) {
235 panic!("PassSpanGuard::end was never called for {}", self.name)
236 }
237}
238
239impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {
240 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
241 where
242 N: Into<Cow<'static, str>>,
243 {
244 if let Some(recorder) = &self {
245 recorder.record_f32(command_encoder, buffer, name);
246 }
247 }
248
249 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
250 where
251 N: Into<Cow<'static, str>>,
252 {
253 if let Some(recorder) = &self {
254 recorder.record_u32(command_encoder, buffer, name);
255 }
256 }
257
258 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
259 if let Some(recorder) = &self {
260 recorder.begin_time_span(encoder, name);
261 }
262 }
263
264 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
265 if let Some(recorder) = &self {
266 recorder.end_time_span(encoder);
267 }
268 }
269
270 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
271 if let Some(recorder) = &self {
272 recorder.begin_pass_span(pass, name);
273 }
274 }
275
276 fn end_pass_span<P: Pass>(&self, pass: &mut P) {
277 if let Some(recorder) = &self {
278 recorder.end_pass_span(pass);
279 }
280 }
281}
282
283impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> {
284 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
285 where
286 N: Into<Cow<'static, str>>,
287 {
288 if let Some(recorder) = self {
289 recorder.record_f32(command_encoder, buffer, name);
290 }
291 }
292
293 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
294 where
295 N: Into<Cow<'static, str>>,
296 {
297 if let Some(recorder) = self {
298 recorder.record_u32(command_encoder, buffer, name);
299 }
300 }
301
302 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
303 if let Some(recorder) = self {
304 recorder.begin_time_span(encoder, name);
305 }
306 }
307
308 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
309 if let Some(recorder) = self {
310 recorder.end_time_span(encoder);
311 }
312 }
313
314 fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
315 if let Some(recorder) = self {
316 recorder.begin_pass_span(pass, name);
317 }
318 }
319
320 fn end_pass_span<P: Pass>(&self, pass: &mut P) {
321 if let Some(recorder) = self {
322 recorder.end_pass_span(pass);
323 }
324 }
325}