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.Interface.Utility.Raii;
using HellionChat.Themes;
using HellionChat.Util;
@@ -33,60 +32,73 @@ internal static class GlobalStyleScope
var childBgWithAlpha = ResolveChildBgAlpha(c.ChildBg, windowOpacity);
var stack = new StackHandle();
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
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);
// Hard contract: every push must be balanced by a pop. The pushes below
// are pure, practically non-throwing ImGui calls, but if one ever threw
// mid-stack we must still pop what we pushed before the exception escapes,
// or the global style stack stays corrupt for every other window this
// frame. Dispose then rethrow.
try
{
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
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.ChildBg, childBgWithAlpha);
stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u);
stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.Border, a.Border);
stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark);
stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight);
stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
}
catch
{
stack.Dispose();
throw;
}
return stack;
}
@@ -101,24 +113,46 @@ internal static class GlobalStyleScope
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 readonly List<IDisposable> _items = new(64);
private int _colorCount;
private int _styleVarCount;
internal void PushColor(ImGuiCol slot, uint rgba) =>
_items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
internal void PushColor(ImGuiCol slot, uint rgba)
{
ImGui.PushStyleColor(slot, ColourUtil.RgbaToAbgr(rgba));
_colorCount++;
}
internal void PushColorAbgr(ImGuiCol slot, uint abgr) =>
_items.Add(ImRaii.PushColor(slot, abgr));
internal void PushColorAbgr(ImGuiCol slot, uint abgr)
{
ImGui.PushStyleColor(slot, abgr);
_colorCount++;
}
internal void PushStyleVar(ImGuiStyleVar var, float value) =>
_items.Add(ImRaii.PushStyle(var, value));
internal void PushStyleVar(ImGuiStyleVar var, float value)
{
ImGui.PushStyleVar(var, value);
_styleVarCount++;
}
public void Dispose()
{
for (var i = _items.Count - 1; i >= 0; i--)
_items[i].Dispose();
_items.Clear();
if (_styleVarCount > 0)
ImGui.PopStyleVar(_styleVarCount);
if (_colorCount > 0)
ImGui.PopStyleColor(_colorCount);
_styleVarCount = 0;
_colorCount = 0;
}
}
}