// TProfilingSampler.samples should just be an array. Unfortunately, Enum cannot be converted to int without generating garbage. // This could be worked around by using Unsafe but it's not available at the moment. // So in the meantime we use a Dictionary with a perf hit... //#define USE_UNSAFE #if UNITY_2020_1_OR_NEWER #define UNITY_USE_RECORDER #endif using System; using System.Linq; using System.Collections.Generic; using UnityEngine.Profiling; namespace UnityEngine.Rendering { class TProfilingSampler : ProfilingSampler where TEnum : Enum { #if USE_UNSAFE internal static TProfilingSampler[] samples; #else internal static Dictionary> samples = new Dictionary>(); #endif static TProfilingSampler() { var names = Enum.GetNames(typeof(TEnum)); #if USE_UNSAFE var values = Enum.GetValues(typeof(TEnum)).Cast().ToArray(); samples = new TProfilingSampler[values.Max() + 1]; #else var values = Enum.GetValues(typeof(TEnum)); #endif for (int i = 0; i < names.Length; i++) { var sample = new TProfilingSampler(names[i]); #if USE_UNSAFE samples[values[i]] = sample; #else samples.Add((TEnum)values.GetValue(i), sample); #endif } } public TProfilingSampler(string name) : base(name) { } } /// /// Wrapper around CPU and GPU profiling samplers. /// Use this along ProfilingScope to profile a piece of code. /// public class ProfilingSampler { /// /// Get the sampler for the corresponding enumeration value. /// /// Type of the enumeration. /// Enumeration value. /// The profiling sampler for the given enumeration value. public static ProfilingSampler Get(TEnum marker) where TEnum : Enum { #if USE_UNSAFE return TProfilingSampler.samples[Unsafe.As(ref marker)]; #else TProfilingSampler.samples.TryGetValue(marker, out var sampler); return sampler; #endif } /// /// Constructor. /// /// Name of the profiling sampler. public ProfilingSampler(string name) { // Caution: Name of sampler MUST not match name provide to cmd.BeginSample(), otherwise // we get a mismatch of marker when enabling the profiler. #if UNITY_USE_RECORDER sampler = CustomSampler.Create(name, true); // Event markers, command buffer CPU profiling and GPU profiling #else // In this case, we need to use the BeginSample(string) API, since it creates a new sampler by that name under the hood, // we need rename this sampler to not clash with the implicit one (it won't be used in this case) sampler = CustomSampler.Create($"Dummy_{name}"); #endif inlineSampler = CustomSampler.Create($"Inl_{name}"); // Profiles code "immediately" this.name = name; #if UNITY_USE_RECORDER m_Recorder = sampler.GetRecorder(); m_Recorder.enabled = false; m_InlineRecorder = inlineSampler.GetRecorder(); m_InlineRecorder.enabled = false; #endif } /// /// Begin the profiling block. /// /// Command buffer used by the profiling block. public void Begin(CommandBuffer cmd) { if (cmd != null) #if UNITY_USE_RECORDER if (sampler != null && sampler.isValid) cmd.BeginSample(sampler); else cmd.BeginSample(name); #else cmd.BeginSample(name); #endif inlineSampler?.Begin(); } /// /// End the profiling block. /// /// Command buffer used by the profiling block. public void End(CommandBuffer cmd) { if (cmd != null) #if UNITY_USE_RECORDER if (sampler != null && sampler.isValid) cmd.EndSample(sampler); else cmd.EndSample(name); #else m_Cmd.EndSample(name); #endif inlineSampler?.End(); } internal bool IsValid() { return (sampler != null && inlineSampler != null); } internal CustomSampler sampler { get; private set; } internal CustomSampler inlineSampler { get; private set; } /// /// Name of the Profiling Sampler /// public string name { get; private set; } #if UNITY_USE_RECORDER Recorder m_Recorder; Recorder m_InlineRecorder; #endif /// /// Set to true to enable recording of profiling sampler timings. /// public bool enableRecording { set { #if UNITY_USE_RECORDER m_Recorder.enabled = value; m_InlineRecorder.enabled = value; #endif } } #if UNITY_USE_RECORDER /// /// GPU Elapsed time in milliseconds. /// public float gpuElapsedTime => m_Recorder.enabled ? m_Recorder.gpuElapsedNanoseconds / 1000000.0f : 0.0f; /// /// Number of times the Profiling Sampler has hit on the GPU /// public int gpuSampleCount => m_Recorder.enabled ? m_Recorder.gpuSampleBlockCount : 0; /// /// CPU Elapsed time in milliseconds (Command Buffer execution). /// public float cpuElapsedTime => m_Recorder.enabled ? m_Recorder.elapsedNanoseconds / 1000000.0f : 0.0f; /// /// Number of times the Profiling Sampler has hit on the CPU in the command buffer. /// public int cpuSampleCount => m_Recorder.enabled ? m_Recorder.sampleBlockCount : 0; /// /// CPU Elapsed time in milliseconds (Direct execution). /// public float inlineCpuElapsedTime => m_InlineRecorder.enabled ? m_InlineRecorder.elapsedNanoseconds / 1000000.0f : 0.0f; /// /// Number of times the Profiling Sampler has hit on the CPU. /// public int inlineCpuSampleCount => m_InlineRecorder.enabled ? m_InlineRecorder.sampleBlockCount : 0; #else /// /// GPU Elapsed time in milliseconds. /// public float gpuElapsedTime => 0.0f; /// /// Number of times the Profiling Sampler has hit on the GPU /// public int gpuSampleCount => 0; /// /// CPU Elapsed time in milliseconds (Command Buffer execution). /// public float cpuElapsedTime => 0.0f; /// /// Number of times the Profiling Sampler has hit on the CPU in the command buffer. /// public int cpuSampleCount => 0; /// /// CPU Elapsed time in milliseconds (Direct execution). /// public float inlineCpuElapsedTime => 0.0f; /// /// Number of times the Profiling Sampler has hit on the CPU. /// public int inlineCpuSampleCount => 0; #endif // Keep the constructor private ProfilingSampler() { } } #if DEVELOPMENT_BUILD || UNITY_EDITOR /// /// Scoped Profiling markers /// public struct ProfilingScope : IDisposable { CommandBuffer m_Cmd; bool m_Disposed; ProfilingSampler m_Sampler; /// /// Profiling Scope constructor /// /// Command buffer used to add markers and compute execution timings. /// Profiling Sampler to be used for this scope. public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler) { // NOTE: Do not mix with named CommandBuffers. // Currently there's an issue which results in mismatched markers. // The named CommandBuffer will close its "profiling scope" on execution. // That will orphan ProfilingScope markers as the named CommandBuffer marker // is their "parent". // Resulting in following pattern: // exec(cmd.start, scope.start, cmd.end) and exec(cmd.start, scope.end, cmd.end) m_Cmd = cmd; m_Disposed = false; m_Sampler = sampler; m_Sampler?.Begin(m_Cmd); } /// /// Dispose pattern implementation /// public void Dispose() { Dispose(true); } // Protected implementation of Dispose pattern. void Dispose(bool disposing) { if (m_Disposed) return; // As this is a struct, it could have been initialized using an empty constructor so we // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix // this but will generate garbage on every frame (and this struct is used quite a lot). if (disposing) { m_Sampler?.End(m_Cmd); } m_Disposed = true; } } #else /// /// Scoped Profiling markers /// public struct ProfilingScope : IDisposable { /// /// Profiling Scope constructor /// /// Command buffer used to add markers and compute execution timings. /// Profiling Sampler to be used for this scope. public ProfilingScope(CommandBuffer cmd, ProfilingSampler sampler) { } /// /// Dispose pattern implementation /// public void Dispose() { } } #endif /// /// Profiling Sampler class. /// [System.Obsolete("Please use ProfilingScope")] public struct ProfilingSample : IDisposable { readonly CommandBuffer m_Cmd; readonly string m_Name; bool m_Disposed; CustomSampler m_Sampler; /// /// Constructor /// /// Command Buffer. /// Name of the profiling sample. /// Custom sampler for CPU profiling. public ProfilingSample(CommandBuffer cmd, string name, CustomSampler sampler = null) { m_Cmd = cmd; m_Name = name; m_Disposed = false; if (cmd != null && name != "") cmd.BeginSample(name); m_Sampler = sampler; m_Sampler?.Begin(); } // Shortcut to string.Format() using only one argument (reduces Gen0 GC pressure) /// /// Constructor /// /// Command Buffer. /// Formating of the profiling sample. /// Parameters for formating the name. public ProfilingSample(CommandBuffer cmd, string format, object arg) : this(cmd, string.Format(format, arg)) { } // Shortcut to string.Format() with variable amount of arguments - for performance critical // code you should pre-build & cache the marker name instead of using this /// /// Constructor. /// /// Command Buffer. /// Formating of the profiling sample. /// Parameters for formating the name. public ProfilingSample(CommandBuffer cmd, string format, params object[] args) : this(cmd, string.Format(format, args)) { } /// /// Dispose pattern implementation /// public void Dispose() { Dispose(true); } // Protected implementation of Dispose pattern. void Dispose(bool disposing) { if (m_Disposed) return; // As this is a struct, it could have been initialized using an empty constructor so we // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix // this but will generate garbage on every frame (and this struct is used quite a lot). if (disposing) { if (m_Cmd != null && m_Name != "") m_Cmd.EndSample(m_Name); m_Sampler?.End(); } m_Disposed = true; } } }