using System; using System.Runtime.InteropServices; namespace Glass.Testbed { /// /// Writes each frame's start and costs into a SlimTune capture when the Testbed is being profiled by SlimTune. /// public class SlimTuneFrameMarker { private const string ProfilerModule = "SlimTuneCLR.dll"; // SlimTune offsets user counter IDs past its own, so these land in the capture as 32 and up. public const uint FrameCounterId = 0; public const uint UpdateCounterId = 1; public const uint DrawCounterId = 2; public const uint SubmitCounterId = 3; public const uint GpuWaitCounterId = 4; public const uint VSyncWaitCounterId = 5; public const uint AllocatedCounterId = 6; public const uint Gen0CounterId = 7; public const uint Gen1CounterId = 8; public const uint Gen2CounterId = 9; public const uint StartProcessorCounterId = 10; public const uint EndProcessorCounterId = 11; public const uint UpdateCyclesCounterId = 12; public const uint DrawCyclesCounterId = 13; [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string moduleName); // Only ever called once the profiler has loaded this module itself, so these bind to its copy. [DllImport(ProfilerModule)] private static extern int IsProfilerConnected(); [DllImport(ProfilerModule)] private static extern void SetCounterName(uint counterId, [MarshalAs(UnmanagedType.LPWStr)] string name); [DllImport(ProfilerModule)] private static extern void WritePerfCounter(uint counterId, double value); private bool named; private long frameNumber; private uint startProcessor; public SlimTuneFrameMarker() { // Asks whether the profiler already loaded its backend, so a normal run never loads it from disk. IsProfiling = GetModuleHandle(ProfilerModule) != IntPtr.Zero; } public bool IsProfiling { get; } private static void NameCounters() { SetCounterName(FrameCounterId, "Frame"); SetCounterName(UpdateCounterId, "Update ms"); SetCounterName(DrawCounterId, "Draw ms"); SetCounterName(SubmitCounterId, "Submit ms"); SetCounterName(GpuWaitCounterId, "GPU wait ms"); SetCounterName(VSyncWaitCounterId, "VSync wait ms"); SetCounterName(AllocatedCounterId, "Allocated bytes"); SetCounterName(Gen0CounterId, "Gen 0 collections"); SetCounterName(Gen1CounterId, "Gen 1 collections"); SetCounterName(Gen2CounterId, "Gen 2 collections"); SetCounterName(StartProcessorCounterId, "Processor at start"); SetCounterName(EndProcessorCounterId, "Processor at end"); SetCounterName(UpdateCyclesCounterId, "Update cycles"); SetCounterName(DrawCyclesCounterId, "Draw cycles"); } // The SlimTune UI commits each counter value on its own, so values that are usually zero are left out. private static void WriteIfNonZero(uint counterId, double value) { if (value != 0) { WritePerfCounter(counterId, value); } } public void MarkFrameStart() { if (!IsProfiling) { return; } // The backend drops whatever it's sent before the UI connects, so the names wait for a connection. if (!named) { if (IsProfilerConnected() == 0) { return; } NameCounters(); named = true; } WritePerfCounter(FrameCounterId, frameNumber); ++frameNumber; startProcessor = NativeMethods.GetCurrentProcessorNumber(); WritePerfCounter(StartProcessorCounterId, startProcessor); } /// /// Writes what the frame begun by the latest MarkFrameStart cost. /// public void RecordFrame(in FrameRecord frame) { if (!named) { return; } // Capture timestamps are whole milliseconds, so readers match these to their frame by row order. They // land after this frame's mark and before the next one's. WritePerfCounter(UpdateCounterId, frame.Update.TotalMilliseconds); WritePerfCounter(DrawCounterId, frame.Draw.TotalMilliseconds); WritePerfCounter(SubmitCounterId, frame.Cost.Submit.TotalMilliseconds); WriteIfNonZero(GpuWaitCounterId, frame.Cost.GpuWait.TotalMilliseconds); WritePerfCounter(VSyncWaitCounterId, frame.Cost.VSyncWait.TotalMilliseconds); WriteIfNonZero(AllocatedCounterId, frame.AllocatedBytes); WriteIfNonZero(Gen0CounterId, frame.Collections.Gen0); WriteIfNonZero(Gen1CounterId, frame.Collections.Gen1); WriteIfNonZero(Gen2CounterId, frame.Collections.Gen2); WritePerfCounter(UpdateCyclesCounterId, frame.UpdateCycles); WritePerfCounter(DrawCyclesCounterId, frame.DrawCycles); // Only when the thread moved during the frame, since it usually doesn't. uint endProcessor = NativeMethods.GetCurrentProcessorNumber(); if (endProcessor != startProcessor) { WritePerfCounter(EndProcessorCounterId, endProcessor); } } } }