ProfilingScope.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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. #if UNITY_2020_1_OR_NEWER
  6. //#define UNITY_USE_RECORDER // Temporarily commented out until a crash is fixed in GPU profiling samplers.
  7. #endif
  8. using System;
  9. using System.Linq;
  10. using System.Collections.Generic;
  11. using UnityEngine.Profiling;
  12. namespace UnityEngine.Rendering
  13. {
  14. class TProfilingSampler<TEnum> : ProfilingSampler where TEnum : Enum
  15. {
  16. #if USE_UNSAFE
  17. internal static TProfilingSampler<TEnum>[] samples;
  18. #else
  19. internal static Dictionary<TEnum, TProfilingSampler<TEnum>> samples = new Dictionary<TEnum, TProfilingSampler<TEnum>>();
  20. #endif
  21. static TProfilingSampler()
  22. {
  23. var names = Enum.GetNames(typeof(TEnum));
  24. #if USE_UNSAFE
  25. var values = Enum.GetValues(typeof(TEnum)).Cast<int>().ToArray();
  26. samples = new TProfilingSampler<TEnum>[values.Max() + 1];
  27. #else
  28. var values = Enum.GetValues(typeof(TEnum));
  29. #endif
  30. for (int i = 0; i < names.Length; i++)
  31. {
  32. var sample = new TProfilingSampler<TEnum>(names[i]);
  33. #if USE_UNSAFE
  34. samples[values[i]] = sample;
  35. #else
  36. samples.Add((TEnum)values.GetValue(i), sample);
  37. #endif
  38. }
  39. }
  40. public TProfilingSampler(string name)
  41. : base(name)
  42. {
  43. }
  44. }
  45. /// <summary>
  46. /// Wrapper around CPU and GPU profiling samplers.
  47. /// Use this along ProfilingScope to profile a piece of code.
  48. /// </summary>
  49. public class ProfilingSampler
  50. {
  51. /// <summary>
  52. /// Get the sampler for the corresponding enumeration value.
  53. /// </summary>
  54. /// <typeparam name="TEnum">Type of the enumeration.</typeparam>
  55. /// <param name="marker">Enumeration value.</param>
  56. /// <returns>The profiling sampler for the given enumeration value.</returns>
  57. public static ProfilingSampler Get<TEnum>(TEnum marker)
  58. where TEnum : Enum
  59. {
  60. #if USE_UNSAFE
  61. return TProfilingSampler<TEnum>.samples[Unsafe.As<TEnum, int>(ref marker)];
  62. #else
  63. TProfilingSampler<TEnum>.samples.TryGetValue(marker, out var sampler);
  64. return sampler;
  65. #endif
  66. }
  67. /// <summary>
  68. /// Constructor.
  69. /// </summary>
  70. /// <param name="name">Name of the profiling sampler.</param>
  71. public ProfilingSampler(string name)
  72. {
  73. // Caution: Name of sampler MUST not match name provide to cmd.BeginSample(), otherwise
  74. // we get a mismatch of marker when enabling the profiler.
  75. #if UNITY_USE_RECORDER
  76. sampler = CustomSampler.Create(name, true); // Event markers, command buffer CPU profiling and GPU profiling
  77. #else
  78. // In this case, we need to use the BeginSample(string) API, since it creates a new sampler by that name under the hood,
  79. // we need rename this sampler to not clash with the implicit one (it won't be used in this case)
  80. sampler = CustomSampler.Create($"Dummy_{name}");
  81. #endif
  82. inlineSampler = CustomSampler.Create($"Inl_{name}"); // Profiles code "immediately"
  83. this.name = name;
  84. #if UNITY_USE_RECORDER
  85. m_Recorder = sampler.GetRecorder();
  86. m_Recorder.enabled = false;
  87. m_InlineRecorder = inlineSampler.GetRecorder();
  88. m_InlineRecorder.enabled = false;
  89. #endif
  90. }
  91. internal bool IsValid() { return (sampler != null && inlineSampler != null); }
  92. internal CustomSampler sampler { get; private set; }
  93. internal CustomSampler inlineSampler { get; private set; }
  94. /// <summary>
  95. /// Name of the Profiling Sampler
  96. /// </summary>
  97. public string name { get; private set; }
  98. #if UNITY_USE_RECORDER
  99. Recorder m_Recorder;
  100. Recorder m_InlineRecorder;
  101. #endif
  102. /// <summary>
  103. /// Set to true to enable recording of profiling sampler timings.
  104. /// </summary>
  105. public bool enableRecording
  106. {
  107. set
  108. {
  109. #if UNITY_USE_RECORDER
  110. m_Recorder.enabled = value;
  111. m_InlineRecorder.enabled = value;
  112. #endif
  113. }
  114. }
  115. #if UNITY_USE_RECORDER
  116. /// <summary>
  117. /// GPU Elapsed time in milliseconds.
  118. /// </summary>
  119. public float gpuElapsedTime => m_Recorder.enabled ? m_Recorder.gpuElapsedNanoseconds / 1000000.0f : 0.0f;
  120. /// <summary>
  121. /// Number of times the Profiling Sampler has hit on the GPU
  122. /// </summary>
  123. public int gpuSampleCount => m_Recorder.enabled ? m_Recorder.gpuSampleBlockCount : 0;
  124. /// <summary>
  125. /// CPU Elapsed time in milliseconds (Command Buffer execution).
  126. /// </summary>
  127. public float cpuElapsedTime => m_Recorder.enabled ? m_Recorder.elapsedNanoseconds / 1000000.0f : 0.0f;
  128. /// <summary>
  129. /// Number of times the Profiling Sampler has hit on the CPU in the command buffer.
  130. /// </summary>
  131. public int cpuSampleCount => m_Recorder.enabled ? m_Recorder.sampleBlockCount : 0;
  132. /// <summary>
  133. /// CPU Elapsed time in milliseconds (Direct execution).
  134. /// </summary>
  135. public float inlineCpuElapsedTime => m_InlineRecorder.enabled ? m_InlineRecorder.elapsedNanoseconds / 1000000.0f : 0.0f;
  136. /// <summary>
  137. /// Number of times the Profiling Sampler has hit on the CPU.
  138. /// </summary>
  139. public int inlineCpuSampleCount => m_InlineRecorder.enabled ? m_InlineRecorder.sampleBlockCount : 0;
  140. #else
  141. /// <summary>
  142. /// GPU Elapsed time in milliseconds.
  143. /// </summary>
  144. public float gpuElapsedTime => 0.0f;
  145. /// <summary>
  146. /// Number of times the Profiling Sampler has hit on the GPU
  147. /// </summary>
  148. public int gpuSampleCount => 0;
  149. /// <summary>
  150. /// CPU Elapsed time in milliseconds (Command Buffer execution).
  151. /// </summary>
  152. public float cpuElapsedTime => 0.0f;
  153. /// <summary>
  154. /// Number of times the Profiling Sampler has hit on the CPU in the command buffer.
  155. /// </summary>
  156. public int cpuSampleCount => 0;
  157. /// <summary>
  158. /// CPU Elapsed time in milliseconds (Direct execution).
  159. /// </summary>
  160. public float inlineCpuElapsedTime => 0.0f;
  161. /// <summary>
  162. /// Number of times the Profiling Sampler has hit on the CPU.
  163. /// </summary>
  164. public int inlineCpuSampleCount => 0;
  165. #endif
  166. // Keep the constructor private
  167. ProfilingSampler() { }
  168. }
  169. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  170. /// <summary>
  171. /// Scoped Profiling markers
  172. /// </summary>
  173. public struct ProfilingScope : IDisposable
  174. {
  175. string m_Name;
  176. CommandBuffer m_Cmd;
  177. bool m_Disposed;
  178. CustomSampler m_Sampler;
  179. CustomSampler m_InlineSampler;
  180. /// <summary>
  181. /// Profiling Scope constructor
  182. /// </summary>
  183. /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
  184. /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
  185. public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler)
  186. {
  187. m_Cmd = cmd;
  188. m_Disposed = false;
  189. if (sampler != null)
  190. {
  191. m_Name = sampler.name; // Don't use CustomSampler.name because it causes garbage
  192. m_Sampler = sampler.sampler;
  193. m_InlineSampler = sampler.inlineSampler;
  194. }
  195. else
  196. {
  197. m_Name = "NullProfilingSampler"; // Don't use CustomSampler.name because it causes garbage
  198. m_Sampler = null;
  199. m_InlineSampler = null;
  200. }
  201. if (cmd != null)
  202. #if UNITY_USE_RECORDER
  203. cmd.BeginSample(m_Sampler);
  204. #else
  205. cmd.BeginSample(m_Name);
  206. #endif
  207. m_InlineSampler?.Begin();
  208. }
  209. /// <summary>
  210. /// Dispose pattern implementation
  211. /// </summary>
  212. public void Dispose()
  213. {
  214. Dispose(true);
  215. }
  216. // Protected implementation of Dispose pattern.
  217. void Dispose(bool disposing)
  218. {
  219. if (m_Disposed)
  220. return;
  221. // As this is a struct, it could have been initialized using an empty constructor so we
  222. // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
  223. // this but will generate garbage on every frame (and this struct is used quite a lot).
  224. if (disposing)
  225. {
  226. if (m_Cmd != null)
  227. #if UNITY_USE_RECORDER
  228. m_Cmd.EndSample(m_Sampler);
  229. #else
  230. m_Cmd.EndSample(m_Name);
  231. #endif
  232. m_InlineSampler?.End();
  233. }
  234. m_Disposed = true;
  235. }
  236. }
  237. #else
  238. /// <summary>
  239. /// Scoped Profiling markers
  240. /// </summary>
  241. public struct ProfilingScope : IDisposable
  242. {
  243. /// <summary>
  244. /// Profiling Scope constructor
  245. /// </summary>
  246. /// <param name="cmd">Command buffer used to add markers and compute execution timings.</param>
  247. /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
  248. public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler)
  249. {
  250. }
  251. /// <summary>
  252. /// Dispose pattern implementation
  253. /// </summary>
  254. public void Dispose()
  255. {
  256. }
  257. }
  258. #endif
  259. /// <summary>
  260. /// Profiling Sampler class.
  261. /// </summary>
  262. [System.Obsolete("Please use ProfilingScope")]
  263. public struct ProfilingSample : IDisposable
  264. {
  265. readonly CommandBuffer m_Cmd;
  266. readonly string m_Name;
  267. bool m_Disposed;
  268. CustomSampler m_Sampler;
  269. /// <summary>
  270. /// Constructor
  271. /// </summary>
  272. /// <param name="cmd">Command Buffer.</param>
  273. /// <param name="name">Name of the profiling sample.</param>
  274. /// <param name="sampler">Custom sampler for CPU profiling.</param>
  275. public ProfilingSample(CommandBuffer cmd, string name, CustomSampler sampler = null)
  276. {
  277. m_Cmd = cmd;
  278. m_Name = name;
  279. m_Disposed = false;
  280. if (cmd != null && name != "")
  281. cmd.BeginSample(name);
  282. m_Sampler = sampler;
  283. m_Sampler?.Begin();
  284. }
  285. // Shortcut to string.Format() using only one argument (reduces Gen0 GC pressure)
  286. /// <summary>
  287. /// Constructor
  288. /// </summary>
  289. /// <param name="cmd">Command Buffer.</param>
  290. /// <param name="format">Formating of the profiling sample.</param>
  291. /// <param name="arg">Parameters for formating the name.</param>
  292. public ProfilingSample(CommandBuffer cmd, string format, object arg) : this(cmd, string.Format(format, arg))
  293. {
  294. }
  295. // Shortcut to string.Format() with variable amount of arguments - for performance critical
  296. // code you should pre-build & cache the marker name instead of using this
  297. /// <summary>
  298. /// Constructor.
  299. /// </summary>
  300. /// <param name="cmd">Command Buffer.</param>
  301. /// <param name="format">Formating of the profiling sample.</param>
  302. /// <param name="args">Parameters for formating the name.</param>
  303. public ProfilingSample(CommandBuffer cmd, string format, params object[] args) : this(cmd, string.Format(format, args))
  304. {
  305. }
  306. /// <summary>
  307. /// Dispose pattern implementation
  308. /// </summary>
  309. public void Dispose()
  310. {
  311. Dispose(true);
  312. }
  313. // Protected implementation of Dispose pattern.
  314. void Dispose(bool disposing)
  315. {
  316. if (m_Disposed)
  317. return;
  318. // As this is a struct, it could have been initialized using an empty constructor so we
  319. // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
  320. // this but will generate garbage on every frame (and this struct is used quite a lot).
  321. if (disposing)
  322. {
  323. if (m_Cmd != null && m_Name != "")
  324. m_Cmd.EndSample(m_Name);
  325. m_Sampler?.End();
  326. }
  327. m_Disposed = true;
  328. }
  329. }
  330. }