Path: blob/main/crates/bevy_render/src/diagnostic/mod.rs
9367 views
//! Infrastructure for recording render diagnostics.1//!2//! For more info, see [`RenderDiagnosticsPlugin`].34mod erased_render_asset_diagnostic_plugin;5pub(crate) mod internal;6mod mesh_allocator_diagnostic_plugin;7mod render_asset_diagnostic_plugin;8#[cfg(feature = "tracing-tracy")]9mod tracy_gpu;1011use alloc::{borrow::Cow, sync::Arc};12use core::marker::PhantomData;13use wgpu::{BufferSlice, CommandEncoder};1415use bevy_app::{App, Plugin, PreUpdate};1617use crate::{renderer::RenderAdapterInfo, RenderApp};1819use self::internal::{sync_diagnostics, Pass, RenderDiagnosticsMutex, WriteTimestamp};20pub use self::{21erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin,22internal::DiagnosticsRecorder, mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin,23render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin,24};2526use crate::renderer::{RenderDevice, RenderQueue};2728/// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass,29/// as well as pipeline statistics (number of primitives, number of shader invocations, etc).30///31/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource,32/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue).33///34/// To record diagnostics in your own passes:35/// 1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder).36///37/// It won't do anything unless [`RenderDiagnosticsPlugin`] is present,38/// so you're free to omit `#[cfg]` clauses.39/// ```ignore40/// let diagnostics = render_context.diagnostic_recorder();41/// ```42/// 2. Begin the span inside a command encoder, or a render/compute pass encoder.43/// ```ignore44/// let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows");45/// ```46/// 3. End the span, providing the encoder (or the same render/compute pass).47/// ```ignore48/// time_span.end(render_context.command_encoder());49/// ```50///51/// # Supported platforms52/// Timestamp queries and pipeline statistics are currently supported only on Vulkan and DX12.53/// On other platforms (Metal, WebGPU, WebGL2) only CPU time will be recorded.54#[derive(Default)]55pub struct RenderDiagnosticsPlugin;5657impl Plugin for RenderDiagnosticsPlugin {58fn build(&self, app: &mut App) {59let render_diagnostics_mutex = RenderDiagnosticsMutex::default();60app.insert_resource(render_diagnostics_mutex.clone())61.add_systems(PreUpdate, sync_diagnostics);6263if let Some(render_app) = app.get_sub_app_mut(RenderApp) {64render_app.insert_resource(render_diagnostics_mutex);65}66}6768fn finish(&self, app: &mut App) {69let Some(render_app) = app.get_sub_app_mut(RenderApp) else {70return;71};7273let adapter_info = render_app.world().resource::<RenderAdapterInfo>();74let device = render_app.world().resource::<RenderDevice>();75let queue = render_app.world().resource::<RenderQueue>();76render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue));77}78}7980/// Allows recording diagnostic spans.81pub trait RecordDiagnostics: Send + Sync {82/// Begin a time span, which will record elapsed CPU and GPU time.83///84/// Returns a guard, which will panic on drop unless you end the span.85fn time_span<E, N>(&self, encoder: &mut E, name: N) -> TimeSpanGuard<'_, Self, E>86where87E: WriteTimestamp,88N: Into<Cow<'static, str>>,89{90self.begin_time_span(encoder, name.into());91TimeSpanGuard {92recorder: self,93marker: PhantomData,94}95}9697/// Begin a pass span, which will record elapsed CPU and GPU time,98/// as well as pipeline statistics on supported platforms.99///100/// Returns a guard, which will panic on drop unless you end the span.101fn pass_span<P, N>(&self, pass: &mut P, name: N) -> PassSpanGuard<'_, Self, P>102where103P: Pass,104N: Into<Cow<'static, str>>,105{106let name = name.into();107self.begin_pass_span(pass, name.clone());108PassSpanGuard {109recorder: self,110name,111marker: PhantomData,112}113}114115/// Reads a f32 from the specified buffer and uploads it as a diagnostic.116///117/// The provided buffer slice must be 4 bytes long, and the buffer must have [`wgpu::BufferUsages::COPY_SRC`];118fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)119where120N: Into<Cow<'static, str>>;121122/// Reads a u32 from the specified buffer and uploads it as a diagnostic.123///124/// The provided buffer slice must be 4 bytes long, and the buffer must have [`wgpu::BufferUsages::COPY_SRC`];125fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)126where127N: Into<Cow<'static, str>>;128129#[doc(hidden)]130fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>);131132#[doc(hidden)]133fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E);134135#[doc(hidden)]136fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>);137138#[doc(hidden)]139fn end_pass_span<P: Pass>(&self, pass: &mut P);140}141142/// Guard returned by [`RecordDiagnostics::time_span`].143///144/// Will panic on drop unless [`TimeSpanGuard::end`] is called.145pub struct TimeSpanGuard<'a, R: ?Sized, E> {146recorder: &'a R,147marker: PhantomData<E>,148}149150impl<R: RecordDiagnostics + ?Sized, E: WriteTimestamp> TimeSpanGuard<'_, R, E> {151/// End the span.152pub fn end(self, encoder: &mut E) {153self.recorder.end_time_span(encoder);154core::mem::forget(self);155}156}157158impl<R: ?Sized, E> Drop for TimeSpanGuard<'_, R, E> {159fn drop(&mut self) {160panic!("TimeSpanScope::end was never called")161}162}163164/// Guard returned by [`RecordDiagnostics::pass_span`].165///166/// Will panic on drop unless [`PassSpanGuard::end`] is called.167pub struct PassSpanGuard<'a, R: ?Sized, P> {168recorder: &'a R,169name: Cow<'static, str>,170marker: PhantomData<P>,171}172173impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {174/// End the span. You have to provide the same pass which was used to begin the span.175pub fn end(self, pass: &mut P) {176self.recorder.end_pass_span(pass);177core::mem::forget(self);178}179}180181impl<R: ?Sized, P> Drop for PassSpanGuard<'_, R, P> {182fn drop(&mut self) {183panic!("PassSpanGuard::end was never called for {}", self.name)184}185}186187impl<T: RecordDiagnostics> RecordDiagnostics for Option<Arc<T>> {188fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)189where190N: Into<Cow<'static, str>>,191{192if let Some(recorder) = &self {193recorder.record_f32(command_encoder, buffer, name);194}195}196197fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)198where199N: Into<Cow<'static, str>>,200{201if let Some(recorder) = &self {202recorder.record_u32(command_encoder, buffer, name);203}204}205206fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {207if let Some(recorder) = &self {208recorder.begin_time_span(encoder, name);209}210}211212fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {213if let Some(recorder) = &self {214recorder.end_time_span(encoder);215}216}217218fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {219if let Some(recorder) = &self {220recorder.begin_pass_span(pass, name);221}222}223224fn end_pass_span<P: Pass>(&self, pass: &mut P) {225if let Some(recorder) = &self {226recorder.end_pass_span(pass);227}228}229}230231impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> {232fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)233where234N: Into<Cow<'static, str>>,235{236if let Some(recorder) = self {237recorder.record_f32(command_encoder, buffer, name);238}239}240241fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)242where243N: Into<Cow<'static, str>>,244{245if let Some(recorder) = self {246recorder.record_u32(command_encoder, buffer, name);247}248}249250fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, name: Cow<'static, str>) {251if let Some(recorder) = self {252recorder.begin_time_span(encoder, name);253}254}255256fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {257if let Some(recorder) = self {258recorder.end_time_span(encoder);259}260}261262fn begin_pass_span<P: Pass>(&self, pass: &mut P, name: Cow<'static, str>) {263if let Some(recorder) = self {264recorder.begin_pass_span(pass, name);265}266}267268fn end_pass_span<P: Pass>(&self, pass: &mut P) {269if let Some(recorder) = self {270recorder.end_pass_span(pass);271}272}273}274275276