diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 7ae220395..34bb6e5c3 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -54,6 +54,7 @@ namespace Dalamud.Interface.Internal private readonly StyleEditorWindow styleEditorWindow; private readonly TitleScreenMenuWindow titleScreenMenuWindow; private readonly FallbackFontNoticeWindow fallbackFontNoticeWindow; + private readonly ProfilerWindow profilerWindow; private readonly TextureWrap logoTexture; private readonly TextureWrap tsmLogoTexture; @@ -99,6 +100,7 @@ namespace Dalamud.Interface.Internal this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false }; this.fallbackFontNoticeWindow = new FallbackFontNoticeWindow() { IsOpen = interfaceManager.IsFallbackFontMode && !configuration.DisableFontFallbackNotice }; + this.profilerWindow = new ProfilerWindow() { IsOpen = false }; this.WindowSystem.AddWindow(this.changelogWindow); this.WindowSystem.AddWindow(this.colorDemoWindow); @@ -115,6 +117,7 @@ namespace Dalamud.Interface.Internal this.WindowSystem.AddWindow(this.styleEditorWindow); this.WindowSystem.AddWindow(this.titleScreenMenuWindow); this.WindowSystem.AddWindow(this.fallbackFontNoticeWindow); + this.WindowSystem.AddWindow(this.profilerWindow); ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; @@ -260,6 +263,11 @@ namespace Dalamud.Interface.Internal /// Opens the . /// public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenProfiler() => this.profilerWindow.IsOpen = true; #endregion @@ -351,6 +359,11 @@ namespace Dalamud.Interface.Internal /// Toggles the . /// public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleProfilerWindow() => this.profilerWindow.Toggle(); #endregion @@ -543,6 +556,11 @@ namespace Dalamud.Interface.Internal this.OpenStyleEditor(); } + if (ImGui.MenuItem("Open Profiler")) + { + this.OpenProfiler(); + } + ImGui.Separator(); if (ImGui.MenuItem("Unload Dalamud")) diff --git a/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs b/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs new file mode 100644 index 000000000..ddaa26756 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/ProfilerWindow.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Numerics; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Windowing; +using Dalamud.Utility.Numerics; +using Dalamud.Utility.Timing; +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows; + +public class ProfilerWindow : Window +{ + private double min; + private double max; + + public ProfilerWindow() : base("Profiler", forceMainWindow: true) { } + + public override void OnOpen() + { + this.min = Timings.AllTimings.Min(x => x.StartTime); + this.max = Timings.AllTimings.Max(x => x.EndTime); + } + + /// + public override void Draw() + { + var width = ImGui.GetWindowWidth(); + var actualMin = Timings.AllTimings.Min(x => x.StartTime); + var actualMax = Timings.AllTimings.Max(x => x.EndTime); + + ImGui.Text("Timings"); + + const int childHeight = 300; + + if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true)) + { + var pos = ImGui.GetCursorScreenPos(); + + for (var i = 0; i < width; i += 80) + { + ImGui.PushFont(InterfaceManager.MonoFont); + + var lineEnd = childHeight - 20; + + ImGui.GetWindowDrawList().AddLine( + pos + new Vector2(i, 0), + pos + new Vector2(i, lineEnd - 10), + ImGui.GetColorU32(ImGuiColors.ParsedGrey.WithW(0x40))); + + // Draw ms label for line + var ms = ((i / width) * (this.max - this.min)) + this.min; + var msStr = (ms / 1000).ToString("F2") + "s"; + var msSize = ImGui.CalcTextSize(msStr); + var labelPos = pos + new Vector2(i - (msSize.X / 2), (-msSize.Y / 2) + lineEnd); + + // nudge label to the side if it's the first, so we're not cut off + if (i == 0) + labelPos.X += msSize.X / 2; + + ImGui.GetWindowDrawList().AddText( + labelPos, + ImGui.GetColorU32(ImGuiColors.ParsedGrey.WithW(0x40)), + msStr); + + ImGui.PopFont(); + } + + uint maxRectDept = 0; + + foreach (var timingHandle in Timings.AllTimings) + { + var startX = (timingHandle.StartTime - this.min) / (this.max - this.min) * width; + var endX = (timingHandle.EndTime - this.min) / (this.max - this.min) * width; + + startX = Math.Max(startX, 0); + endX = Math.Max(endX, 0); + + var rectColor = timingHandle.IsMainThread ? ImGuiColors.ParsedBlue : ImGuiColors.ParsedPurple; + rectColor.X -= timingHandle.Depth * 0.12f; + rectColor.Y -= timingHandle.Depth * 0.12f; + rectColor.Z -= timingHandle.Depth * 0.12f; + + if (maxRectDept < timingHandle.Depth) + maxRectDept = timingHandle.Depth; + + if (startX == endX) + { + continue; + } + + var minPos = pos + new Vector2((uint)startX, 20 * timingHandle.Depth); + var maxPos = pos + new Vector2((uint)endX, 20 * (timingHandle.Depth + 1)); + + ImGui.GetWindowDrawList().AddRectFilled( + minPos, + maxPos, + ImGui.GetColorU32(rectColor)); + + ImGui.GetWindowDrawList().AddTextClippedEx(minPos, maxPos, timingHandle.Name, null, Vector2.Zero, null); + + // Show tooltip when hovered + var mousePos = ImGui.GetMousePos(); + if (mousePos.X > pos.X + startX && mousePos.X < pos.X + endX && + mousePos.Y > pos.Y + (20 * timingHandle.Depth) && + mousePos.Y < pos.Y + (20 * (timingHandle.Depth + 1))) + { + ImGui.BeginTooltip(); + ImGui.Text(timingHandle.Name); + ImGui.Text(timingHandle.MemberName); + ImGui.Text($"{timingHandle.FileName}:{timingHandle.LineNumber}"); + ImGui.Text($"Duration: {timingHandle.Duration}ms"); + ImGui.EndTooltip(); + } + } + + uint eventTextDepth = maxRectDept + 2; + + foreach (var timingEvent in Timings.Events) + { + var startX = (timingEvent.StartTime - this.min) / (this.max - this.min) * width; + + if (startX < 0 || startX > width) + { + continue; + } + + ImGui.GetWindowDrawList().AddLine( + pos + new Vector2((uint)startX, 0), + pos + new Vector2((uint)startX, childHeight), + ImGui.GetColorU32(ImGuiColors.ParsedOrange), + 1.5f); + + const uint padding = 5; + + var textSize = ImGui.CalcTextSize(timingEvent.Name); + var textPos = pos + new Vector2((uint)startX + padding, eventTextDepth * 20); + + if (textPos.X + textSize.X > pos.X + width - 20) + { + textPos.X = pos.X + (uint)startX - textSize.X - padding; + } + + ImGui.GetWindowDrawList().AddText( + textPos, + ImGui.GetColorU32(ImGuiColors.DalamudWhite), + timingEvent.Name); + } + } + + ImGui.EndChild(); + + var sliderMin = (float)this.min / 1000f; + if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.1fs")) + { + this.min = sliderMin * 1000f; + } + + var sliderMax = (float)this.max / 1000f; + if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.1fs")) + { + this.max = sliderMax * 1000f; + } + + var sizeShown = (float)(this.max - this.min); + var sizeActual = (float)(actualMax - actualMin); + if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.1fs")) + { + this.max = this.min + sizeShown; + } + + ImGui.Text("Min: " + actualMin.ToString("0.000")); + ImGui.Text("Max: " + actualMax.ToString("0.000")); + ImGui.Text("Timings: " + Timings.AllTimings.Count); + } +}