Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/diagnostic/mod.rs
9367 views
1
//! Infrastructure for recording render diagnostics.
2
//!
3
//! For more info, see [`RenderDiagnosticsPlugin`].
4
5
mod erased_render_asset_diagnostic_plugin;
6
pub(crate) mod internal;
7
mod mesh_allocator_diagnostic_plugin;
8
mod render_asset_diagnostic_plugin;
9
#[cfg(feature = "tracing-tracy")]
10
mod tracy_gpu;
11
12
use alloc::{borrow::Cow, sync::Arc};
13
use core::marker::PhantomData;
14
use wgpu::{BufferSlice, CommandEncoder};
15
16
use bevy_app::{App, Plugin, PreUpdate};
17
18
use crate::{renderer::RenderAdapterInfo, RenderApp};
19
20
use self::internal::{sync_diagnostics, Pass, RenderDiagnosticsMutex, WriteTimestamp};
21
pub use self::{
22
erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin,
23
internal::DiagnosticsRecorder, mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin,
24
render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin,
25
};
26
27
use crate::renderer::{RenderDevice, RenderQueue};
28
29
/// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass,
30
/// as well as pipeline statistics (number of primitives, number of shader invocations, etc).
31
///
32
/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource,
33
/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue).
34
///
35
/// To record diagnostics in your own passes:
36
/// 1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder).
37
///
38
/// It won't do anything unless [`RenderDiagnosticsPlugin`] is present,
39
/// so you're free to omit `#[cfg]` clauses.
40
/// ```ignore
41
/// let diagnostics = render_context.diagnostic_recorder();
42
/// ```
43
/// 2. Begin the span inside a command encoder, or a render/compute pass encoder.
44
/// ```ignore
45
/// let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows");
46
/// ```
47
/// 3. End the span, providing the encoder (or the same render/compute pass).
48
/// ```ignore
49
/// time_span.end(render_context.command_encoder());
50
/// ```
51
///
52
/// # Supported platforms
53
/// Timestamp queries and pipeline statistics are currently supported only on Vulkan and DX12.
54
/// On other platforms (Metal, WebGPU, WebGL2) only CPU time will be recorded.
55
#[derive(Default)]
56
pub struct RenderDiagnosticsPlugin;
57
58
impl Plugin for RenderDiagnosticsPlugin {
59
fn build(&self, app: &mut App) {
60
let render_diagnostics_mutex = RenderDiagnosticsMutex::default();
61
app.insert_resource(render_diagnostics_mutex.clone())
62
.add_systems(PreUpdate, sync_diagnostics);
63
64
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
65
render_app.insert_resource(render_diagnostics_mutex);
66
}
67
}
68
69
fn finish(&self, app: &mut App) {
70
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
71
return;
72
};
73
74
let adapter_info = render_app.world().resource::<RenderAdapterInfo>();
75
let device = render_app.world().resource::<RenderDevice>();
76
let queue = render_app.world().resource::<RenderQueue>();
77
render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue));
78
}
79
}
80
81
/// Allows recording diagnostic spans.
82
pub trait RecordDiagnostics: Send + Sync {
83
/// Begin a time span, which will record elapsed CPU and GPU time.
84
///
85
/// Returns a guard, which will panic on drop unless you end the span.
86
fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>
87
where
88
E: WriteTimestamp,
89
N: Into<Cow<'static, str>>,
90
{
91
self.begin_time_span(encoder, name.into());
92
TimeSpanGuard {
93
recorder: self,
94
marker: PhantomData,
95
}
96
}
97
98
/// Begin a pass span, which will record elapsed CPU and GPU time,
99
/// as well as pipeline statistics on supported platforms.
100
///
101
/// Returns a guard, which will panic on drop unless you end the span.
102
fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>
103
where
104
P: Pass,
105
N: Into<Cow<'static, str>>,
106
{
107
let name = name.into();
108
self.begin_pass_span(pass, name.clone());
109
PassSpanGuard {
110
recorder: self,
111
name,
112
marker: PhantomData,
113
}
114
}
115
116
/// Reads a f32 from the specified buffer and uploads it as a diagnostic.
117
///
118
/// The provided buffer slice must be 4 bytes long, and the buffer must have [`wgpu::BufferUsages::COPY_SRC`];
119
fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
120
where
121
N: Into<Cow<'static, str>>;
122
123
/// Reads a u32 from the specified buffer and uploads it as a diagnostic.
124
///
125
/// The provided buffer slice must be 4 bytes long, and the buffer must have [`wgpu::BufferUsages::COPY_SRC`];
126
fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
127
where
128
N: Into<Cow<'static, str>>;
129
130
#[doc(hidden)]
131
fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);
132
133
#[doc(hidden)]
134
fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);
135
136
#[doc(hidden)]
137
fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);
138
139
#[doc(hidden)]
140
fn end_pass_span<P: Pass>(&self, pass: &mut P);
141
}
142
143
/// Guard returned by [`RecordDiagnostics::time_span`].
144
///
145
/// Will panic on drop unless [`TimeSpanGuard::end`] is called.
146
pub struct TimeSpanGuard<'a, R: ?Sized, E> {
147
recorder: &'a R,
148
marker: PhantomData<E>,
149
}
150
151
impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {
152
/// End the span.
153
pub fn end(self, encoder: &mut E) {
154
self.recorder.end_time_span(encoder);
155
core::mem::forget(self);
156
}
157
}
158
159
impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {
160
fn drop(&mut self) {
161
panic!("TimeSpanScope::end was never called")
162
}
163
}
164
165
/// Guard returned by [`RecordDiagnostics::pass_span`].
166
///
167
/// Will panic on drop unless [`PassSpanGuard::end`] is called.
168
pub struct PassSpanGuard<'a, R: ?Sized, P> {
169
recorder: &'a R,
170
name: Cow<'static, str>,
171
marker: PhantomData<P>,
172
}
173
174
impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
175
/// End the span. You have to provide the same pass which was used to begin the span.
176
pub fn end(self, pass: &mut P) {
177
self.recorder.end_pass_span(pass);
178
core::mem::forget(self);
179
}
180
}
181
182
impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {
183
fn drop(&mut self) {
184
panic!("PassSpanGuard::end was never called for {}", self.name)
185
}
186
}
187
188
impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {
189
fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
190
where
191
N: Into<Cow<'static, str>>,
192
{
193
if let Some(recorder) = &self {
194
recorder.record_f32(command_encoder, buffer, name);
195
}
196
}
197
198
fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
199
where
200
N: Into<Cow<'static, str>>,
201
{
202
if let Some(recorder) = &self {
203
recorder.record_u32(command_encoder, buffer, name);
204
}
205
}
206
207
fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
208
if let Some(recorder) = &self {
209
recorder.begin_time_span(encoder, name);
210
}
211
}
212
213
fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
214
if let Some(recorder) = &self {
215
recorder.end_time_span(encoder);
216
}
217
}
218
219
fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
220
if let Some(recorder) = &self {
221
recorder.begin_pass_span(pass, name);
222
}
223
}
224
225
fn end_pass_span<P: Pass>(&self, pass: &mut P) {
226
if let Some(recorder) = &self {
227
recorder.end_pass_span(pass);
228
}
229
}
230
}
231
232
impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> {
233
fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
234
where
235
N: Into<Cow<'static, str>>,
236
{
237
if let Some(recorder) = self {
238
recorder.record_f32(command_encoder, buffer, name);
239
}
240
}
241
242
fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
243
where
244
N: Into<Cow<'static, str>>,
245
{
246
if let Some(recorder) = self {
247
recorder.record_u32(command_encoder, buffer, name);
248
}
249
}
250
251
fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {
252
if let Some(recorder) = self {
253
recorder.begin_time_span(encoder, name);
254
}
255
}
256
257
fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
258
if let Some(recorder) = self {
259
recorder.end_time_span(encoder);
260
}
261
}
262
263
fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {
264
if let Some(recorder) = self {
265
recorder.begin_pass_span(pass, name);
266
}
267
}
268
269
fn end_pass_span<P: Pass>(&self, pass: &mut P) {
270
if let Some(recorder) = self {
271
recorder.end_pass_span(pass);
272
}
273
}
274
}
275
276