Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/diagnostic/internal.rs
6596 views
1
use alloc::{borrow::Cow, sync::Arc};
2
use core::{
3
ops::{DerefMut, Range},
4
sync::atomic::{AtomicBool, Ordering},
5
};
6
use std::thread::{self, ThreadId};
7
8
use bevy_diagnostic::{Diagnostic, DiagnosticMeasurement, DiagnosticPath, DiagnosticsStore};
9
use bevy_ecs::resource::Resource;
10
use bevy_ecs::system::{Res, ResMut};
11
use bevy_platform::time::Instant;
12
use std::sync::Mutex;
13
use wgpu::{
14
Buffer, BufferDescriptor, BufferUsages, CommandEncoder, ComputePass, Features, MapMode,
15
PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, RenderPass,
16
};
17
18
use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper};
19
20
use super::RecordDiagnostics;
21
22
// buffer offset must be divisible by 256, so this constant must be divisible by 32 (=256/8)
23
const MAX_TIMESTAMP_QUERIES: u32 = 256;
24
const MAX_PIPELINE_STATISTICS: u32 = 128;
25
26
const TIMESTAMP_SIZE: u64 = 8;
27
const PIPELINE_STATISTICS_SIZE: u64 = 40;
28
29
struct DiagnosticsRecorderInternal {
30
timestamp_period_ns: f32,
31
features: Features,
32
current_frame: Mutex<FrameData>,
33
submitted_frames: Vec<FrameData>,
34
finished_frames: Vec<FrameData>,
35
#[cfg(feature = "tracing-tracy")]
36
tracy_gpu_context: tracy_client::GpuContext,
37
}
38
39
/// Records diagnostics into [`QuerySet`]'s keeping track of the mapping between
40
/// spans and indices to the corresponding entries in the [`QuerySet`].
41
#[derive(Resource)]
42
pub struct DiagnosticsRecorder(WgpuWrapper<DiagnosticsRecorderInternal>);
43
44
impl DiagnosticsRecorder {
45
/// Creates the new `DiagnosticsRecorder`.
46
pub fn new(
47
adapter_info: &RenderAdapterInfo,
48
device: &RenderDevice,
49
queue: &RenderQueue,
50
) -> DiagnosticsRecorder {
51
let features = device.features();
52
53
#[cfg(feature = "tracing-tracy")]
54
let tracy_gpu_context =
55
super::tracy_gpu::new_tracy_gpu_context(adapter_info, device, queue);
56
let _ = adapter_info; // Prevent unused variable warnings when tracing-tracy is not enabled
57
58
DiagnosticsRecorder(WgpuWrapper::new(DiagnosticsRecorderInternal {
59
timestamp_period_ns: queue.get_timestamp_period(),
60
features,
61
current_frame: Mutex::new(FrameData::new(
62
device,
63
features,
64
#[cfg(feature = "tracing-tracy")]
65
tracy_gpu_context.clone(),
66
)),
67
submitted_frames: Vec::new(),
68
finished_frames: Vec::new(),
69
#[cfg(feature = "tracing-tracy")]
70
tracy_gpu_context,
71
}))
72
}
73
74
fn current_frame_mut(&mut self) -> &mut FrameData {
75
self.0.current_frame.get_mut().expect("lock poisoned")
76
}
77
78
fn current_frame_lock(&self) -> impl DerefMut<Target = FrameData> + '_ {
79
self.0.current_frame.lock().expect("lock poisoned")
80
}
81
82
/// Begins recording diagnostics for a new frame.
83
pub fn begin_frame(&mut self) {
84
let internal = &mut self.0;
85
let mut idx = 0;
86
while idx < internal.submitted_frames.len() {
87
let timestamp = internal.timestamp_period_ns;
88
if internal.submitted_frames[idx].run_mapped_callback(timestamp) {
89
let removed = internal.submitted_frames.swap_remove(idx);
90
internal.finished_frames.push(removed);
91
} else {
92
idx += 1;
93
}
94
}
95
96
self.current_frame_mut().begin();
97
}
98
99
/// 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`].
102
pub fn resolve(&mut self, encoder: &mut CommandEncoder) {
103
self.current_frame_mut().resolve(encoder);
104
}
105
106
/// 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.
112
pub fn finish_frame(
113
&mut self,
114
device: &RenderDevice,
115
callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static,
116
) {
117
#[cfg(feature = "tracing-tracy")]
118
let tracy_gpu_context = self.0.tracy_gpu_context.clone();
119
120
let internal = &mut self.0;
121
internal
122
.current_frame
123
.get_mut()
124
.expect("lock poisoned")
125
.finish(callback);
126
127
// reuse one of the finished frames, if we can
128
let new_frame = match internal.finished_frames.pop() {
129
Some(frame) => frame,
130
None => FrameData::new(
131
device,
132
internal.features,
133
#[cfg(feature = "tracing-tracy")]
134
tracy_gpu_context,
135
),
136
};
137
138
let old_frame = core::mem::replace(
139
internal.current_frame.get_mut().expect("lock poisoned"),
140
new_frame,
141
);
142
internal.submitted_frames.push(old_frame);
143
}
144
}
145
146
impl RecordDiagnostics for DiagnosticsRecorder {
147
fn begin_time_span<E: WriteTimestamp>(&self, encoder: &mut E, span_name: Cow<'static, str>) {
148
self.current_frame_lock()
149
.begin_time_span(encoder, span_name);
150
}
151
152
fn end_time_span<E: WriteTimestamp>(&self, encoder: &mut E) {
153
self.current_frame_lock().end_time_span(encoder);
154
}
155
156
fn begin_pass_span<P: Pass>(&self, pass: &mut P, span_name: Cow<'static, str>) {
157
self.current_frame_lock().begin_pass(pass, span_name);
158
}
159
160
fn end_pass_span<P: Pass>(&self, pass: &mut P) {
161
self.current_frame_lock().end_pass(pass);
162
}
163
}
164
165
struct SpanRecord {
166
thread_id: ThreadId,
167
path_range: Range<usize>,
168
pass_kind: Option<PassKind>,
169
begin_timestamp_index: Option<u32>,
170
end_timestamp_index: Option<u32>,
171
begin_instant: Option<Instant>,
172
end_instant: Option<Instant>,
173
pipeline_statistics_index: Option<u32>,
174
}
175
176
struct FrameData {
177
timestamps_query_set: Option<QuerySet>,
178
num_timestamps: u32,
179
supports_timestamps_inside_passes: bool,
180
supports_timestamps_inside_encoders: bool,
181
pipeline_statistics_query_set: Option<QuerySet>,
182
num_pipeline_statistics: u32,
183
buffer_size: u64,
184
pipeline_statistics_buffer_offset: u64,
185
resolve_buffer: Option<Buffer>,
186
read_buffer: Option<Buffer>,
187
path_components: Vec<Cow<'static, str>>,
188
open_spans: Vec<SpanRecord>,
189
closed_spans: Vec<SpanRecord>,
190
is_mapped: Arc<AtomicBool>,
191
callback: Option<Box<dyn FnOnce(RenderDiagnostics) + Send + Sync + 'static>>,
192
#[cfg(feature = "tracing-tracy")]
193
tracy_gpu_context: tracy_client::GpuContext,
194
}
195
196
impl FrameData {
197
fn new(
198
device: &RenderDevice,
199
features: Features,
200
#[cfg(feature = "tracing-tracy")] tracy_gpu_context: tracy_client::GpuContext,
201
) -> FrameData {
202
let wgpu_device = device.wgpu_device();
203
let mut buffer_size = 0;
204
205
let timestamps_query_set = if features.contains(Features::TIMESTAMP_QUERY) {
206
buffer_size += u64::from(MAX_TIMESTAMP_QUERIES) * TIMESTAMP_SIZE;
207
Some(wgpu_device.create_query_set(&QuerySetDescriptor {
208
label: Some("timestamps_query_set"),
209
ty: QueryType::Timestamp,
210
count: MAX_TIMESTAMP_QUERIES,
211
}))
212
} else {
213
None
214
};
215
216
let pipeline_statistics_buffer_offset = buffer_size;
217
218
let pipeline_statistics_query_set =
219
if features.contains(Features::PIPELINE_STATISTICS_QUERY) {
220
buffer_size += u64::from(MAX_PIPELINE_STATISTICS) * PIPELINE_STATISTICS_SIZE;
221
Some(wgpu_device.create_query_set(&QuerySetDescriptor {
222
label: Some("pipeline_statistics_query_set"),
223
ty: QueryType::PipelineStatistics(PipelineStatisticsTypes::all()),
224
count: MAX_PIPELINE_STATISTICS,
225
}))
226
} else {
227
None
228
};
229
230
let (resolve_buffer, read_buffer) = if buffer_size > 0 {
231
let resolve_buffer = wgpu_device.create_buffer(&BufferDescriptor {
232
label: Some("render_statistics_resolve_buffer"),
233
size: buffer_size,
234
usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC,
235
mapped_at_creation: false,
236
});
237
let read_buffer = wgpu_device.create_buffer(&BufferDescriptor {
238
label: Some("render_statistics_read_buffer"),
239
size: buffer_size,
240
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
241
mapped_at_creation: false,
242
});
243
(Some(resolve_buffer), Some(read_buffer))
244
} else {
245
(None, None)
246
};
247
248
FrameData {
249
timestamps_query_set,
250
num_timestamps: 0,
251
supports_timestamps_inside_passes: features
252
.contains(Features::TIMESTAMP_QUERY_INSIDE_PASSES),
253
supports_timestamps_inside_encoders: features
254
.contains(Features::TIMESTAMP_QUERY_INSIDE_ENCODERS),
255
pipeline_statistics_query_set,
256
num_pipeline_statistics: 0,
257
buffer_size,
258
pipeline_statistics_buffer_offset,
259
resolve_buffer,
260
read_buffer,
261
path_components: Vec::new(),
262
open_spans: Vec::new(),
263
closed_spans: Vec::new(),
264
is_mapped: Arc::new(AtomicBool::new(false)),
265
callback: None,
266
#[cfg(feature = "tracing-tracy")]
267
tracy_gpu_context,
268
}
269
}
270
271
fn begin(&mut self) {
272
self.num_timestamps = 0;
273
self.num_pipeline_statistics = 0;
274
self.path_components.clear();
275
self.open_spans.clear();
276
self.closed_spans.clear();
277
}
278
279
fn write_timestamp(
280
&mut self,
281
encoder: &mut impl WriteTimestamp,
282
is_inside_pass: bool,
283
) -> Option<u32> {
284
// `encoder.write_timestamp` is unsupported on WebGPU.
285
if !self.supports_timestamps_inside_encoders {
286
return None;
287
}
288
289
if is_inside_pass && !self.supports_timestamps_inside_passes {
290
return None;
291
}
292
293
if self.num_timestamps >= MAX_TIMESTAMP_QUERIES {
294
return None;
295
}
296
297
let set = self.timestamps_query_set.as_ref()?;
298
let index = self.num_timestamps;
299
encoder.write_timestamp(set, index);
300
self.num_timestamps += 1;
301
Some(index)
302
}
303
304
fn write_pipeline_statistics(
305
&mut self,
306
encoder: &mut impl WritePipelineStatistics,
307
) -> Option<u32> {
308
if self.num_pipeline_statistics >= MAX_PIPELINE_STATISTICS {
309
return None;
310
}
311
312
let set = self.pipeline_statistics_query_set.as_ref()?;
313
let index = self.num_pipeline_statistics;
314
encoder.begin_pipeline_statistics_query(set, index);
315
self.num_pipeline_statistics += 1;
316
Some(index)
317
}
318
319
fn open_span(
320
&mut self,
321
pass_kind: Option<PassKind>,
322
name: Cow<'static, str>,
323
) -> &mut SpanRecord {
324
let thread_id = thread::current().id();
325
326
let parent = self
327
.open_spans
328
.iter()
329
.filter(|v| v.thread_id == thread_id)
330
.next_back();
331
332
let path_range = match &parent {
333
Some(parent) if parent.path_range.end == self.path_components.len() => {
334
parent.path_range.start..parent.path_range.end + 1
335
}
336
Some(parent) => {
337
self.path_components
338
.extend_from_within(parent.path_range.clone());
339
self.path_components.len() - parent.path_range.len()..self.path_components.len() + 1
340
}
341
None => self.path_components.len()..self.path_components.len() + 1,
342
};
343
344
self.path_components.push(name);
345
346
self.open_spans.push(SpanRecord {
347
thread_id,
348
path_range,
349
pass_kind,
350
begin_timestamp_index: None,
351
end_timestamp_index: None,
352
begin_instant: None,
353
end_instant: None,
354
pipeline_statistics_index: None,
355
});
356
357
self.open_spans.last_mut().unwrap()
358
}
359
360
fn close_span(&mut self) -> &mut SpanRecord {
361
let thread_id = thread::current().id();
362
363
let iter = self.open_spans.iter();
364
let (index, _) = iter
365
.enumerate()
366
.filter(|(_, v)| v.thread_id == thread_id)
367
.next_back()
368
.unwrap();
369
370
let span = self.open_spans.swap_remove(index);
371
self.closed_spans.push(span);
372
self.closed_spans.last_mut().unwrap()
373
}
374
375
fn begin_time_span(&mut self, encoder: &mut impl WriteTimestamp, name: Cow<'static, str>) {
376
let begin_instant = Instant::now();
377
let begin_timestamp_index = self.write_timestamp(encoder, false);
378
379
let span = self.open_span(None, name);
380
span.begin_instant = Some(begin_instant);
381
span.begin_timestamp_index = begin_timestamp_index;
382
}
383
384
fn end_time_span(&mut self, encoder: &mut impl WriteTimestamp) {
385
let end_timestamp_index = self.write_timestamp(encoder, false);
386
387
let span = self.close_span();
388
span.end_timestamp_index = end_timestamp_index;
389
span.end_instant = Some(Instant::now());
390
}
391
392
fn begin_pass<P: Pass>(&mut self, pass: &mut P, name: Cow<'static, str>) {
393
let begin_instant = Instant::now();
394
395
let begin_timestamp_index = self.write_timestamp(pass, true);
396
let pipeline_statistics_index = self.write_pipeline_statistics(pass);
397
398
let span = self.open_span(Some(P::KIND), name);
399
span.begin_instant = Some(begin_instant);
400
span.begin_timestamp_index = begin_timestamp_index;
401
span.pipeline_statistics_index = pipeline_statistics_index;
402
}
403
404
fn end_pass(&mut self, pass: &mut impl Pass) {
405
let end_timestamp_index = self.write_timestamp(pass, true);
406
407
let span = self.close_span();
408
span.end_timestamp_index = end_timestamp_index;
409
410
if span.pipeline_statistics_index.is_some() {
411
pass.end_pipeline_statistics_query();
412
}
413
414
span.end_instant = Some(Instant::now());
415
}
416
417
fn resolve(&mut self, encoder: &mut CommandEncoder) {
418
let Some(resolve_buffer) = &self.resolve_buffer else {
419
return;
420
};
421
422
match &self.timestamps_query_set {
423
Some(set) if self.num_timestamps > 0 => {
424
encoder.resolve_query_set(set, 0..self.num_timestamps, resolve_buffer, 0);
425
}
426
_ => {}
427
}
428
429
match &self.pipeline_statistics_query_set {
430
Some(set) if self.num_pipeline_statistics > 0 => {
431
encoder.resolve_query_set(
432
set,
433
0..self.num_pipeline_statistics,
434
resolve_buffer,
435
self.pipeline_statistics_buffer_offset,
436
);
437
}
438
_ => {}
439
}
440
441
let Some(read_buffer) = &self.read_buffer else {
442
return;
443
};
444
445
encoder.copy_buffer_to_buffer(resolve_buffer, 0, read_buffer, 0, self.buffer_size);
446
}
447
448
fn diagnostic_path(&self, range: &Range<usize>, field: &str) -> DiagnosticPath {
449
DiagnosticPath::from_components(
450
core::iter::once("render")
451
.chain(self.path_components[range.clone()].iter().map(|v| &**v))
452
.chain(core::iter::once(field)),
453
)
454
}
455
456
fn finish(&mut self, callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static) {
457
let Some(read_buffer) = &self.read_buffer else {
458
// we still have cpu timings, so let's use them
459
460
let mut diagnostics = Vec::new();
461
462
for span in &self.closed_spans {
463
if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {
464
diagnostics.push(RenderDiagnostic {
465
path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),
466
suffix: "ms",
467
value: (end - begin).as_secs_f64() * 1000.0,
468
});
469
}
470
}
471
472
callback(RenderDiagnostics(diagnostics));
473
return;
474
};
475
476
self.callback = Some(Box::new(callback));
477
478
let is_mapped = self.is_mapped.clone();
479
read_buffer.slice(..).map_async(MapMode::Read, move |res| {
480
if let Err(e) = res {
481
tracing::warn!("Failed to download render statistics buffer: {e}");
482
return;
483
}
484
485
is_mapped.store(true, Ordering::Release);
486
});
487
}
488
489
// returns true if the frame is considered finished, false otherwise
490
fn run_mapped_callback(&mut self, timestamp_period_ns: f32) -> bool {
491
let Some(read_buffer) = &self.read_buffer else {
492
return true;
493
};
494
if !self.is_mapped.load(Ordering::Acquire) {
495
// need to wait more
496
return false;
497
}
498
let Some(callback) = self.callback.take() else {
499
return true;
500
};
501
502
let data = read_buffer.slice(..).get_mapped_range();
503
504
let timestamps = data[..(self.num_timestamps * 8) as usize]
505
.chunks(8)
506
.map(|v| u64::from_le_bytes(v.try_into().unwrap()))
507
.collect::<Vec<u64>>();
508
509
let start = self.pipeline_statistics_buffer_offset as usize;
510
let len = (self.num_pipeline_statistics as usize) * 40;
511
let pipeline_statistics = data[start..start + len]
512
.chunks(8)
513
.map(|v| u64::from_le_bytes(v.try_into().unwrap()))
514
.collect::<Vec<u64>>();
515
516
let mut diagnostics = Vec::new();
517
518
for span in &self.closed_spans {
519
if let (Some(begin), Some(end)) = (span.begin_instant, span.end_instant) {
520
diagnostics.push(RenderDiagnostic {
521
path: self.diagnostic_path(&span.path_range, "elapsed_cpu"),
522
suffix: "ms",
523
value: (end - begin).as_secs_f64() * 1000.0,
524
});
525
}
526
527
if let (Some(begin), Some(end)) = (span.begin_timestamp_index, span.end_timestamp_index)
528
{
529
let begin = timestamps[begin as usize] as f64;
530
let end = timestamps[end as usize] as f64;
531
let value = (end - begin) * (timestamp_period_ns as f64) / 1e6;
532
533
#[cfg(feature = "tracing-tracy")]
534
{
535
// 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.
536
// 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.
537
// 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.
538
let name = &self.path_components[span.path_range.clone()].join("/");
539
let mut tracy_gpu_span =
540
self.tracy_gpu_context.span_alloc(name, "", "", 0).unwrap();
541
tracy_gpu_span.end_zone();
542
tracy_gpu_span.upload_timestamp_start(begin as i64);
543
tracy_gpu_span.upload_timestamp_end(end as i64);
544
}
545
546
diagnostics.push(RenderDiagnostic {
547
path: self.diagnostic_path(&span.path_range, "elapsed_gpu"),
548
suffix: "ms",
549
value,
550
});
551
}
552
553
if let Some(index) = span.pipeline_statistics_index {
554
let index = (index as usize) * 5;
555
556
if span.pass_kind == Some(PassKind::Render) {
557
diagnostics.push(RenderDiagnostic {
558
path: self.diagnostic_path(&span.path_range, "vertex_shader_invocations"),
559
suffix: "",
560
value: pipeline_statistics[index] as f64,
561
});
562
563
diagnostics.push(RenderDiagnostic {
564
path: self.diagnostic_path(&span.path_range, "clipper_invocations"),
565
suffix: "",
566
value: pipeline_statistics[index + 1] as f64,
567
});
568
569
diagnostics.push(RenderDiagnostic {
570
path: self.diagnostic_path(&span.path_range, "clipper_primitives_out"),
571
suffix: "",
572
value: pipeline_statistics[index + 2] as f64,
573
});
574
575
diagnostics.push(RenderDiagnostic {
576
path: self.diagnostic_path(&span.path_range, "fragment_shader_invocations"),
577
suffix: "",
578
value: pipeline_statistics[index + 3] as f64,
579
});
580
}
581
582
if span.pass_kind == Some(PassKind::Compute) {
583
diagnostics.push(RenderDiagnostic {
584
path: self.diagnostic_path(&span.path_range, "compute_shader_invocations"),
585
suffix: "",
586
value: pipeline_statistics[index + 4] as f64,
587
});
588
}
589
}
590
}
591
592
callback(RenderDiagnostics(diagnostics));
593
594
drop(data);
595
read_buffer.unmap();
596
self.is_mapped.store(false, Ordering::Release);
597
598
true
599
}
600
}
601
602
/// Resource which stores render diagnostics of the most recent frame.
603
#[derive(Debug, Default, Clone, Resource)]
604
pub struct RenderDiagnostics(Vec<RenderDiagnostic>);
605
606
/// A render diagnostic which has been recorded, but not yet stored in [`DiagnosticsStore`].
607
#[derive(Debug, Clone, Resource)]
608
pub struct RenderDiagnostic {
609
pub path: DiagnosticPath,
610
pub suffix: &'static str,
611
pub value: f64,
612
}
613
614
/// Stores render diagnostics before they can be synced with the main app.
615
///
616
/// This mutex is locked twice per frame:
617
/// 1. in `PreUpdate`, during [`sync_diagnostics`],
618
/// 2. after rendering has finished and statistics have been downloaded from GPU.
619
#[derive(Debug, Default, Clone, Resource)]
620
pub struct RenderDiagnosticsMutex(pub(crate) Arc<Mutex<Option<RenderDiagnostics>>>);
621
622
/// Updates render diagnostics measurements.
623
pub fn sync_diagnostics(mutex: Res<RenderDiagnosticsMutex>, mut store: ResMut<DiagnosticsStore>) {
624
let Some(diagnostics) = mutex.0.lock().ok().and_then(|mut v| v.take()) else {
625
return;
626
};
627
628
let time = Instant::now();
629
630
for diagnostic in &diagnostics.0 {
631
if store.get(&diagnostic.path).is_none() {
632
store.add(Diagnostic::new(diagnostic.path.clone()).with_suffix(diagnostic.suffix));
633
}
634
635
store
636
.get_mut(&diagnostic.path)
637
.unwrap()
638
.add_measurement(DiagnosticMeasurement {
639
time,
640
value: diagnostic.value,
641
});
642
}
643
}
644
645
pub trait WriteTimestamp {
646
fn write_timestamp(&mut self, query_set: &QuerySet, index: u32);
647
}
648
649
impl WriteTimestamp for CommandEncoder {
650
fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
651
CommandEncoder::write_timestamp(self, query_set, index);
652
}
653
}
654
655
impl WriteTimestamp for RenderPass<'_> {
656
fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
657
RenderPass::write_timestamp(self, query_set, index);
658
}
659
}
660
661
impl WriteTimestamp for ComputePass<'_> {
662
fn write_timestamp(&mut self, query_set: &QuerySet, index: u32) {
663
ComputePass::write_timestamp(self, query_set, index);
664
}
665
}
666
667
pub trait WritePipelineStatistics {
668
fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32);
669
670
fn end_pipeline_statistics_query(&mut self);
671
}
672
673
impl WritePipelineStatistics for RenderPass<'_> {
674
fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {
675
RenderPass::begin_pipeline_statistics_query(self, query_set, index);
676
}
677
678
fn end_pipeline_statistics_query(&mut self) {
679
RenderPass::end_pipeline_statistics_query(self);
680
}
681
}
682
683
impl WritePipelineStatistics for ComputePass<'_> {
684
fn begin_pipeline_statistics_query(&mut self, query_set: &QuerySet, index: u32) {
685
ComputePass::begin_pipeline_statistics_query(self, query_set, index);
686
}
687
688
fn end_pipeline_statistics_query(&mut self) {
689
ComputePass::end_pipeline_statistics_query(self);
690
}
691
}
692
693
pub trait Pass: WritePipelineStatistics + WriteTimestamp {
694
const KIND: PassKind;
695
}
696
697
impl Pass for RenderPass<'_> {
698
const KIND: PassKind = PassKind::Render;
699
}
700
701
impl Pass for ComputePass<'_> {
702
const KIND: PassKind = PassKind::Compute;
703
}
704
705
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
706
pub enum PassKind {
707
Render,
708
Compute,
709
}
710
711