bevy_render/diagnostic/
mod.rs

1//! Infrastructure for recording render diagnostics.
2//!
3//! For more info, see [`RenderDiagnosticsPlugin`].
4
5pub(crate) mod internal;
6#[cfg(feature = "tracing-tracy")]
7mod tracy_gpu;
8
9use alloc::{borrow::Cow, sync::Arc};
10use core::marker::PhantomData;
11
12use bevy_app::{App, Plugin, PreUpdate};
13
14use crate::{renderer::RenderAdapterInfo, RenderApp};
15
16use self::internal::{
17    sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp,
18};
19
20use super::{RenderDevice, RenderQueue};
21
22/// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass,
23/// as well as pipeline statistics (number of primitives, number of shader invocations, etc).
24///
25/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource,
26/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue).
27///
28/// To record diagnostics in your own passes:
29///  1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder).
30///
31///     It won't do anything unless [`RenderDiagnosticsPlugin`] is present,
32///     so you're free to omit `#[cfg]` clauses.
33///     ```ignore
34///     let diagnostics = render_context.diagnostic_recorder();
35///     ```
36///  2. Begin the span inside a command encoder, or a render/compute pass encoder.
37///     ```ignore
38///     let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows");
39///     ```
40///  3. End the span, providing the same encoder.
41///     ```ignore
42///     time_span.end(render_context.command_encoder());
43///     ```
44///
45/// # Supported platforms
46/// Timestamp queries and pipeline statistics are currently supported only on Vulkan and DX12.
47/// On other platforms (Metal, WebGPU, WebGL2) only CPU time will be recorded.
48#[derive(Default)]
49pub struct RenderDiagnosticsPlugin;
50
51impl Plugin for RenderDiagnosticsPlugin {
52    fn build(&self, app: &mut App) {
53        let render_diagnostics_mutex = RenderDiagnosticsMutex::default();
54        app.insert_resource(render_diagnostics_mutex.clone())
55            .add_systems(PreUpdate, sync_diagnostics);
56
57        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
58            render_app.insert_resource(render_diagnostics_mutex);
59        }
60    }
61
62    fn finish(&self, app: &mut App) {
63        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
64            return;
65        };
66
67        let adapter_info = render_app.world().resource::<RenderAdapterInfo>();
68        let device = render_app.world().resource::<RenderDevice>();
69        let queue = render_app.world().resource::<RenderQueue>();
70        render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue));
71    }
72}
73
74/// Allows recording diagnostic spans.
75pub trait RecordDiagnostics: Send + Sync {
76    /// Begin a time span, which will record elapsed CPU and GPU time.
77    ///
78    /// Returns a guard, which will panic on drop unless you end the span.
79    fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>
80    where
81        E: WriteTimestamp,
82        N: Into<Cow<'static, str>>,
83    {
84        self.begin_time_span(encoder, name.into());
85        TimeSpanGuard {
86            recorder: self,
87            marker: PhantomData,
88        }
89    }
90
91    /// Begin a pass span, which will record elapsed CPU and GPU time,
92    /// as well as pipeline statistics on supported platforms.
93    ///
94    /// Returns a guard, which will panic on drop unless you end the span.
95    fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>
96    where
97        P: Pass,
98        N: Into<Cow<'static, str>>,
99    {
100        self.begin_pass_span(pass, name.into());
101        PassSpanGuard {
102            recorder: self,
103            marker: PhantomData,
104        }
105    }
106
107    #[doc(hidden)]
108    fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);
109
110    #[doc(hidden)]
111    fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);
112
113    #[doc(hidden)]
114    fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);
115
116    #[doc(hidden)]
117    fn end_pass_span<P: Pass>(&self, pass: &mut P);
118}
119
120/// Guard returned by [`RecordDiagnostics::time_span`].
121///
122/// Will panic on drop unless [`TimeSpanGuard::end`] is called.
123pub struct TimeSpanGuard<'a, R: ?Sized, E> {
124    recorder: &'a R,
125    marker: PhantomData<E>,
126}
127
128impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {
129    /// End the span. You have to provide the same encoder which was used to begin the span.
130    pub fn end(self, encoder: &mut E) {
131        self.recorder.end_time_span(encoder);
132        core::mem::forget(self);
133    }
134}
135
136impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {
137    fn drop(&mut self) {
138        panic!("TimeSpanScope::end was never called")
139    }
140}
141
142/// Guard returned by [`RecordDiagnostics::pass_span`].
143///
144/// Will panic on drop unless [`PassSpanGuard::end`] is called.
145pub struct PassSpanGuard<'a, R: ?Sized, P> {
146    recorder: &'a R,
147    marker: PhantomData<P>,
148}
149
150impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
151    /// End the span. You have to provide the same encoder which was used to begin the span.
152    pub fn end(self, pass: &mut P) {
153        self.recorder.end_pass_span(pass);
154        core::mem::forget(self);
155    }
156}
157
158impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
159    fn drop(&mut self) {
160        panic!("PassSpanScope::end was never called")
161    }
162}
163
164impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {
165    fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
166        if let Some(recorder) = &self {
167            recorder.begin_time_span(encoder, name);
168        }
169    }
170
171    fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
172        if let Some(recorder) = &self {
173            recorder.end_time_span(encoder);
174        }
175    }
176
177    fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
178        if let Some(recorder) = &self {
179            recorder.begin_pass_span(pass, name);
180        }
181    }
182
183    fn end_pass_span<P: Pass>(&self, pass: &mut P) {
184        if let Some(recorder) = &self {
185            recorder.end_pass_span(pass);
186        }
187    }
188}