perf(style): make GlobalStyleScope.StackHandle GC-free via counter scope

This commit is contained in:
2026-06-16 19:23:15 +02:00
parent 430c8f235a
commit 32013babaf
+89 -55
View File
@@ -1,5 +1,4 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Themes; using HellionChat.Themes;
using HellionChat.Util; using HellionChat.Util;
@@ -33,60 +32,73 @@ internal static class GlobalStyleScope
var childBgWithAlpha = ResolveChildBgAlpha(c.ChildBg, windowOpacity); var childBgWithAlpha = ResolveChildBgAlpha(c.ChildBg, windowOpacity);
var stack = new StackHandle(); var stack = new StackHandle();
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding); // Hard contract: every push must be balanced by a pop. The pushes below
stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding); // are pure, practically non-throwing ImGui calls, but if one ever threw
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding); // mid-stack we must still pop what we pushed before the exception escapes,
stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding); // or the global style stack stays corrupt for every other window this
stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding); // frame. Dispose then rethrow.
stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding); try
stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding); {
stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize); stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize); stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding);
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding);
stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding);
stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding);
stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding);
stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding);
stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize);
stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize);
stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha); stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha); stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg); stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Border, a.Border); stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u); stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg); stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover); stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface); stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg); stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity); stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg); stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.Button, a.Primary); stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight); stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark); stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface); stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover); stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity); stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg); stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight); stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity); stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg); stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark); stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg); stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface); stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight); stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent); stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg); stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight); stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent); stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary); stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary); stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight); stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.Separator, a.Border); stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight); stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary); stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
}
catch
{
stack.Dispose();
throw;
}
return stack; return stack;
} }
@@ -101,24 +113,46 @@ internal static class GlobalStyleScope
return (themeChildBgRgba & 0xFFFFFF00u) | childBgAlpha; return (themeChildBgRgba & 0xFFFFFF00u) | childBgAlpha;
} }
// Counter-based scope: pushes go straight onto the global ImGui style
// stack and we only remember how many of each kind we pushed. Dispose
// pops them in one batched PopStyleColor(count)/PopStyleVar(count) call.
// This replaces the old List<IDisposable> + per-push boxed ImRaii structs
// (44 allocations/frame) with two ints — zero per-frame GC. Every style
// var pushed here is a single-float var, so PopStyleVar(count) is valid;
// both colour paths funnel into one PushStyleColor, so one colour counter
// covers them. Symmetry is the whole contract: the pop counts must equal
// the push counts or the global stack corrupts for every other window.
private sealed class StackHandle : IDisposable private sealed class StackHandle : IDisposable
{ {
private readonly List<IDisposable> _items = new(64); private int _colorCount;
private int _styleVarCount;
internal void PushColor(ImGuiCol slot, uint rgba) => internal void PushColor(ImGuiCol slot, uint rgba)
_items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba))); {
ImGui.PushStyleColor(slot, ColourUtil.RgbaToAbgr(rgba));
_colorCount++;
}
internal void PushColorAbgr(ImGuiCol slot, uint abgr) => internal void PushColorAbgr(ImGuiCol slot, uint abgr)
_items.Add(ImRaii.PushColor(slot, abgr)); {
ImGui.PushStyleColor(slot, abgr);
_colorCount++;
}
internal void PushStyleVar(ImGuiStyleVar var, float value) => internal void PushStyleVar(ImGuiStyleVar var, float value)
_items.Add(ImRaii.PushStyle(var, value)); {
ImGui.PushStyleVar(var, value);
_styleVarCount++;
}
public void Dispose() public void Dispose()
{ {
for (var i = _items.Count - 1; i >= 0; i--) if (_styleVarCount > 0)
_items[i].Dispose(); ImGui.PopStyleVar(_styleVarCount);
_items.Clear(); if (_colorCount > 0)
ImGui.PopStyleColor(_colorCount);
_styleVarCount = 0;
_colorCount = 0;
} }
} }
} }