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);
}
}
}
}