Path: blob/main/crates/bevy_render/src/diagnostic/internal.rs
9396 views
use alloc::{borrow::Cow, sync::Arc};1use core::{2ops::{DerefMut, Range},3sync::atomic::{AtomicBool, Ordering},4};5use std::thread::{self, ThreadId};67use bevy_diagnostic::{Diagnostic, DiagnosticMeasurement, DiagnosticPath, DiagnosticsStore};8use bevy_ecs::resource::Resource;9use bevy_ecs::system::{Res, ResMut};10use bevy_platform::time::Instant;11use std::sync::Mutex;12use wgpu::{13Buffer, BufferDescriptor, BufferSize, BufferSlice, BufferUsages, CommandEncoder, ComputePass,14Device, Features, MapMode, PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType,15RenderPass,16};1718use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper};1920use super::RecordDiagnostics;2122// buffer offset must be divisible by 256, so this constant must be divisible by 32 (=256/8)23const MAX_TIMESTAMP_QUERIES: u32 = 256;24const MAX_PIPELINE_STATISTICS: u32 = 128;2526const TIMESTAMP_SIZE: u64 = 8;27const PIPELINE_STATISTICS_SIZE: u64 = 40;2829struct DiagnosticsRecorderInternal {30timestamp_period_ns: f32,31features: Features,32current_frame: Mutex<FrameData>,33submitted_frames: Vec<FrameData>,34finished_frames: Vec<FrameData>,35#[cfg(feature = "tracing-tracy")]36tracy_gpu_context: tracy_client::GpuContext,37}3839/// Records diagnostics into [`QuerySet`]'s keeping track of the mapping between40/// spans and indices to the corresponding entries in the [`QuerySet`].41#[derive(Resource)]42pub struct DiagnosticsRecorder(WgpuWrapper<DiagnosticsRecorderInternal>);4344impl DiagnosticsRecorder {45/// Creates the new `DiagnosticsRecorder`.46pub fn new(47adapter_info: &RenderAdapterInfo,48device: &RenderDevice,49queue: &RenderQueue,50) -> DiagnosticsRecorder {51let features = device.features();5253#[cfg(feature = "tracing-tracy")]54let tracy_gpu_context =55super::tracy_gpu::new_tracy_gpu_context(adapter_info, device, queue);56let _ = adapter_info; // Prevent unused variable warnings when tracing-tracy is not enabled5758DiagnosticsRecorder(WgpuWrapper::new(DiagnosticsRecorderInternal {59timestamp_period_ns: queue.get_timestamp_period(),60features,61current_frame: Mutex::new(FrameData::new(62device,63features,64#[cfg(feature = "tracing-tracy")]65tracy_gpu_context.clone(),66)),67submitted_frames: Vec::new(),68finished_frames: Vec::new(),69#[cfg(feature = "tracing-tracy")]70tracy_gpu_context,71}))72}7374fn current_frame_mut(&mut self) -> &mut FrameData {75self.0.current_frame.get_mut().expect("lock poisoned")76}7778fn current_frame_lock(&self) -> impl DerefMut<Target = FrameData> + '_ {79self.0.current_frame.lock().expect("lock poisoned")80}8182/// Begins recording diagnostics for a new frame.83pub fn begin_frame(&mut self) {84let internal = &mut self.0;85let mut idx = 0;86while idx < internal.submitted_frames.len() {87let timestamp = internal.timestamp_period_ns;88if internal.submitted_frames[idx].run_mapped_callback(timestamp) {89let removed = internal.submitted_frames.swap_remove(idx);90internal.finished_frames.push(removed);91} else {92idx += 1;93}94}9596self.current_frame_mut().begin();97}9899/// Copies data from [`QuerySet`]'s to a [`Buffer`], after which it can be downloaded to CPU.100///101/// Should be called before [`DiagnosticsRecorder::finish_frame`].102pub fn resolve(&mut self, encoder: &mut CommandEncoder) {103self.current_frame_mut().resolve(encoder);104}105106/// Finishes recording diagnostics for the current frame.107///108/// The specified `callback` will be invoked when diagnostics become available.109///110/// Should be called after [`DiagnosticsRecorder::resolve`],111/// and **after** all commands buffers have been queued.112pub fn finish_frame(113&mut self,114device: &RenderDevice,115callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static,116) {117#[cfg(feature = "tracing-tracy")]118let tracy_gpu_context = self.0.tracy_gpu_context.clone();119120let internal = &mut self.0;121internal122.current_frame123.get_mut()124.expect("lock poisoned")125.finish(callback);126127// reuse one of the finished frames, if we can128let new_frame = match internal.finished_frames.pop() {129Some(frame) => frame,130None => FrameData::new(131device,132internal.features,133#[cfg(feature = "tracing-tracy")]134tracy_gpu_context,135),136};137138let old_frame = core::mem::replace(139internal.current_frame.get_mut().expect("lock poisoned"),140new_frame,141);142internal.submitted_frames.push(old_frame);143}144}145146impl RecordDiagnostics for DiagnosticsRecorder {147fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)148where149N: Into<Cow<'static, str>>,150{151assert_eq!(152buffer.size(),153BufferSize::new(4).unwrap(),154"DiagnosticsRecorder::record_f32 buffer slice must be 4 bytes long"155);156assert!(157buffer.buffer().usage().contains(BufferUsages::COPY_SRC),158"DiagnosticsRecorder::record_f32 buffer must have BufferUsages::COPY_SRC"159);160161self.current_frame_lock()162.record_value(command_encoder, buffer, name.into(), true);163}164165fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)166where167N: Into<Cow<'static, str>>,168{169assert_eq!(170buffer.size(),171BufferSize::new(4).unwrap(),172"DiagnosticsRecorder::record_u32 buffer slice must be 4 bytes long"173);174assert!(175buffer.buffer().usage().contains(BufferUsages::COPY_SRC),176"DiagnosticsRecorder::record_u32 buffer must have BufferUsages::COPY_SRC"177);178179self.current_frame_lock()180.record_value(command_encoder, buffer, name.into(), false);181}182183fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, span_name: Cow<'static, str>) {184self.current_frame_lock()185.begin_time_span(encoder, span_name);186}187188fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {189self.current_frame_lock().end_time_span(encoder);190}191192fn begin_pass_span<P: Pass>(&self, pass: &mut P, span_name: Cow<'static, str>) {193self.current_frame_lock().begin_pass(pass, span_name);194}195196fn end_pass_span<P: Pass>(&self, pass: &mut P) {197self.current_frame_lock().end_pass(pass);198}199}200201struct SpanRecord {202thread_id: ThreadId,203path_range: Range<usize>,204pass_kind: Option<PassKind>,205begin_timestamp_index: Option<u32>,206end_timestamp_index: Option<u32>,207begin_instant: Option<Instant>,208end_instant: Option<Instant>,209pipeline_statistics_index: Option<u32>,210}211212struct FrameData {213device: Device,214timestamps_query_set: Option<QuerySet>,215num_timestamps: u32,216supports_timestamps_inside_passes: bool,217supports_timestamps_inside_encoders: bool,218pipeline_statistics_query_set: Option<QuerySet>,219num_pipeline_statistics: u32,220buffer_size: u64,221pipeline_statistics_buffer_offset: u64,222resolve_buffer: Option<Buffer>,223read_buffer: Option<Buffer>,224path_components: Vec<Cow<'static, str>>,225open_spans: Vec<SpanRecord>,226closed_spans: Vec<SpanRecord>,227value_buffers: Vec<(Buffer, Cow<'static, str>, bool)>,228is_mapped: Arc<AtomicBool>,229callback: Option<Box<dyn FnOnce(RenderDiagnostics) + Send + Sync + 'static>>,230#[cfg(feature = "tracing-tracy")]231tracy_gpu_context: tracy_client::GpuContext,232}233234impl FrameData {235fn new(236device: &RenderDevice,237features: Features,238#[cfg(feature = "tracing-tracy")] tracy_gpu_context: tracy_client::GpuContext,239) -> FrameData {240let wgpu_device = device.wgpu_device();241let mut buffer_size = 0;242243let timestamps_query_set = if features.contains(Features::TIMESTAMP_QUERY) {244buffer_size += u64::from(MAX_TIMESTAMP_QUERIES) * TIMESTAMP_SIZE;245Some(wgpu_device.create_query_set(&QuerySetDescriptor {246label: Some("timestamps_query_set"),247ty: QueryType::Timestamp,248count: MAX_TIMESTAMP_QUERIES,249}))250} else {251None252};253254let pipeline_statistics_buffer_offset = buffer_size;255256let pipeline_statistics_query_set =257if features.contains(Features::PIPELINE_STATISTICS_QUERY) {258buffer_size += u64::from(MAX_PIPELINE_STATISTICS) * PIPELINE_STATISTICS_SIZE;259Some(wgpu_device.create_query_set(&QuerySetDescriptor {260label: Some("pipeline_statistics_query_set"),261ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()),262count: MAX_PIPELINE_STATISTICS,263}))264} else {265None266};267268let (resolve_buffer, read_buffer) = if buffer_size > 0 {269let resolve_buffer = wgpu_device.create_buffer(&BufferDescriptor {270label: Some("render_statistics_resolve_buffer"),271size: buffer_size,272usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC,273mapped_at_creation: false,274});275let read_buffer = wgpu_device.create_buffer(&BufferDescriptor {276label: Some("render_statistics_read_buffer"),277size: buffer_size,278usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,279mapped_at_creation: false,280});281(Some(resolve_buffer), Some(read_buffer))282} else {283(None, None)284};285286FrameData {287device: wgpu_device.clone(),288timestamps_query_set,289num_timestamps: 0,290supports_timestamps_inside_passes: features291.contains(Features::TIMESTAMP_QUERY_INSIDE_PASSES),292supports_timestamps_inside_encoders: features293.contains(Features::TIMESTAMP_QUERY_INSIDE_ENCODERS),294pipeline_statistics_query_set,295num_pipeline_statistics: 0,296buffer_size,297pipeline_statistics_buffer_offset,298resolve_buffer,299read_buffer,300path_components: Vec::new(),301open_spans: Vec::new(),302closed_spans: Vec::new(),303value_buffers: Vec::new(),304is_mapped: Arc::new(AtomicBool::new(false)),305callback: None,306#[cfg(feature = "tracing-tracy")]307tracy_gpu_context,308}309}310311fn begin(&mut self) {312self.num_timestamps = 0;313self.num_pipeline_statistics = 0;314self.path_components.clear();315self.open_spans.clear();316self.closed_spans.clear();317}318319fn write_timestamp(320&mut self,321encoder: &mut impl WriteTimestamp,322is_inside_pass: bool,323) -> Option<u32> {324// `encoder.write_timestamp` is unsupported on WebGPU.325if !self.supports_timestamps_inside_encoders {326return None;327}328329if is_inside_pass && !self.supports_timestamps_inside_passes {330return None;331}332333if self.num_timestamps >= MAX_TIMESTAMP_QUERIES {334return None;335}336337let set = self.timestamps_query_set.as_ref()?;338let index = self.num_timestamps;339encoder.write_timestamp(set, index);340self.num_timestamps += 1;341Some(index)342}343344fn write_pipeline_statistics(345&mut self,346encoder: &mut impl WritePipelineStatistics,347) -> Option<u32> {348if self.num_pipeline_statistics >= MAX_PIPELINE_STATISTICS {349return None;350}351352let set = self.pipeline_statistics_query_set.as_ref()?;353let index = self.num_pipeline_statistics;354encoder.begin_pipeline_statistics_query(set, index);355self.num_pipeline_statistics += 1;356Some(index)357}358359fn open_span(360&mut self,361pass_kind: Option<PassKind>,362name: Cow<'static, str>,363) -> &mut SpanRecord {364let thread_id = thread::current().id();365366let parent = self.open_spans.iter().rfind(|v| v.thread_id == thread_id);367368let path_range = match &parent {369Some(parent) if parent.path_range.end == self.path_components.len() => {370parent.path_range.start..parent.path_range.end + 1371}372Some(parent) => {373self.path_components374.extend_from_within(parent.path_range.clone());375self.path_components.len() - parent.path_range.len()..self.path_components.len() + 1376}377None => self.path_components.len()..self.path_components.len() + 1,378};379380self.path_components.push(name);381382self.open_spans.push(SpanRecord {383thread_id,384path_range,385pass_kind,386begin_timestamp_index: None,387end_timestamp_index: None,388begin_instant: None,389end_instant: None,390pipeline_statistics_index: None,391});392393self.open_spans.last_mut().unwrap()394}395396fn close_span(&mut self) -> &mut SpanRecord {397let thread_id = thread::current().id();398399let iter = self.open_spans.iter();400let (index, _) = iter401.enumerate()402.rfind(|(_, v)| v.thread_id == thread_id)403.unwrap();404405let span = self.open_spans.swap_remove(index);406self.closed_spans.push(span);407self.closed_spans.last_mut().unwrap()408}409410fn record_value(411&mut self,412command_encoder: &mut CommandEncoder,413buffer: &BufferSlice,414name: Cow<'static, str>,415is_f32: bool,416) {417let dest_buffer = self.device.create_buffer(&BufferDescriptor {418label: Some(&format!("render_diagnostic_{name}")),419size: 4,420usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,421mapped_at_creation: false,422});423424command_encoder.copy_buffer_to_buffer(425buffer.buffer(),426buffer.offset(),427&dest_buffer,4280,429Some(buffer.size().into()),430);431432command_encoder.map_buffer_on_submit(&dest_buffer, MapMode::Read, .., |_| {});433434self.value_buffers.push((dest_buffer, name, is_f32));435}436437fn begin_time_span(&mut self, encoder: &mut impl WriteTimestamp, name: Cow<'static, str>) {438let begin_instant = Instant::now();439let begin_timestamp_index = self.write_timestamp(encoder, false);440441let span = self.open_span(None, name);442span.begin_instant = Some(begin_instant);443span.begin_timestamp_index = begin_timestamp_index;444}445446fn end_time_span(&mut self, encoder: &mut impl WriteTimestamp) {447let end_timestamp_index = self.write_timestamp(encoder, false);448449let span = self.close_span();450span.end_timestamp_index = end_timestamp_index;451span.end_instant = Some(Instant::now());452}453454fn begin_pass<P: Pass>(&mut self, pass: &mut P, name: Cow<'static, str>) {455let begin_instant = Instant::now();456457let begin_timestamp_index = self.write_timestamp(pass, true);458let pipeline_statistics_index = self.write_pipeline_statistics(pass);459460let span = self.open_span(Some(P::KIND), name);461span.begin_instant = Some(begin_instant);462span.begin_timestamp_index = begin_timestamp_index;463span.pipeline_statistics_index = pipeline_statistics_index;464}465466fn end_pass(&mut self, pass: &mut impl Pass) {467let end_timestamp_index = self.write_timestamp(pass, true);468469let span = self.close_span();470span.end_timestamp_index = end_timestamp_index;471472if span.pipeline_statistics_index.is_some() {473pass.end_pipeline_statistics_query();474}475476span.end_instant = Some(Instant::now());477}478479fn resolve(&mut self, encoder: &mut CommandEncoder) {480let Some(resolve_buffer) = &self.resolve_buffer else {481return;482};483484match &self.timestamps_query_set {485Some(set) if self.num_timestamps > 0 => {486encoder.resolve_query_set(set, 0..self.num_timestamps, resolve_buffer, 0);487}488_ => {}489}490491match &self.pipeline_statistics_query_set {492Some(set) if self.num_pipeline_statistics > 0 => {493encoder.resolve_query_set(494set,4950..self.num_pipeline_statistics,496resolve_buffer,497self.pipeline_statistics_buffer_offset,498);499}500_ => {}501}502503let Some(read_buffer) = &self.read_buffer else {504return;505};506507encoder.copy_buffer_to_buffer(resolve_buffer, 0, read_buffer, 0, self.buffer_size);508}509510fn diagnostic_path(&self, range: &Range<usize>, field: &str) -> DiagnosticPath {511DiagnosticPath::from_components(512core::iter::once("render")513.chain(self.path_components[range.clone()].iter().map(|v| &**v))514.chain(core::iter::once(field)),515)516}517518fn finish(&mut self, callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static) {519let Some(read_buffer) = &self.read_buffer else {520// we still have cpu timings, so let's use them521522let mut diagnostics = Vec::new();523524for span in &self.closed_spans {525if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {526diagnostics.push(RenderDiagnostic {527path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),528suffix: "ms",529value: (end - begin).as_secs_f64() * 1000.0,530});531}532}533534for (buffer, diagnostic_path, is_f32) in self.value_buffers.drain(..) {535let buffer = buffer.get_mapped_range(..);536diagnostics.push(RenderDiagnostic {537path: DiagnosticPath::from_components(538core::iter::once("render")539.chain(core::iter::once(diagnostic_path.as_ref())),540),541suffix: "",542value: if is_f32 {543f32::from_le_bytes((*buffer).try_into().unwrap()) as f64544} else {545u32::from_le_bytes((*buffer).try_into().unwrap()) as f64546},547});548}549550callback(RenderDiagnostics(diagnostics));551return;552};553554self.callback = Some(Box::new(callback));555556let is_mapped = self.is_mapped.clone();557read_buffer.slice(..).map_async(MapMode::Read, move |res| {558if let Err(e) = res {559bevy_log::warn!("Failed to download render statistics buffer: {e}");560return;561}562563is_mapped.store(true, Ordering::Release);564});565}566567// returns true if the frame is considered finished, false otherwise568fn run_mapped_callback(&mut self, timestamp_period_ns: f32) -> bool {569let Some(read_buffer) = &self.read_buffer else {570return true;571};572if !self.is_mapped.load(Ordering::Acquire) {573// need to wait more574return false;575}576let Some(callback) = self.callback.take() else {577return true;578};579580let data = read_buffer.slice(..).get_mapped_range();581582let timestamps = data[..(self.num_timestamps * 8) as usize]583.chunks(8)584.map(|v| u64::from_le_bytes(v.try_into().unwrap()))585.collect::<Vec<u64>>();586587let start = self.pipeline_statistics_buffer_offset as usize;588let len = (self.num_pipeline_statistics as usize) * 40;589let pipeline_statistics = data[start..start + len]590.chunks(8)591.map(|v| u64::from_le_bytes(v.try_into().unwrap()))592.collect::<Vec<u64>>();593594let mut diagnostics = Vec::new();595596for span in &self.closed_spans {597if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {598diagnostics.push(RenderDiagnostic {599path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),600suffix: "ms",601value: (end - begin).as_secs_f64() * 1000.0,602});603}604605if let (Some(begin), Some(end)) = (span.begin_timestamp_index, span.end_timestamp_index)606{607let begin = timestamps[begin as usize] as f64;608let end = timestamps[end as usize] as f64;609let value = (end - begin) * (timestamp_period_ns as f64) / 1e6;610611#[cfg(feature = "tracing-tracy")]612{613// Calling span_alloc() and end_zone() here instead of in open_span() and close_span() means that tracy does not know where each GPU command was recorded on the CPU timeline.614// Unfortunately we must do it this way, because tracy does not play nicely with multithreaded command recording. The start/end pairs would get all mixed up.615// The GPU spans themselves are still accurate though, and it's probably safe to assume that each GPU span in frame N belongs to the corresponding CPU render node span from frame N-1.616let name = &self.path_components[span.path_range.clone()].join("/");617let mut tracy_gpu_span =618self.tracy_gpu_context.span_alloc(name, "", "", 0).unwrap();619tracy_gpu_span.end_zone();620tracy_gpu_span.upload_timestamp_start(begin as i64);621tracy_gpu_span.upload_timestamp_end(end as i64);622}623624diagnostics.push(RenderDiagnostic {625path: self.diagnostic_path(&span.path_range, "elapsed_gpu"),626suffix: "ms",627value,628});629}630631if let Some(index) = span.pipeline_statistics_index {632let index = (index as usize) * 5;633634if span.pass_kind == Some(PassKind::Render) {635diagnostics.push(RenderDiagnostic {636path: self.diagnostic_path(&span.path_range, "vertex_shader_invocations"),637suffix: "",638value: pipeline_statistics[index] as f64,639});640641diagnostics.push(RenderDiagnostic {642path: self.diagnostic_path(&span.path_range, "clipper_invocations"),643suffix: "",644value: pipeline_statistics[index + 1] as f64,645});646647diagnostics.push(RenderDiagnostic {648path: self.diagnostic_path(&span.path_range, "clipper_primitives_out"),649suffix: "",650value: pipeline_statistics[index + 2] as f64,651});652653diagnostics.push(RenderDiagnostic {654path: self.diagnostic_path(&span.path_range, "fragment_shader_invocations"),655suffix: "",656value: pipeline_statistics[index + 3] as f64,657});658}659660if span.pass_kind == Some(PassKind::Compute) {661diagnostics.push(RenderDiagnostic {662path: self.diagnostic_path(&span.path_range, "compute_shader_invocations"),663suffix: "",664value: pipeline_statistics[index + 4] as f64,665});666}667}668}669670for (buffer, diagnostic_path, is_f32) in self.value_buffers.drain(..) {671let buffer = buffer.get_mapped_range(..);672diagnostics.push(RenderDiagnostic {673path: DiagnosticPath::from_components(674core::iter::once("render").chain(core::iter::once(diagnostic_path.as_ref())),675),676suffix: "",677value: if is_f32 {678f32::from_le_bytes((*buffer).try_into().unwrap()) as f64679} else {680u32::from_le_bytes((*buffer).try_into().unwrap()) as f64681},682});683}684685callback(RenderDiagnostics(diagnostics));686687drop(data);688read_buffer.unmap();689self.is_mapped.store(false, Ordering::Release);690691true692}693}694695/// Resource which stores render diagnostics of the most recent frame.696#[derive(Debug, Default, Clone, Resource)]697pub struct RenderDiagnostics(Vec<RenderDiagnostic>);698699/// A render diagnostic which has been recorded, but not yet stored in [`DiagnosticsStore`].700#[derive(Debug, Clone, Resource)]701pub struct RenderDiagnostic {702pub path: DiagnosticPath,703pub suffix: &'static str,704pub value: f64,705}706707/// Stores render diagnostics before they can be synced with the main app.708///709/// This mutex is locked twice per frame:710/// 1. in `PreUpdate`, during [`sync_diagnostics`],711/// 2. after rendering has finished and statistics have been downloaded from GPU.712#[derive(Debug, Default, Clone, Resource)]713pub struct RenderDiagnosticsMutex(pub(crate) Arc<Mutex<Option<RenderDiagnostics>>>);714715/// Updates render diagnostics measurements.716pub fn sync_diagnostics(mutex: Res<RenderDiagnosticsMutex>, mut store: ResMut<DiagnosticsStore>) {717let Some(diagnostics) = mutex.0.lock().ok().and_then(|mut v| v.take()) else {718return;719};720721let time = Instant::now();722723for diagnostic in &diagnostics.0 {724if store.get(&diagnostic.path).is_none() {725store.add(Diagnostic::new(diagnostic.path.clone()).with_suffix(diagnostic.suffix));726}727728store729.get_mut(&diagnostic.path)730.unwrap()731.add_measurement(DiagnosticMeasurement {732time,733value: diagnostic.value,734});735}736}737738pub trait WriteTimestamp {739fn write_timestamp(&mut self, query_set: &QuerySet, index: u32);740}741742impl WriteTimestamp for CommandEncoder {743fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {744if cfg!(target_os = "macos") {745// When using tracy (and thus this function), rendering was flickering on macOS Tahoe.746// See: https://github.com/bevyengine/bevy/issues/22257747// The issue seems to be triggered when `write_timestamp` is called very close to frame748// presentation.749return;750}751CommandEncoder::write_timestamp(self, query_set, index);752}753}754755impl WriteTimestamp for RenderPass<'_> {756fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {757RenderPass::write_timestamp(self, query_set, index);758}759}760761impl WriteTimestamp for ComputePass<'_> {762fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {763ComputePass::write_timestamp(self, query_set, index);764}765}766767pub trait WritePipelineStatistics {768fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32);769770fn end_pipeline_statistics_query(&mut self);771}772773impl WritePipelineStatistics for RenderPass<'_> {774fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {775RenderPass::begin_pipeline_statistics_query(self, query_set, index);776}777778fn end_pipeline_statistics_query(&mut self) {779RenderPass::end_pipeline_statistics_query(self);780}781}782783impl WritePipelineStatistics for ComputePass<'_> {784fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {785ComputePass::begin_pipeline_statistics_query(self, query_set, index);786}787788fn end_pipeline_statistics_query(&mut self) {789ComputePass::end_pipeline_statistics_query(self);790}791}792793pub trait Pass: WritePipelineStatistics + WriteTimestamp {794const KIND: PassKind;795}796797impl Pass for RenderPass<'_> {798const KIND: PassKind = PassKind::Render;799}800801impl Pass for ComputePass<'_> {802const KIND: PassKind = PassKind::Compute;803}804805#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]806pub enum PassKind {807Render,808Compute,809}810811812