Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/diagnostic/mod.rs
6596 views
1
//! Infrastructure for recording render diagnostics.
2
//!
3
//! For more info, see [`RenderDiagnosticsPlugin`].
4
5
pub(crate) mod internal;
6
#[cfg(feature = "tracing-tracy")]
7
mod tracy_gpu;
8
9
use alloc::{borrow::Cow, sync::Arc};
10
use core::marker::PhantomData;
11
12
use bevy_app::{App, Plugin, PreUpdate};
13
14
use crate::{renderer::RenderAdapterInfo, RenderApp};
15
16
use self::internal::{
17
sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp,
18
};
19
20
use crate::renderer::{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)]
49
pub struct RenderDiagnosticsPlugin;
50
51
impl 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.
75
pub 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.
123
pub struct TimeSpanGuard<'a, R: ?Sized, E> {
124
recorder: &'a R,
125
marker: PhantomData<E>,
126
}
127
128
impl<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
136
impl<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.
145
pub struct PassSpanGuard<'a, R: ?Sized, P> {
146
recorder: &'a R,
147
marker: PhantomData<P>,
148
}
149
150
impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
151
/// End the span. You have to provide the same pass 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
158
impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
159
fn drop(&mut self) {
160
panic!("PassSpanScope::end was never called")
161
}
162
}
163
164
impl<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
}
189
190