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}