Path: blob/main/crates/bevy_render/src/diagnostic/mod.rs
6596 views
//! Infrastructure for recording render diagnostics.1//!2//! For more info, see [`RenderDiagnosticsPlugin`].34pub(crate) mod internal;5#[cfg(feature = "tracing-tracy")]6mod tracy_gpu;78use alloc::{borrow::Cow, sync::Arc};9use core::marker::PhantomData;1011use bevy_app::{App, Plugin, PreUpdate};1213use crate::{renderer::RenderAdapterInfo, RenderApp};1415use self::internal::{16sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp,17};1819use crate::renderer::{RenderDevice, RenderQueue};2021/// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass,22/// as well as pipeline statistics (number of primitives, number of shader invocations, etc).23///24/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource,25/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue).26///27/// To record diagnostics in your own passes:28/// 1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder).29///30/// It won't do anything unless [`RenderDiagnosticsPlugin`] is present,31/// so you're free to omit `#[cfg]` clauses.32/// ```ignore33/// let diagnostics = render_context.diagnostic_recorder();34/// ```35/// 2. Begin the span inside a command encoder, or a render/compute pass encoder.36/// ```ignore37/// let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows");38/// ```39/// 3. End the span, providing the same encoder.40/// ```ignore41/// time_span.end(render_context.command_encoder());42/// ```43///44/// # Supported platforms45/// Timestamp queries and pipeline statistics are currently supported only on Vulkan and DX12.46/// On other platforms (Metal, WebGPU, WebGL2) only CPU time will be recorded.47#[derive(Default)]48pub struct RenderDiagnosticsPlugin;4950impl Plugin for RenderDiagnosticsPlugin {51fn build(&self, app: &mut App) {52let render_diagnostics_mutex = RenderDiagnosticsMutex::default();53app.insert_resource(render_diagnostics_mutex.clone())54.add_systems(PreUpdate, sync_diagnostics);5556if let Some(render_app) = app.get_sub_app_mut(RenderApp) {57render_app.insert_resource(render_diagnostics_mutex);58}59}6061fn finish(&self, app: &mut App) {62let Some(render_app) = app.get_sub_app_mut(RenderApp) else {63return;64};6566let adapter_info = render_app.world().resource::<RenderAdapterInfo>();67let device = render_app.world().resource::<RenderDevice>();68let queue = render_app.world().resource::<RenderQueue>();69render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue));70}71}7273/// Allows recording diagnostic spans.74pub trait RecordDiagnostics: Send + Sync {75/// Begin a time span, which will record elapsed CPU and GPU time.76///77/// Returns a guard, which will panic on drop unless you end the span.78fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>79where80E: WriteTimestamp,81N: Into<Cow<'static, str>>,82{83self.begin_time_span(encoder, name.into());84TimeSpanGuard {85recorder: self,86marker: PhantomData,87}88}8990/// Begin a pass span, which will record elapsed CPU and GPU time,91/// as well as pipeline statistics on supported platforms.92///93/// Returns a guard, which will panic on drop unless you end the span.94fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>95where96P: Pass,97N: Into<Cow<'static, str>>,98{99self.begin_pass_span(pass, name.into());100PassSpanGuard {101recorder: self,102marker: PhantomData,103}104}105106#[doc(hidden)]107fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);108109#[doc(hidden)]110fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);111112#[doc(hidden)]113fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);114115#[doc(hidden)]116fn end_pass_span<P: Pass>(&self, pass: &mut P);117}118119/// Guard returned by [`RecordDiagnostics::time_span`].120///121/// Will panic on drop unless [`TimeSpanGuard::end`] is called.122pub struct TimeSpanGuard<'a, R: ?Sized, E> {123recorder: &'a R,124marker: PhantomData<E>,125}126127impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {128/// End the span. You have to provide the same encoder which was used to begin the span.129pub fn end(self, encoder: &mut E) {130self.recorder.end_time_span(encoder);131core::mem::forget(self);132}133}134135impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {136fn drop(&mut self) {137panic!("TimeSpanScope::end was never called")138}139}140141/// Guard returned by [`RecordDiagnostics::pass_span`].142///143/// Will panic on drop unless [`PassSpanGuard::end`] is called.144pub struct PassSpanGuard<'a, R: ?Sized, P> {145recorder: &'a R,146marker: PhantomData<P>,147}148149impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {150/// End the span. You have to provide the same pass which was used to begin the span.151pub fn end(self, pass: &mut P) {152self.recorder.end_pass_span(pass);153core::mem::forget(self);154}155}156157impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {158fn drop(&mut self) {159panic!("PassSpanScope::end was never called")160}161}162163impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {164fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {165if let Some(recorder) = &self {166recorder.begin_time_span(encoder, name);167}168}169170fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {171if let Some(recorder) = &self {172recorder.end_time_span(encoder);173}174}175176fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {177if let Some(recorder) = &self {178recorder.begin_pass_span(pass, name);179}180}181182fn end_pass_span<P: Pass>(&self, pass: &mut P) {183if let Some(recorder) = &self {184recorder.end_pass_span(pass);185}186}187}188189190