1use alloc::{borrow::Cow, sync::Arc};
2use core::{
3 ops::{DerefMut, Range},
4 sync::atomic::{AtomicBool, Ordering},
5};
6use std::thread::{self, ThreadId};
7
8use bevy_diagnostic::{Diagnostic, DiagnosticMeasurement, DiagnosticPath, DiagnosticsStore};
9use bevy_ecs::resource::Resource;
10use bevy_ecs::system::{Res, ResMut};
11use bevy_platform::time::Instant;
12use std::sync::Mutex;
13use wgpu::{
14 Buffer, BufferDescriptor, BufferSize, BufferSlice, BufferUsages, CommandEncoder, ComputePass,
15 Device, Features, MapMode, PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType,
16 RenderPass,
17};
18
19use crate::renderer::{wgpu_wrapper, RenderAdapterInfo, RenderDevice, RenderQueue};
20
21use super::RecordDiagnostics;
22
23const MAX_TIMESTAMP_QUERIES: u32 = 256;
25const MAX_PIPELINE_STATISTICS: u32 = 128;
26
27const TIMESTAMP_SIZE: u64 = 8;
28const PIPELINE_STATISTICS_SIZE: u64 = 40;
29
30struct DiagnosticsRecorderInternal {
31 timestamp_period_ns: f32,
32 features: Features,
33 current_frame: Mutex<FrameData>,
34 submitted_frames: Vec<FrameData>,
35 finished_frames: Vec<FrameData>,
36 #[cfg(feature = "tracing-tracy")]
37 tracy_gpu_context: Option<tracy_client::GpuContext>,
38}
39
40wgpu_wrapper!(struct WgpuDiagnosticsRecorderInternal(DiagnosticsRecorderInternal));
41
42#[derive(Resource)]
45pub struct DiagnosticsRecorder(WgpuDiagnosticsRecorderInternal);
46
47impl DiagnosticsRecorder {
48 pub fn new(
50 adapter_info: &RenderAdapterInfo,
51 device: &RenderDevice,
52 queue: &RenderQueue,
53 ) -> DiagnosticsRecorder {
54 let features = device.features();
55
56 #[cfg(feature = "tracing-tracy")]
57 let tracy_gpu_context =
58 super::tracy_gpu::new_tracy_gpu_context(adapter_info, device, queue);
59 let _ = adapter_info; DiagnosticsRecorder(WgpuDiagnosticsRecorderInternal::new(
62 DiagnosticsRecorderInternal {
63 timestamp_period_ns: queue.get_timestamp_period(),
64 features,
65 current_frame: Mutex::new(FrameData::new(
66 device,
67 features,
68 #[cfg(feature = "tracing-tracy")]
69 tracy_gpu_context.clone(),
70 )),
71 submitted_frames: Vec::new(),
72 finished_frames: Vec::new(),
73 #[cfg(feature = "tracing-tracy")]
74 tracy_gpu_context,
75 },
76 ))
77 }
78
79 fn current_frame_mut(&mut self) -> &mut FrameData {
80 self.0.current_frame.get_mut().expect("lock poisoned")
81 }
82
83 fn current_frame_lock(&self) -> impl DerefMut<Target = FrameData> + '_ {
84 self.0.current_frame.lock().expect("lock poisoned")
85 }
86
87 pub fn begin_frame(&mut self) {
89 let internal = &mut self.0;
90 let mut idx = 0;
91 while idx < internal.submitted_frames.len() {
92 let timestamp = internal.timestamp_period_ns;
93 if internal.submitted_frames[idx].run_mapped_callback(timestamp) {
94 let removed = internal.submitted_frames.swap_remove(idx);
95 internal.finished_frames.push(removed);
96 } else {
97 idx += 1;
98 }
99 }
100
101 self.current_frame_mut().begin();
102 }
103
104 pub fn resolve(&mut self, encoder: &mut CommandEncoder) {
108 self.current_frame_mut().resolve(encoder);
109 }
110
111 pub fn finish_frame(
118 &mut self,
119 device: &RenderDevice,
120 callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static,
121 ) {
122 #[cfg(feature = "tracing-tracy")]
123 let tracy_gpu_context = self.0.tracy_gpu_context.clone();
124
125 let internal = &mut self.0;
126 internal
127 .current_frame
128 .get_mut()
129 .expect("lock poisoned")
130 .finish(callback);
131
132 let new_frame = match internal.finished_frames.pop() {
134 Some(frame) => frame,
135 None => FrameData::new(
136 device,
137 internal.features,
138 #[cfg(feature = "tracing-tracy")]
139 tracy_gpu_context,
140 ),
141 };
142
143 let old_frame = core::mem::replace(
144 internal.current_frame.get_mut().expect("lock poisoned"),
145 new_frame,
146 );
147 internal.submitted_frames.push(old_frame);
148 }
149}
150
151impl RecordDiagnostics for DiagnosticsRecorder {
152 fn record_f32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
153 where
154 N: Into<Cow<'static, str>>,
155 {
156 assert_eq!(
157 buffer.size(),
158 BufferSize::new(4).unwrap().get(),
159 "DiagnosticsRecorder::record_f32 buffer slice must be 4 bytes long"
160 );
161 assert!(
162 buffer.buffer().usage().contains(BufferUsages::COPY_SRC),
163 "DiagnosticsRecorder::record_f32 buffer must have BufferUsages::COPY_SRC"
164 );
165
166 self.current_frame_lock()
167 .record_value(command_encoder, buffer, name.into(), true);
168 }
169
170 fn record_u32<N>(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N)
171 where
172 N: Into<Cow<'static, str>>,
173 {
174 assert_eq!(
175 buffer.size(),
176 BufferSize::new(4).unwrap().get(),
177 "DiagnosticsRecorder::record_u32 buffer slice must be 4 bytes long"
178 );
179 assert!(
180 buffer.buffer().usage().contains(BufferUsages::COPY_SRC),
181 "DiagnosticsRecorder::record_u32 buffer must have BufferUsages::COPY_SRC"
182 );
183
184 self.current_frame_lock()
185 .record_value(command_encoder, buffer, name.into(), false);
186 }
187
188 fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, span_name: Cow<'static, str>) {
189 self.current_frame_lock()
190 .begin_time_span(encoder, span_name);
191 }
192
193 fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
194 self.current_frame_lock().end_time_span(encoder);
195 }
196
197 fn begin_pass_span<P: Pass>(&self, pass: &mut P, span_name: Cow<'static, str>) {
198 self.current_frame_lock().begin_pass(pass, span_name);
199 }
200
201 fn end_pass_span<P: Pass>(&self, pass: &mut P) {
202 self.current_frame_lock().end_pass(pass);
203 }
204}
205
206struct SpanRecord {
207 thread_id: ThreadId,
208 path_range: Range<usize>,
209 pass_kind: Option<PassKind>,
210 begin_timestamp_index: Option<u32>,
211 end_timestamp_index: Option<u32>,
212 begin_instant: Option<Instant>,
213 end_instant: Option<Instant>,
214 pipeline_statistics_index: Option<u32>,
215}
216
217struct FrameData {
218 device: Device,
219 timestamps_query_set: Option<QuerySet>,
220 num_timestamps: u32,
221 supports_timestamps_inside_passes: bool,
222 supports_timestamps_inside_encoders: bool,
223 pipeline_statistics_query_set: Option<QuerySet>,
224 num_pipeline_statistics: u32,
225 buffer_size: u64,
226 pipeline_statistics_buffer_offset: u64,
227 resolve_buffer: Option<Buffer>,
228 read_buffer: Option<Buffer>,
229 path_components: Vec<Cow<'static, str>>,
230 open_spans: Vec<SpanRecord>,
231 closed_spans: Vec<SpanRecord>,
232 value_buffers: Vec<(Buffer, Cow<'static, str>, bool)>,
233 is_mapped: Arc<AtomicBool>,
234 callback: Option<Box<dyn FnOnce(RenderDiagnostics) + Send + Sync + 'static>>,
235 #[cfg(feature = "tracing-tracy")]
236 tracy_gpu_context: Option<tracy_client::GpuContext>,
237}
238
239impl FrameData {
240 fn new(
241 device: &RenderDevice,
242 features: Features,
243 #[cfg(feature = "tracing-tracy")] tracy_gpu_context: Option<tracy_client::GpuContext>,
244 ) -> FrameData {
245 let wgpu_device = device.wgpu_device();
246 let mut buffer_size = 0;
247
248 let timestamps_query_set = if features.contains(Features::TIMESTAMP_QUERY) {
249 buffer_size += u64::from(MAX_TIMESTAMP_QUERIES) * TIMESTAMP_SIZE;
250 Some(wgpu_device.create_query_set(&QuerySetDescriptor {
251 label: Some("timestamps_query_set"),
252 ty: QueryType::Timestamp,
253 count: MAX_TIMESTAMP_QUERIES,
254 }))
255 } else {
256 None
257 };
258
259 let pipeline_statistics_buffer_offset = buffer_size;
260
261 let pipeline_statistics_query_set =
262 if features.contains(Features::PIPELINE_STATISTICS_QUERY) {
263 buffer_size += u64::from(MAX_PIPELINE_STATISTICS) * PIPELINE_STATISTICS_SIZE;
264 Some(wgpu_device.create_query_set(&QuerySetDescriptor {
265 label: Some("pipeline_statistics_query_set"),
266 ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()),
267 count: MAX_PIPELINE_STATISTICS,
268 }))
269 } else {
270 None
271 };
272
273 let (resolve_buffer, read_buffer) = if buffer_size > 0 {
274 let resolve_buffer = wgpu_device.create_buffer(&BufferDescriptor {
275 label: Some("render_statistics_resolve_buffer"),
276 size: buffer_size,
277 usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC,
278 mapped_at_creation: false,
279 });
280 let read_buffer = wgpu_device.create_buffer(&BufferDescriptor {
281 label: Some("render_statistics_read_buffer"),
282 size: buffer_size,
283 usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
284 mapped_at_creation: false,
285 });
286 (Some(resolve_buffer), Some(read_buffer))
287 } else {
288 (None, None)
289 };
290
291 FrameData {
292 device: wgpu_device.clone(),
293 timestamps_query_set,
294 num_timestamps: 0,
295 supports_timestamps_inside_passes: features
296 .contains(Features::TIMESTAMP_QUERY_INSIDE_PASSES),
297 supports_timestamps_inside_encoders: features
298 .contains(Features::TIMESTAMP_QUERY_INSIDE_ENCODERS),
299 pipeline_statistics_query_set,
300 num_pipeline_statistics: 0,
301 buffer_size,
302 pipeline_statistics_buffer_offset,
303 resolve_buffer,
304 read_buffer,
305 path_components: Vec::new(),
306 open_spans: Vec::new(),
307 closed_spans: Vec::new(),
308 value_buffers: Vec::new(),
309 is_mapped: Arc::new(AtomicBool::new(false)),
310 callback: None,
311 #[cfg(feature = "tracing-tracy")]
312 tracy_gpu_context,
313 }
314 }
315
316 fn begin(&mut self) {
317 self.num_timestamps = 0;
318 self.num_pipeline_statistics = 0;
319 self.path_components.clear();
320 self.open_spans.clear();
321 self.closed_spans.clear();
322 }
323
324 fn write_timestamp(
325 &mut self,
326 encoder: &mut impl WriteTimestamp,
327 is_inside_pass: bool,
328 ) -> Option<u32> {
329 if !self.supports_timestamps_inside_encoders {
331 return None;
332 }
333
334 if is_inside_pass && !self.supports_timestamps_inside_passes {
335 return None;
336 }
337
338 if self.num_timestamps >= MAX_TIMESTAMP_QUERIES {
339 return None;
340 }
341
342 let set = self.timestamps_query_set.as_ref()?;
343 let index = self.num_timestamps;
344 encoder.write_timestamp(set, index);
345 self.num_timestamps += 1;
346 Some(index)
347 }
348
349 fn write_pipeline_statistics(
350 &mut self,
351 encoder: &mut impl WritePipelineStatistics,
352 ) -> Option<u32> {
353 if self.num_pipeline_statistics >= MAX_PIPELINE_STATISTICS {
354 return None;
355 }
356
357 let set = self.pipeline_statistics_query_set.as_ref()?;
358 let index = self.num_pipeline_statistics;
359 encoder.begin_pipeline_statistics_query(set, index);
360 self.num_pipeline_statistics += 1;
361 Some(index)
362 }
363
364 fn open_span(
365 &mut self,
366 pass_kind: Option<PassKind>,
367 name: Cow<'static, str>,
368 ) -> &mut SpanRecord {
369 let thread_id = thread::current().id();
370
371 let parent = self.open_spans.iter().rfind(|v| v.thread_id == thread_id);
372
373 let path_range = match &parent {
374 Some(parent) if parent.path_range.end == self.path_components.len() => {
375 parent.path_range.start..parent.path_range.end + 1
376 }
377 Some(parent) => {
378 self.path_components
379 .extend_from_within(parent.path_range.clone());
380 self.path_components.len() - parent.path_range.len()..self.path_components.len() + 1
381 }
382 None => self.path_components.len()..self.path_components.len() + 1,
383 };
384
385 self.path_components.push(name);
386
387 self.open_spans.push(SpanRecord {
388 thread_id,
389 path_range,
390 pass_kind,
391 begin_timestamp_index: None,
392 end_timestamp_index: None,
393 begin_instant: None,
394 end_instant: None,
395 pipeline_statistics_index: None,
396 });
397
398 self.open_spans.last_mut().unwrap()
399 }
400
401 fn close_span(&mut self) -> &mut SpanRecord {
402 let thread_id = thread::current().id();
403
404 let iter = self.open_spans.iter();
405 let (index, _) = iter
406 .enumerate()
407 .rfind(|(_, v)| v.thread_id == thread_id)
408 .unwrap();
409
410 let span = self.open_spans.swap_remove(index);
411 self.closed_spans.push(span);
412 self.closed_spans.last_mut().unwrap()
413 }
414
415 fn record_value(
416 &mut self,
417 command_encoder: &mut CommandEncoder,
418 buffer: &BufferSlice,
419 name: Cow<'static, str>,
420 is_f32: bool,
421 ) {
422 let dest_buffer = self.device.create_buffer(&BufferDescriptor {
423 label: Some(&format!("render_diagnostic_{name}")),
424 size: 4,
425 usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
426 mapped_at_creation: false,
427 });
428
429 command_encoder.copy_buffer_to_buffer(
430 buffer.buffer(),
431 buffer.offset(),
432 &dest_buffer,
433 0,
434 Some(buffer.size()),
435 );
436
437 command_encoder.map_buffer_on_submit(&dest_buffer, MapMode::Read, .., |_| {});
438
439 self.value_buffers.push((dest_buffer, name, is_f32));
440 }
441
442 fn begin_time_span(&mut self, encoder: &mut impl WriteTimestamp, name: Cow<'static, str>) {
443 let begin_instant = Instant::now();
444 let begin_timestamp_index = self.write_timestamp(encoder, false);
445
446 let span = self.open_span(None, name);
447 span.begin_instant = Some(begin_instant);
448 span.begin_timestamp_index = begin_timestamp_index;
449 }
450
451 fn end_time_span(&mut self, encoder: &mut impl WriteTimestamp) {
452 let end_timestamp_index = self.write_timestamp(encoder, false);
453
454 let span = self.close_span();
455 span.end_timestamp_index = end_timestamp_index;
456 span.end_instant = Some(Instant::now());
457 }
458
459 fn begin_pass<P: Pass>(&mut self, pass: &mut P, name: Cow<'static, str>) {
460 let begin_instant = Instant::now();
461
462 let begin_timestamp_index = self.write_timestamp(pass, true);
463 let pipeline_statistics_index = self.write_pipeline_statistics(pass);
464
465 let span = self.open_span(Some(P::KIND), name);
466 span.begin_instant = Some(begin_instant);
467 span.begin_timestamp_index = begin_timestamp_index;
468 span.pipeline_statistics_index = pipeline_statistics_index;
469 }
470
471 fn end_pass(&mut self, pass: &mut impl Pass) {
472 let end_timestamp_index = self.write_timestamp(pass, true);
473
474 let span = self.close_span();
475 span.end_timestamp_index = end_timestamp_index;
476
477 if span.pipeline_statistics_index.is_some() {
478 pass.end_pipeline_statistics_query();
479 }
480
481 span.end_instant = Some(Instant::now());
482 }
483
484 fn resolve(&mut self, encoder: &mut CommandEncoder) {
485 let Some(resolve_buffer) = &self.resolve_buffer else {
486 return;
487 };
488
489 match &self.timestamps_query_set {
490 Some(set) if self.num_timestamps > 0 => {
491 encoder.resolve_query_set(set, 0..self.num_timestamps, resolve_buffer, 0);
492 }
493 _ => {}
494 }
495
496 match &self.pipeline_statistics_query_set {
497 Some(set) if self.num_pipeline_statistics > 0 => {
498 encoder.resolve_query_set(
499 set,
500 0..self.num_pipeline_statistics,
501 resolve_buffer,
502 self.pipeline_statistics_buffer_offset,
503 );
504 }
505 _ => {}
506 }
507
508 let Some(read_buffer) = &self.read_buffer else {
509 return;
510 };
511
512 encoder.copy_buffer_to_buffer(resolve_buffer, 0, read_buffer, 0, self.buffer_size);
513 }
514
515 fn diagnostic_path(&self, range: &Range<usize>, field: &str) -> DiagnosticPath {
516 DiagnosticPath::from_components(
517 core::iter::once("render")
518 .chain(self.path_components[range.clone()].iter().map(|v| &**v))
519 .chain(core::iter::once(field)),
520 )
521 }
522
523 fn finish(&mut self, callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static) {
524 let Some(read_buffer) = &self.read_buffer else {
525 let mut diagnostics = Vec::new();
528
529 for span in &self.closed_spans {
530 if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {
531 diagnostics.push(RenderDiagnostic {
532 path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),
533 suffix: "ms",
534 value: (end - begin).as_secs_f64() * 1000.0,
535 });
536 }
537 }
538
539 for (buffer, diagnostic_path, is_f32) in self.value_buffers.drain(..) {
540 let buffer = buffer.get_mapped_range(..).unwrap();
541 diagnostics.push(RenderDiagnostic {
542 path: DiagnosticPath::from_components(
543 core::iter::once("render")
544 .chain(core::iter::once(diagnostic_path.as_ref())),
545 ),
546 suffix: "",
547 value: if is_f32 {
548 f32::from_le_bytes((*buffer).try_into().unwrap()) as f64
549 } else {
550 u32::from_le_bytes((*buffer).try_into().unwrap()) as f64
551 },
552 });
553 }
554
555 callback(RenderDiagnostics(diagnostics));
556 return;
557 };
558
559 self.callback = Some(Box::new(callback));
560
561 let is_mapped = self.is_mapped.clone();
562 read_buffer.slice(..).map_async(MapMode::Read, move |res| {
563 if let Err(e) = res {
564 bevy_log::warn!("Failed to download render statistics buffer: {e}");
565 return;
566 }
567
568 is_mapped.store(true, Ordering::Release);
569 });
570 }
571
572 fn run_mapped_callback(&mut self, timestamp_period_ns: f32) -> bool {
574 let Some(read_buffer) = &self.read_buffer else {
575 return true;
576 };
577 if !self.is_mapped.load(Ordering::Acquire) {
578 return false;
580 }
581 let Some(callback) = self.callback.take() else {
582 return true;
583 };
584
585 let data = read_buffer.slice(..).get_mapped_range().unwrap();
586
587 let timestamps = data[..(self.num_timestamps * 8) as usize]
588 .as_chunks()
589 .0
590 .iter()
591 .map(|&v| u64::from_le_bytes(v))
592 .collect::<Vec<u64>>();
593
594 let start = self.pipeline_statistics_buffer_offset as usize;
595 let len = (self.num_pipeline_statistics as usize) * 40;
596 let pipeline_statistics = data[start..start + len]
597 .as_chunks()
598 .0
599 .iter()
600 .map(|&v| u64::from_le_bytes(v))
601 .collect::<Vec<u64>>();
602
603 let mut diagnostics = Vec::new();
604
605 for span in &self.closed_spans {
606 if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {
607 diagnostics.push(RenderDiagnostic {
608 path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),
609 suffix: "ms",
610 value: (end - begin).as_secs_f64() * 1000.0,
611 });
612 }
613
614 if let (Some(begin), Some(end)) = (span.begin_timestamp_index, span.end_timestamp_index)
615 {
616 let begin = timestamps[begin as usize] as f64;
617 let end = timestamps[end as usize] as f64;
618 let value = (end - begin) * (timestamp_period_ns as f64) / 1e6;
619
620 #[cfg(feature = "tracing-tracy")]
621 {
622 if let Some(tracy_gpu_context) = &self.tracy_gpu_context {
626 let name = &self.path_components[span.path_range.clone()].join("/");
627 let mut tracy_gpu_span =
628 tracy_gpu_context.span_alloc(name, "", "", 0).unwrap();
629 tracy_gpu_span.end_zone();
630 tracy_gpu_span.upload_timestamp_start(begin as i64);
631 tracy_gpu_span.upload_timestamp_end(end as i64);
632 }
633 }
634
635 diagnostics.push(RenderDiagnostic {
636 path: self.diagnostic_path(&span.path_range, "elapsed_gpu"),
637 suffix: "ms",
638 value,
639 });
640 }
641
642 if let Some(index) = span.pipeline_statistics_index {
643 let index = (index as usize) * 5;
644
645 if span.pass_kind == Some(PassKind::Render) {
646 diagnostics.push(RenderDiagnostic {
647 path: self.diagnostic_path(&span.path_range, "vertex_shader_invocations"),
648 suffix: "",
649 value: pipeline_statistics[index] as f64,
650 });
651
652 diagnostics.push(RenderDiagnostic {
653 path: self.diagnostic_path(&span.path_range, "clipper_invocations"),
654 suffix: "",
655 value: pipeline_statistics[index + 1] as f64,
656 });
657
658 diagnostics.push(RenderDiagnostic {
659 path: self.diagnostic_path(&span.path_range, "clipper_primitives_out"),
660 suffix: "",
661 value: pipeline_statistics[index + 2] as f64,
662 });
663
664 diagnostics.push(RenderDiagnostic {
665 path: self.diagnostic_path(&span.path_range, "fragment_shader_invocations"),
666 suffix: "",
667 value: pipeline_statistics[index + 3] as f64,
668 });
669 }
670
671 if span.pass_kind == Some(PassKind::Compute) {
672 diagnostics.push(RenderDiagnostic {
673 path: self.diagnostic_path(&span.path_range, "compute_shader_invocations"),
674 suffix: "",
675 value: pipeline_statistics[index + 4] as f64,
676 });
677 }
678 }
679 }
680
681 for (buffer, diagnostic_path, is_f32) in self.value_buffers.drain(..) {
682 let buffer = buffer.get_mapped_range(..).unwrap();
683 diagnostics.push(RenderDiagnostic {
684 path: DiagnosticPath::from_components(
685 core::iter::once("render").chain(core::iter::once(diagnostic_path.as_ref())),
686 ),
687 suffix: "",
688 value: if is_f32 {
689 f32::from_le_bytes((*buffer).try_into().unwrap()) as f64
690 } else {
691 u32::from_le_bytes((*buffer).try_into().unwrap()) as f64
692 },
693 });
694 }
695
696 callback(RenderDiagnostics(diagnostics));
697
698 drop(data);
699 read_buffer.unmap();
700 self.is_mapped.store(false, Ordering::Release);
701
702 true
703 }
704}
705
706#[derive(Debug, Default, Clone, Resource)]
708pub struct RenderDiagnostics(Vec<RenderDiagnostic>);
709
710#[derive(Debug, Clone, Resource)]
712pub struct RenderDiagnostic {
713 pub path: DiagnosticPath,
714 pub suffix: &'static str,
715 pub value: f64,
716}
717
718#[derive(Debug, Default, Clone, Resource)]
724pub struct RenderDiagnosticsMutex(pub(crate) Arc<Mutex<Option<RenderDiagnostics>>>);
725
726pub fn sync_diagnostics(mutex: Res<RenderDiagnosticsMutex>, mut store: ResMut<DiagnosticsStore>) {
728 let Some(diagnostics) = mutex.0.lock().ok().and_then(|mut v| v.take()) else {
729 return;
730 };
731
732 let time = Instant::now();
733
734 for diagnostic in &diagnostics.0 {
735 if store.get(&diagnostic.path).is_none() {
736 store.add(Diagnostic::new(diagnostic.path.clone()).with_suffix(diagnostic.suffix));
737 }
738
739 store
740 .get_mut(&diagnostic.path)
741 .unwrap()
742 .add_measurement(DiagnosticMeasurement {
743 time,
744 value: diagnostic.value,
745 });
746 }
747}
748
749pub trait WriteTimestamp {
750 fn write_timestamp(&mut self, query_set: &QuerySet, index: u32);
751}
752
753impl WriteTimestamp for CommandEncoder {
754 fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
755 if cfg!(target_os = "macos") {
756 return;
761 }
762 CommandEncoder::write_timestamp(self, query_set, index);
763 }
764}
765
766impl WriteTimestamp for RenderPass<'_> {
767 fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
768 RenderPass::write_timestamp(self, query_set, index);
769 }
770}
771
772impl WriteTimestamp for ComputePass<'_> {
773 fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
774 ComputePass::write_timestamp(self, query_set, index);
775 }
776}
777
778pub trait WritePipelineStatistics {
779 fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32);
780
781 fn end_pipeline_statistics_query(&mut self);
782}
783
784impl WritePipelineStatistics for RenderPass<'_> {
785 fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {
786 RenderPass::begin_pipeline_statistics_query(self, query_set, index);
787 }
788
789 fn end_pipeline_statistics_query(&mut self) {
790 RenderPass::end_pipeline_statistics_query(self);
791 }
792}
793
794impl WritePipelineStatistics for ComputePass<'_> {
795 fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {
796 ComputePass::begin_pipeline_statistics_query(self, query_set, index);
797 }
798
799 fn end_pipeline_statistics_query(&mut self) {
800 ComputePass::end_pipeline_statistics_query(self);
801 }
802}
803
804pub trait Pass: WritePipelineStatistics + WriteTimestamp {
805 const KIND: PassKind;
806}
807
808impl Pass for RenderPass<'_> {
809 const KIND: PassKind = PassKind::Render;
810}
811
812impl Pass for ComputePass<'_> {
813 const KIND: PassKind = PassKind::Compute;
814}
815
816#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
817pub enum PassKind {
818 Render,
819 Compute,
820}