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);