2024-10-19 23:41:05 +02:00
|
|
|
|
using ImGuiNET;
|
|
|
|
|
using MoonTools.ECS;
|
|
|
|
|
using Nerfed.Runtime;
|
|
|
|
|
|
|
|
|
|
namespace Nerfed.Editor.Systems
|
|
|
|
|
{
|
|
|
|
|
internal class EditorProfilerWindow : MoonTools.ECS.System
|
|
|
|
|
{
|
|
|
|
|
const ImGuiTableFlags tableFlags = ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.NoBordersInBody | ImGuiTableFlags.ScrollY | ImGuiTableFlags.ScrollX;
|
|
|
|
|
const ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags.SpanAllColumns;
|
|
|
|
|
const ImGuiTreeNodeFlags treeNodeLeafFlags = ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen;
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
private int selectedFrame = 0;
|
|
|
|
|
private int previousSelectedFrame = -1;
|
|
|
|
|
private IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> orderedCombinedData = null;
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
|
|
|
|
public EditorProfilerWindow(World world) : base(world)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Update(TimeSpan delta)
|
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (Profiler.Frames.Count <= 0)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.Begin("Profiler");
|
|
|
|
|
|
|
|
|
|
ImGui.BeginChild("Toolbar", new System.Numerics.Vector2(0, 0), ImGuiChildFlags.AutoResizeY);
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (ImGui.RadioButton("Recording", Profiler.IsRecording))
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
Profiler.SetActive(!Profiler.IsRecording);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.SameLine();
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (Profiler.IsRecording)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
// Select last frame when recording to see latest frame data.
|
|
|
|
|
selectedFrame = Profiler.Frames.Count - 1;
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (ImGui.SliderInt(string.Empty, ref selectedFrame, 0, Profiler.Frames.Count - 1))
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
// Stop recording when browsing frames.
|
|
|
|
|
Profiler.SetActive(false);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
Profiler.Frame frame = Profiler.Frames.ElementAt(selectedFrame);
|
|
|
|
|
double ms = frame.ElapsedMilliseconds();
|
2024-10-19 23:41:05 +02:00
|
|
|
|
double s = 1000;
|
2024-10-20 23:17:41 +02:00
|
|
|
|
ImGui.Text($"Frame: {frame.FrameCount} ({ms:0.000} ms | {(s / ms):0} fps)");
|
2024-10-19 23:41:05 +02:00
|
|
|
|
ImGui.EndChild();
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (!Profiler.IsRecording) {
|
|
|
|
|
if (previousSelectedFrame != selectedFrame)
|
|
|
|
|
{
|
|
|
|
|
previousSelectedFrame = selectedFrame;
|
|
|
|
|
orderedCombinedData = CalculateCombinedData(frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DrawFlameGraph(frame);
|
|
|
|
|
|
|
|
|
|
DrawHierachy(frame);
|
|
|
|
|
|
|
|
|
|
ImGui.SameLine();
|
|
|
|
|
|
|
|
|
|
DrawCombined(orderedCombinedData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.End();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void DrawHierachy(Profiler.Frame frame)
|
|
|
|
|
{
|
|
|
|
|
if(frame == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-19 23:41:05 +02:00
|
|
|
|
ImGui.BeginChild("Hierachy", new System.Numerics.Vector2(150, 0), ImGuiChildFlags.ResizeX);
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (ImGui.BeginTable("ProfilerData", 3, tableFlags, new System.Numerics.Vector2(0, 0)))
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
|
|
|
|
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.8f, 0);
|
2024-10-20 23:17:41 +02:00
|
|
|
|
ImGui.TableSetupColumn("thread", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
|
|
|
|
ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible
|
|
|
|
|
ImGui.TableHeadersRow();
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
foreach (Profiler.ScopeNode node in frame.RootNodes)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
DrawHierachyNode(node);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.EndTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.EndChild();
|
2024-10-20 23:17:41 +02:00
|
|
|
|
}
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
private static void DrawHierachyNode(Profiler.ScopeNode node)
|
|
|
|
|
{
|
|
|
|
|
ImGui.TableNextRow();
|
|
|
|
|
ImGui.TableNextColumn();
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
bool isOpen = false;
|
|
|
|
|
bool isLeaf = node.Children.Count == 0;
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
if (isLeaf) {
|
|
|
|
|
ImGui.TreeNodeEx(node.Label, treeNodeLeafFlags);
|
|
|
|
|
}
|
|
|
|
|
else
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
isOpen = ImGui.TreeNodeEx(node.Label, treeNodeFlags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
|
ImGui.Text($"{node.ManagedThreadId}");
|
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
|
ImGui.Text($"{node.ElapsedMilliseconds():0.000}");
|
|
|
|
|
|
|
|
|
|
if (isOpen)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < node.Children.Count; i++)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
DrawHierachyNode(node.Children[i]);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
2024-10-20 23:17:41 +02:00
|
|
|
|
ImGui.TreePop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void DrawCombined(in IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> orderedCombinedData)
|
|
|
|
|
{
|
|
|
|
|
if(orderedCombinedData == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
2024-10-20 23:17:41 +02:00
|
|
|
|
|
|
|
|
|
ImGui.BeginChild("Combined", new System.Numerics.Vector2(0, 0));
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
2024-10-20 03:51:59 +02:00
|
|
|
|
if (ImGui.BeginTable("ProfilerCombinedData", 3, tableFlags, new System.Numerics.Vector2(0, 0)))
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 03:51:59 +02:00
|
|
|
|
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.6f, 0);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
2024-10-20 03:51:59 +02:00
|
|
|
|
ImGui.TableSetupColumn("calls", ImGuiTableColumnFlags.WidthStretch, 0.2f, 2);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible
|
|
|
|
|
ImGui.TableHeadersRow();
|
|
|
|
|
|
2024-10-20 03:51:59 +02:00
|
|
|
|
foreach (KeyValuePair<string, (double ms, uint calls)> combinedData in orderedCombinedData)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
|
|
|
|
ImGui.TableNextRow();
|
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
|
ImGui.Text($"{combinedData.Key}");
|
|
|
|
|
ImGui.TableNextColumn();
|
2024-10-20 03:51:59 +02:00
|
|
|
|
ImGui.Text($"{combinedData.Value.ms:0.000}");
|
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
|
ImGui.Text($"{combinedData.Value.calls}");
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.EndTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui.EndChild();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
private static IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> CalculateCombinedData(Profiler.Frame frame)
|
2024-10-19 23:41:05 +02:00
|
|
|
|
{
|
2024-10-20 23:17:41 +02:00
|
|
|
|
Dictionary<string, (double ms, uint calls)> combinedRecordData = new Dictionary<string, (double ms, uint calls)>(128);
|
|
|
|
|
foreach (Profiler.ScopeNode node in frame.RootNodes)
|
|
|
|
|
{
|
|
|
|
|
CalculateCombinedData(node, in combinedRecordData);
|
|
|
|
|
}
|
|
|
|
|
return combinedRecordData.OrderByDescending(x => x.Value.ms);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void CalculateCombinedData(Profiler.ScopeNode node, in Dictionary<string, (double ms, uint calls)> combinedRecordData)
|
|
|
|
|
{
|
|
|
|
|
if (combinedRecordData.TryGetValue(node.Label, out (double ms, uint calls) combined))
|
|
|
|
|
{
|
|
|
|
|
combinedRecordData[node.Label] = (combined.ms + node.ElapsedMilliseconds(), combined.calls + 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
combinedRecordData.Add(node.Label, (node.ElapsedMilliseconds(), 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < node.Children.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
CalculateCombinedData(node.Children[i], combinedRecordData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void DrawFlameGraph(Profiler.Frame frame)
|
|
|
|
|
{
|
|
|
|
|
if (frame == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-10-19 23:41:05 +02:00
|
|
|
|
|
2024-10-20 23:17:41 +02:00
|
|
|
|
ProfilerVisualizer.RenderFlameGraph(frame);
|
2024-10-19 23:41:05 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|