A game about forced loneliness, made by TACStudios
1// TProfilingSampler<TEnum>.samples should just be an array. Unfortunately, Enum cannot be converted to int without generating garbage.
2// This could be worked around by using Unsafe but it's not available at the moment.
3// So in the meantime we use a Dictionary with a perf hit...
4//#define USE_UNSAFE
5
6#if UNITY_2020_1_OR_NEWER
7#define UNITY_USE_RECORDER
8#endif
9
10using System;
11using System.Linq;
12using System.Collections.Generic;
13using UnityEngine.Profiling;
14using Unity.Profiling;
15
16
17namespace UnityEngine.Rendering
18{
19 class TProfilingSampler<TEnum> : ProfilingSampler where TEnum : Enum
20 {
21#if USE_UNSAFE
22 internal static TProfilingSampler<TEnum>[] samples;
23#else
24 internal static Dictionary<TEnum, TProfilingSampler<TEnum>> samples = new Dictionary<TEnum, TProfilingSampler<TEnum>>();
25#endif
26 static TProfilingSampler()
27 {
28 var names = Enum.GetNames(typeof(TEnum));
29#if USE_UNSAFE
30 var values = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();
31 samples = new TProfilingSampler<TEnum>[values.Max() + 1];
32#else
33 var values = Enum.GetValues(typeof(TEnum));
34#endif
35
36 for (int i = 0; i < names.Length; i++)
37 {
38 var sample = new TProfilingSampler<TEnum>(names[i]);
39#if USE_UNSAFE
40 samples[values[i]] = sample;
41#else
42 samples.Add((TEnum)values.GetValue(i), sample);
43#endif
44 }
45 }
46
47 public TProfilingSampler(string name)
48 : base(name)
49 {
50 }
51 }
52
53 /// <summary>
54 /// Wrapper around CPU and GPU profiling samplers.
55 /// Use this along ProfilingScope to profile a piece of code.
56 /// </summary>
57 [IgnoredByDeepProfiler]
58 public class ProfilingSampler
59 {
60 /// <summary>
61 /// Get the sampler for the corresponding enumeration value.
62 /// </summary>
63 /// <typeparam name="TEnum">Type of the enumeration.</typeparam>
64 /// <param name="marker">Enumeration value.</param>
65 /// <returns>The profiling sampler for the given enumeration value.</returns>
66#if DEVELOPMENT_BUILD || UNITY_EDITOR
67 public static ProfilingSampler Get<TEnum>(TEnum marker)
68 where TEnum : Enum
69 {
70#if USE_UNSAFE
71 return TProfilingSampler<TEnum>.samples[Unsafe.As<TEnum, int>(ref marker)];
72#else
73 TProfilingSampler<TEnum>.samples.TryGetValue(marker, out var sampler);
74 return sampler;
75#endif
76 }
77#else
78 public static ProfilingSampler Get<TEnum>(TEnum marker)
79 where TEnum : Enum
80 {
81 return null;
82 }
83#endif
84
85 /// <summary>
86 /// Constructor.
87 /// </summary>
88 /// <param name="name">Name of the profiling sampler.</param>
89 public ProfilingSampler(string name)
90 {
91 // Caution: Name of sampler MUST not match name provide to cmd.BeginSample(), otherwise
92 // we get a mismatch of marker when enabling the profiler.
93#if UNITY_USE_RECORDER
94 sampler = CustomSampler.Create(name, true); // Event markers, command buffer CPU profiling and GPU profiling
95#else
96 // In this case, we need to use the BeginSample(string) API, since it creates a new sampler by that name under the hood,
97 // we need rename this sampler to not clash with the implicit one (it won't be used in this case)
98 sampler = CustomSampler.Create($"Dummy_{name}");
99#endif
100 inlineSampler = CustomSampler.Create($"Inl_{name}"); // Profiles code "immediately"
101 this.name = name;
102
103#if UNITY_USE_RECORDER
104 m_Recorder = sampler.GetRecorder();
105 m_Recorder.enabled = false;
106 m_InlineRecorder = inlineSampler.GetRecorder();
107 m_InlineRecorder.enabled = false;
108#endif
109 }
110
111 /// <summary>
112 /// Begin the profiling block.
113 /// </summary>
114 /// <param name="cmd">Command buffer used by the profiling block.</param>
115 public void Begin(CommandBuffer cmd)
116 {
117 if (cmd != null)
118#if UNITY_USE_RECORDER
119 if (sampler != null && sampler.isValid)
120 cmd.BeginSample(sampler);
121 else
122 cmd.BeginSample(name);
123#else
124 cmd.BeginSample(name);
125#endif
126 inlineSampler?.Begin();
127 }
128
129 /// <summary>
130 /// End the profiling block.
131 /// </summary>
132 /// <param name="cmd">Command buffer used by the profiling block.</param>
133 public void End(CommandBuffer cmd)
134 {
135 if (cmd != null)
136#if UNITY_USE_RECORDER
137 if (sampler != null && sampler.isValid)
138 cmd.EndSample(sampler);
139 else
140 cmd.EndSample(name);
141#else
142 m_Cmd.EndSample(name);
143#endif
144 inlineSampler?.End();
145 }
146
147 internal bool IsValid() { return (sampler != null && inlineSampler != null); }
148
149 internal CustomSampler sampler { get; private set; }
150 internal CustomSampler inlineSampler { get; private set; }
151 /// <summary>
152 /// Name of the Profiling Sampler
153 /// </summary>
154 public string name { get; private set; }
155
156#if UNITY_USE_RECORDER
157 Recorder m_Recorder;
158 Recorder m_InlineRecorder;
159#endif
160
161 /// <summary>
162 /// Set to true to enable recording of profiling sampler timings.
163 /// </summary>
164 public bool enableRecording
165 {
166 set
167 {
168#if UNITY_USE_RECORDER
169 m_Recorder.enabled = value;
170 m_InlineRecorder.enabled = value;
171#endif
172 }
173 }
174
175#if UNITY_USE_RECORDER
176 /// <summary>
177 /// GPU Elapsed time in milliseconds.
178 /// </summary>
179 public float gpuElapsedTime => m_Recorder.enabled ? m_Recorder.gpuElapsedNanoseconds / 1000000.0f : 0.0f;
180 /// <summary>
181 /// Number of times the Profiling Sampler has hit on the GPU
182 /// </summary>
183 public int gpuSampleCount => m_Recorder.enabled ? m_Recorder.gpuSampleBlockCount : 0;
184 /// <summary>
185 /// CPU Elapsed time in milliseconds (Command Buffer execution).
186 /// </summary>
187 public float cpuElapsedTime => m_Recorder.enabled ? m_Recorder.elapsedNanoseconds / 1000000.0f : 0.0f;
188 /// <summary>
189 /// Number of times the Profiling Sampler has hit on the CPU in the command buffer.
190 /// </summary>
191 public int cpuSampleCount => m_Recorder.enabled ? m_Recorder.sampleBlockCount : 0;
192 /// <summary>
193 /// CPU Elapsed time in milliseconds (Direct execution).
194 /// </summary>
195 public float inlineCpuElapsedTime => m_InlineRecorder.enabled ? m_InlineRecorder.elapsedNanoseconds / 1000000.0f : 0.0f;
196 /// <summary>
197 /// Number of times the Profiling Sampler has hit on the CPU.
198 /// </summary>
199 public int inlineCpuSampleCount => m_InlineRecorder.enabled ? m_InlineRecorder.sampleBlockCount : 0;
200#else
201 /// <summary>
202 /// GPU Elapsed time in milliseconds.
203 /// </summary>
204 public float gpuElapsedTime => 0.0f;
205 /// <summary>
206 /// Number of times the Profiling Sampler has hit on the GPU
207 /// </summary>
208 public int gpuSampleCount => 0;
209 /// <summary>
210 /// CPU Elapsed time in milliseconds (Command Buffer execution).
211 /// </summary>
212 public float cpuElapsedTime => 0.0f;
213 /// <summary>
214 /// Number of times the Profiling Sampler has hit on the CPU in the command buffer.
215 /// </summary>
216 public int cpuSampleCount => 0;
217 /// <summary>
218 /// CPU Elapsed time in milliseconds (Direct execution).
219 /// </summary>
220 public float inlineCpuElapsedTime => 0.0f;
221 /// <summary>
222 /// Number of times the Profiling Sampler has hit on the CPU.
223 /// </summary>
224 public int inlineCpuSampleCount => 0;
225#endif
226 // Keep the constructor private
227 ProfilingSampler() { }
228 }
229
230#if DEVELOPMENT_BUILD || UNITY_EDITOR
231 /// <summary>
232 /// Scoped Profiling markers
233 /// </summary>
234 [IgnoredByDeepProfiler]
235 public struct ProfilingScope : IDisposable
236 {
237 CommandBuffer m_Cmd;
238 bool m_Disposed;
239 ProfilingSampler m_Sampler;
240
241 /// <summary>
242 /// Profiling Scope constructor
243 /// </summary>
244 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
245 public ProfilingScope(ProfilingSampler sampler)
246 {
247 m_Cmd = null;
248 m_Disposed = false;
249 m_Sampler = sampler;
250 m_Sampler?.Begin(m_Cmd);
251 }
252
253 /// <summary>
254 /// Profiling Scope constructor
255 /// </summary>
256 /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
257 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
258 public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler)
259 {
260 // NOTE: Do not mix with named CommandBuffers.
261 // Currently there's an issue which results in mismatched markers.
262 // The named CommandBuffer will close its "profiling scope" on execution.
263 // That will orphan ProfilingScope markers as the named CommandBuffer marker
264 // is their "parent".
265 // Resulting in following pattern:
266 // exec(cmd.start, scope.start, cmd.end) and exec(cmd.start, scope.end, cmd.end)
267 m_Cmd = cmd;
268 m_Disposed = false;
269 m_Sampler = sampler;
270 m_Sampler?.Begin(m_Cmd);
271 }
272
273 /// <summary>
274 /// Profiling Scope constructor
275 /// </summary>
276 /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
277 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
278 public ProfilingScope(BaseCommandBuffer cmd, ProfilingSampler sampler)
279 {
280 // NOTE: Do not mix with named CommandBuffers.
281 // Currently there's an issue which results in mismatched markers.
282 // The named CommandBuffer will close its "profiling scope" on execution.
283 // That will orphan ProfilingScope markers as the named CommandBuffer marker
284 // is their "parent".
285 // Resulting in following pattern:
286 // exec(cmd.start, scope.start, cmd.end) and exec(cmd.start, scope.end, cmd.end)
287 m_Cmd = cmd.m_WrappedCommandBuffer;
288 m_Disposed = false;
289 m_Sampler = sampler;
290 m_Sampler?.Begin(m_Cmd);
291 }
292
293 /// <summary>
294 /// Dispose pattern implementation
295 /// </summary>
296 public void Dispose()
297 {
298 Dispose(true);
299 }
300
301 // Protected implementation of Dispose pattern.
302 void Dispose(bool disposing)
303 {
304 if (m_Disposed)
305 return;
306
307 // As this is a struct, it could have been initialized using an empty constructor so we
308 // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
309 // this but will generate garbage on every frame (and this struct is used quite a lot).
310 if (disposing)
311 {
312 m_Sampler?.End(m_Cmd);
313 }
314
315 m_Disposed = true;
316 }
317 }
318#else
319 /// <summary>
320 /// Scoped Profiling markers
321 /// </summary>
322 public struct ProfilingScope : IDisposable
323 {
324 /// <summary>
325 /// Profiling Scope constructor
326 /// </summary>
327 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
328 public ProfilingScope(ProfilingSampler sampler)
329 {
330 }
331
332 /// <summary>
333 /// Profiling Scope constructor
334 /// </summary>
335 /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
336 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
337 public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler)
338 {
339 }
340
341 /// <summary>
342 /// Profiling Scope constructor
343 /// </summary>
344 /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
345 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
346 public ProfilingScope(BaseCommandBuffer cmd, ProfilingSampler sampler)
347 {
348 }
349
350 /// <summary>
351 /// Dispose pattern implementation
352 /// </summary>
353 public void Dispose()
354 {
355 }
356 }
357#endif
358
359
360 /// <summary>
361 /// Profiling Sampler class.
362 /// </summary>
363 [System.Obsolete("Please use ProfilingScope")]
364 [IgnoredByDeepProfiler]
365 public struct ProfilingSample : IDisposable
366 {
367 readonly CommandBuffer m_Cmd;
368 readonly string m_Name;
369
370 bool m_Disposed;
371 CustomSampler m_Sampler;
372
373 /// <summary>
374 /// Constructor
375 /// </summary>
376 /// <param name="cmd">Command Buffer.</param>
377 /// <param name="name">Name of the profiling sample.</param>
378 /// <param name="sampler">Custom sampler for CPU profiling.</param>
379 public ProfilingSample(CommandBuffer cmd, string name, CustomSampler sampler = null)
380 {
381 m_Cmd = cmd;
382 m_Name = name;
383 m_Disposed = false;
384 if (cmd != null && name != "")
385 cmd.BeginSample(name);
386 m_Sampler = sampler;
387 m_Sampler?.Begin();
388 }
389
390 // Shortcut to string.Format() using only one argument (reduces Gen0 GC pressure)
391 /// <summary>
392 /// Constructor
393 /// </summary>
394 /// <param name="cmd">Command Buffer.</param>
395 /// <param name="format">Formating of the profiling sample.</param>
396 /// <param name="arg">Parameters for formating the name.</param>
397 public ProfilingSample(CommandBuffer cmd, string format, object arg) : this(cmd, string.Format(format, arg))
398 {
399 }
400
401 // Shortcut to string.Format() with variable amount of arguments - for performance critical
402 // code you should pre-build & cache the marker name instead of using this
403 /// <summary>
404 /// Constructor.
405 /// </summary>
406 /// <param name="cmd">Command Buffer.</param>
407 /// <param name="format">Formating of the profiling sample.</param>
408 /// <param name="args">Parameters for formating the name.</param>
409 public ProfilingSample(CommandBuffer cmd, string format, params object[] args) : this(cmd, string.Format(format, args))
410 {
411 }
412
413 /// <summary>
414 /// Dispose pattern implementation
415 /// </summary>
416 public void Dispose()
417 {
418 Dispose(true);
419 }
420
421 // Protected implementation of Dispose pattern.
422 void Dispose(bool disposing)
423 {
424 if (m_Disposed)
425 return;
426
427 // As this is a struct, it could have been initialized using an empty constructor so we
428 // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
429 // this but will generate garbage on every frame (and this struct is used quite a lot).
430 if (disposing)
431 {
432 if (m_Cmd != null && m_Name != "")
433 m_Cmd.EndSample(m_Name);
434 m_Sampler?.End();
435 }
436
437 m_Disposed = true;
438 }
439 }
440}