wgpu_profiler/scope.rs
1//! Scope types that wrap a `wgpu` encoder/pass and start a scope on creation. In most cases, they
2//! then allow automatically ending the scope on drop.
3
4use crate::{GpuProfiler, GpuProfilerQuery, ProfilerCommandRecorder};
5
6/// Scope that takes a (mutable) reference to the encoder/pass.
7///
8/// Calls [`GpuProfiler::end_query()`] on drop.
9pub struct Scope<'a, Recorder: ProfilerCommandRecorder> {
10 pub profiler: &'a GpuProfiler,
11 pub recorder: &'a mut Recorder,
12 pub scope: Option<GpuProfilerQuery>,
13}
14
15impl<R: ProfilerCommandRecorder> Drop for Scope<'_, R> {
16 #[inline]
17 fn drop(&mut self) {
18 if let Some(scope) = self.scope.take() {
19 self.profiler.end_query(self.recorder, scope);
20 }
21 }
22}
23
24/// Scope that takes ownership of the encoder/pass.
25///
26/// Calls [`GpuProfiler::end_query()`] on drop.
27pub struct OwningScope<'a, Recorder: ProfilerCommandRecorder> {
28 pub profiler: &'a GpuProfiler,
29 pub recorder: Recorder,
30 pub scope: Option<GpuProfilerQuery>,
31}
32
33impl<R: ProfilerCommandRecorder> Drop for OwningScope<'_, R> {
34 #[inline]
35 fn drop(&mut self) {
36 if let Some(scope) = self.scope.take() {
37 self.profiler.end_query(&mut self.recorder, scope);
38 }
39 }
40}
41
42/// Scope that takes ownership of the encoder/pass.
43///
44/// Does NOT call [`GpuProfiler::end_query()`] on drop.
45/// This construct is just for completeness in cases where working with scopes is preferred but one can't rely on the Drop call in the right place.
46/// This is useful when the owned value needs to be recovered after the end of the scope.
47/// In particular, to submit a [`wgpu::CommandEncoder`] to a queue, ownership of the encoder is necessary.
48pub struct ManualOwningScope<'a, Recorder: ProfilerCommandRecorder> {
49 pub profiler: &'a GpuProfiler,
50 pub recorder: Recorder,
51 pub scope: Option<GpuProfilerQuery>,
52}
53
54impl<R: ProfilerCommandRecorder> ManualOwningScope<'_, R> {
55 /// Ends the scope allowing the extraction of the owned [`ProfilerCommandRecorder`].
56 #[track_caller]
57 #[inline]
58 pub fn end_query(mut self) -> R {
59 // Can't fail since creation implies begin_query.
60 self.profiler
61 .end_query(&mut self.recorder, self.scope.take().unwrap());
62 self.recorder
63 }
64}
65
66/// Most implementation code of the different scope types is exactly the same.
67///
68/// This macro allows to avoid code duplication.
69/// Another way of achieving this are extension traits, but this would mean that a user has to
70/// import the extension trait to use all methods of the scope types which I found a bit annoying.
71macro_rules! impl_scope_ext {
72 ($scope:ident, $recorder_type:ty) => {
73 impl<'a, R: ProfilerCommandRecorder> $scope<'a, R> {
74 /// Starts a new profiler scope nested within this one.
75 #[must_use]
76 #[track_caller]
77 #[inline]
78 pub fn scope(&mut self, label: impl Into<String>) -> Scope<'_, R> {
79 let recorder: &mut R = &mut self.recorder;
80 let scope = self
81 .profiler
82 .begin_query(label, recorder)
83 .with_parent(self.scope.as_ref());
84 Scope {
85 profiler: self.profiler,
86 recorder,
87 scope: Some(scope),
88 }
89 }
90 }
91
92 impl<'a> $scope<'a, wgpu::CommandEncoder> {
93 /// Start a render pass wrapped in a [`OwningScope`].
94 ///
95 /// Ignores passed `wgpu::RenderPassDescriptor::timestamp_writes` and replaces it with
96 /// `timestamp_writes` managed by `GpuProfiler` if profiling is enabled.
97 ///
98 /// This also sets the `wgpu::RenderPassDescriptor::label` if it's `None` (default).
99 ///
100 /// Note that in order to take measurements, this requires the [`wgpu::Features::TIMESTAMP_QUERY`] feature.
101 /// [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] & [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES`] are not required.
102 #[track_caller]
103 pub fn scoped_render_pass(
104 &mut self,
105 label: impl Into<String>,
106 pass_descriptor: wgpu::RenderPassDescriptor<'_>,
107 ) -> OwningScope<'_, wgpu::RenderPass<'_>> {
108 let child_scope = self
109 .profiler
110 .begin_pass_query(label, &mut self.recorder)
111 .with_parent(self.scope.as_ref());
112 let render_pass = self
113 .recorder
114 .begin_render_pass(&wgpu::RenderPassDescriptor {
115 timestamp_writes: child_scope.render_pass_timestamp_writes(),
116 label: pass_descriptor.label.or(Some(&child_scope.label)),
117 ..pass_descriptor
118 });
119
120 OwningScope {
121 profiler: self.profiler,
122 recorder: render_pass,
123 scope: Some(child_scope),
124 }
125 }
126
127 /// Start a compute pass wrapped in a [`OwningScope`].
128 ///
129 /// Uses passed label both for profiler scope and compute pass label.
130 /// `timestamp_writes` managed by `GpuProfiler` if profiling is enabled.
131 ///
132 /// Note that in order to take measurements, this requires the [`wgpu::Features::TIMESTAMP_QUERY`] feature.
133 /// [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] & [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES`] are not required.
134 #[track_caller]
135 pub fn scoped_compute_pass(
136 &mut self,
137 label: impl Into<String>,
138 ) -> OwningScope<'_, wgpu::ComputePass<'_>> {
139 let child_scope = self
140 .profiler
141 .begin_pass_query(label, &mut self.recorder)
142 .with_parent(self.scope.as_ref());
143
144 let render_pass = self
145 .recorder
146 .begin_compute_pass(&wgpu::ComputePassDescriptor {
147 label: Some(&child_scope.label),
148 timestamp_writes: child_scope.compute_pass_timestamp_writes(),
149 });
150
151 OwningScope {
152 profiler: self.profiler,
153 recorder: render_pass,
154 scope: Some(child_scope),
155 }
156 }
157 }
158
159 impl<'a, R: ProfilerCommandRecorder> std::ops::Deref for $scope<'a, R> {
160 type Target = R;
161
162 #[inline]
163 fn deref(&self) -> &Self::Target {
164 &self.recorder
165 }
166 }
167
168 impl<'a, R: ProfilerCommandRecorder> std::ops::DerefMut for $scope<'a, R> {
169 #[inline]
170 fn deref_mut(&mut self) -> &mut Self::Target {
171 &mut self.recorder
172 }
173 }
174 };
175}
176
177impl_scope_ext!(Scope, &'a mut R);
178impl_scope_ext!(OwningScope, R);
179impl_scope_ext!(ManualOwningScope, R);