diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs
new file mode 100644
index 000000000..5c76854d2
--- /dev/null
+++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs
@@ -0,0 +1,22 @@
+namespace Dalamud.Interface.Internal.ManagedAsserts
+{
+ ///
+ /// Offsets to various data in ImGui context.
+ ///
+ ///
+ /// Last updated for ImGui 1.83.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")]
+ internal static class ImGuiContextOffsets
+ {
+ public const int CurrentWindowStackOffset = 0x73A;
+
+ public const int ColorStackOffset = 0x79C;
+
+ public const int StyleVarStackOffset = 0x7A0;
+
+ public const int FontStackOffset = 0x7A4;
+
+ public const int BeginPopupStackOffset = 0x7B8;
+ }
+}
diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs
new file mode 100644
index 000000000..b0df91162
--- /dev/null
+++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs
@@ -0,0 +1,136 @@
+using System.Diagnostics;
+
+using ImGuiNET;
+
+using static Dalamud.NativeFunctions;
+
+namespace Dalamud.Interface.Internal.ManagedAsserts
+{
+ ///
+ /// Report ImGui problems with a MessageBox dialog.
+ ///
+ internal static class ImGuiManagedAsserts
+ {
+ ///
+ /// Gets a value indicating whether asserts are enabled for ImGui.
+ ///
+ public static bool AssertsEnabled { get; private set; }
+
+ ///
+ /// Create a snapshot of the current ImGui context.
+ /// Should be called before rendering an ImGui frame.
+ ///
+ /// A snapshot of the current context.
+ public static unsafe ImGuiContextSnapshot GetSnapshot()
+ {
+ var contextPtr = ImGui.GetCurrentContext();
+
+ var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size
+ var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size
+ var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size
+ var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size
+ var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size
+
+ return new ImGuiContextSnapshot
+ {
+ StyleVarStackSize = styleVarStack,
+ ColorStackSize = colorStack,
+ FontStackSize = fontStack,
+ BeginPopupStackSize = popupStack,
+ WindowStackSize = windowStack,
+ };
+ }
+
+ ///
+ /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog.
+ ///
+ /// The source of any problems, something to blame.
+ /// ImGui context snapshot.
+ public static void ReportProblems(string source, ImGuiContextSnapshot before)
+ {
+ if (!AssertsEnabled)
+ {
+ return;
+ }
+
+ var cSnap = GetSnapshot();
+
+ if (before.StyleVarStackSize != cSnap.StyleVarStackSize)
+ {
+ ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}");
+ return;
+ }
+
+ if (before.ColorStackSize != cSnap.ColorStackSize)
+ {
+ ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}");
+ return;
+ }
+
+ if (before.FontStackSize != cSnap.FontStackSize)
+ {
+ ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}");
+ return;
+ }
+
+ if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize)
+ {
+ ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}");
+ return;
+ }
+
+ if (cSnap.WindowStackSize != 1)
+ {
+ if (cSnap.WindowStackSize > 1)
+ {
+ ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
+ }
+ else
+ {
+ ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
+ }
+ }
+ }
+
+ private static void ShowAssert(string source, string message)
+ {
+ var caption = $"You fucked up";
+ message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them.";
+ var flags = MessageBoxType.Ok | MessageBoxType.IconError;
+
+ _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags);
+ AssertsEnabled = false;
+ }
+
+ ///
+ /// A snapshot of various ImGui context properties.
+ ///
+ public class ImGuiContextSnapshot
+ {
+ ///
+ /// Gets the ImGui style var stack size.
+ ///
+ public int StyleVarStackSize { get; init; }
+
+ ///
+ /// Gets the ImGui color stack size.
+ ///
+ public int ColorStackSize { get; init; }
+
+ ///
+ /// Gets the ImGui font stack size.
+ ///
+ public int FontStackSize { get; init; }
+
+ ///
+ /// Gets the ImGui begin popup stack size.
+ ///
+ public int BeginPopupStackSize { get; init; }
+
+ ///
+ /// Gets the ImGui window stack size.
+ ///
+ public int WindowStackSize { get; init; }
+ }
+ }
+}
diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs
index 3e3694407..5ff2fac4c 100644
--- a/Dalamud/Interface/UiBuilder.cs
+++ b/Dalamud/Interface/UiBuilder.cs
@@ -6,6 +6,7 @@ using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Interface.Internal;
+using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications;
using ImGuiNET;
using ImGuiScene;
@@ -275,6 +276,12 @@ namespace Dalamud.Interface
ImGui.End();
}
+ ImGuiManagedAsserts.ImGuiContextSnapshot? snapshot = null;
+ if (this.Draw != null)
+ {
+ snapshot = ImGuiManagedAsserts.GetSnapshot();
+ }
+
try
{
this.Draw?.Invoke();
@@ -288,6 +295,12 @@ namespace Dalamud.Interface
this.hasErrorWindow = true;
}
+ // Only if Draw was successful
+ if (this.Draw != null && snapshot != null)
+ {
+ ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot);
+ }
+
this.FrameCount++;
if (DoStats)
diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs
index 4d3a03a0d..5f6334e8e 100644
--- a/Dalamud/Interface/Windowing/WindowSystem.cs
+++ b/Dalamud/Interface/Windowing/WindowSystem.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
+using Dalamud.Interface.Internal.ManagedAsserts;
using ImGuiNET;
using Serilog;
@@ -98,8 +99,12 @@ namespace Dalamud.Interface.Windowing
#if DEBUG
// Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}");
#endif
+ var snapshot = ImGuiManagedAsserts.GetSnapshot();
window.DrawInternal();
+
+ var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName;
+ ImGuiManagedAsserts.ReportProblems(source, snapshot);
}
var focusedWindow = this.windows.FirstOrDefault(x => x.IsFocused && x.RespectCloseHotkey);