bevy_render/diagnostic/
mod.rs

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