bevy_render/diagnostic/
mod.rs

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