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(
79                &mut self,
80                label: impl Into<String>,
81                device: &wgpu::Device,
82            ) -> Scope<'_, R> {
83                let recorder: &mut R = &mut self.recorder;
84                let scope = self
85                    .profiler
86                    .begin_query(label, recorder, device)
87                    .with_parent(self.scope.as_ref());
88                Scope {
89                    profiler: self.profiler,
90                    recorder,
91                    scope: Some(scope),
92                }
93            }
94        }
95
96        impl<'a> $scope<'a, wgpu::CommandEncoder> {
97            /// Start a render pass wrapped in a [`OwningScope`].
98            ///
99            /// Ignores passed `wgpu::RenderPassDescriptor::timestamp_writes` and replaces it with
100            /// `timestamp_writes` managed by `GpuProfiler` if profiling is enabled.
101            ///
102            /// This also sets the `wgpu::RenderPassDescriptor::label` if it's `None` (default).
103            ///
104            /// Note that in order to take measurements, this requires the [`wgpu::Features::TIMESTAMP_QUERY`] feature.
105            /// [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] & [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES`] are not required.
106            #[track_caller]
107            pub fn scoped_render_pass<'b>(
108                &'b mut self,
109                label: impl Into<String>,
110                device: &wgpu::Device,
111                pass_descriptor: wgpu::RenderPassDescriptor<'_>,
112            ) -> OwningScope<'b, wgpu::RenderPass<'b>> {
113                let child_scope = self
114                    .profiler
115                    .begin_pass_query(label, &mut self.recorder, device)
116                    .with_parent(self.scope.as_ref());
117                let render_pass = self
118                    .recorder
119                    .begin_render_pass(&wgpu::RenderPassDescriptor {
120                        timestamp_writes: child_scope.render_pass_timestamp_writes(),
121                        label: pass_descriptor.label.or(Some(&child_scope.label)),
122                        ..pass_descriptor
123                    });
124
125                OwningScope {
126                    profiler: self.profiler,
127                    recorder: render_pass,
128                    scope: Some(child_scope),
129                }
130            }
131
132            /// Start a compute pass wrapped in a [`OwningScope`].
133            ///
134            /// Uses passed label both for profiler scope and compute pass label.
135            /// `timestamp_writes` managed by `GpuProfiler` if profiling is enabled.
136            ///
137            /// Note that in order to take measurements, this requires the [`wgpu::Features::TIMESTAMP_QUERY`] feature.
138            /// [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] & [`wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES`] are not required.
139            #[track_caller]
140            pub fn scoped_compute_pass<'b>(
141                &'b mut self,
142                label: impl Into<String>,
143                device: &wgpu::Device,
144            ) -> OwningScope<'b, wgpu::ComputePass<'b>> {
145                let child_scope = self
146                    .profiler
147                    .begin_pass_query(label, &mut self.recorder, device)
148                    .with_parent(self.scope.as_ref());
149
150                let render_pass = self
151                    .recorder
152                    .begin_compute_pass(&wgpu::ComputePassDescriptor {
153                        label: Some(&child_scope.label),
154                        timestamp_writes: child_scope.compute_pass_timestamp_writes(),
155                    });
156
157                OwningScope {
158                    profiler: self.profiler,
159                    recorder: render_pass,
160                    scope: Some(child_scope),
161                }
162            }
163        }
164
165        impl<'a, R: ProfilerCommandRecorder> std::ops::Deref for $scope<'a, R> {
166            type Target = R;
167
168            #[inline]
169            fn deref(&self) -> &Self::Target {
170                &self.recorder
171            }
172        }
173
174        impl<'a, R: ProfilerCommandRecorder> std::ops::DerefMut for $scope<'a, R> {
175            #[inline]
176            fn deref_mut(&mut self) -> &mut Self::Target {
177                &mut self.recorder
178            }
179        }
180    };
181}
182
183impl_scope_ext!(Scope, &'a mut R);
184impl_scope_ext!(OwningScope, R);
185impl_scope_ext!(ManualOwningScope, R);