diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d9a60769..9dee4195 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -42,6 +42,9 @@ Other Changes: - SliderScalar: Improved assert when using U32 or U64 types with a large v_max value. (#2765) [@loicmouton] - ImDrawList: clarified the name of many parameters so reading the code is a little easier. (#2740) - Using offsetof() when available in C++11. Avoids Clang sanitizer complaining about old-style macros. (#94) +- Added a mechanism to compact/free the larger allocations of unused windows (buffers are compacted when + a window is unused for 60 seconds, as per io.ConfigWindowsMemoryCompactTimer = 60.0f). Note that memory + usage has never been reported as a problem, so this is merely a touch of overzealous luxury. (#2636) - Backends: DX11: Fixed GSGetShader() call not passing an initialized instance count, would generally make the debug layer complain (Added in 1.72). - Backends: Vulkan: Added support for specifying multisample count. diff --git a/imgui.cpp b/imgui.cpp index fbe7a29e..6a8cc176 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1244,6 +1244,7 @@ ImGuiIO::ImGuiIO() ConfigInputTextCursorBlink = true; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; + ConfigWindowsMemoryCompactTimer = 60.0f; // Platform Functions BackendPlatformName = BackendRendererName = NULL; @@ -2699,6 +2700,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; + LastTimeActive = -1.0f; ItemWidthDefault = 0.0f; FontWindowScale = 1.0f; SettingsIdx = -1; @@ -2713,6 +2715,9 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) NavLastIds[0] = NavLastIds[1] = 0; NavRectRel[0] = NavRectRel[1] = ImRect(); NavLastChildNavWindow = NULL; + + MemoryCompacted = false; + MemoryDrawListIdxCapacity = MemoryDrawListVtxCapacity = 0; } ImGuiWindow::~ImGuiWindow() @@ -2783,6 +2788,36 @@ static void SetCurrentWindow(ImGuiWindow* window) g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); } +// Free up/compact internal window buffers, we can use this when a window becomes unused. +// This is currently unused by the library, but you may call this yourself for easy GC. +// Not freed: +// - ImGuiWindow, ImGuiWindowSettings, Name +// - StateStorage, ColumnsStorage (may hold useful data) +// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. +void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) +{ + window->MemoryCompacted = true; + window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity; + window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity; + window->IDStack.clear(); + window->DrawList->ClearFreeMemory(); + window->DC.ChildWindows.clear(); + window->DC.ItemFlagsStack.clear(); + window->DC.ItemWidthStack.clear(); + window->DC.TextWrapPosStack.clear(); + window->DC.GroupStack.clear(); +} + +void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) +{ + // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening. + // The other buffers tends to amortize much faster. + window->MemoryCompacted = false; + window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity); + window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity); + window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0; +} + void ImGui::SetNavID(ImGuiID id, int nav_layer) { ImGuiContext& g = *GImGui; @@ -3814,8 +3849,9 @@ void ImGui::NewFrame() g.NavIdTabCounter = INT_MAX; - // Mark all windows as not visible + // Mark all windows as not visible and compact unused memory. IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size); + const float memory_compact_start_time = (g.IO.ConfigWindowsMemoryCompactTimer > 0.0f) ? (float)g.Time - g.IO.ConfigWindowsMemoryCompactTimer : FLT_MAX; for (int i = 0; i != g.Windows.Size; i++) { ImGuiWindow* window = g.Windows[i]; @@ -3823,6 +3859,10 @@ void ImGui::NewFrame() window->BeginCount = 0; window->Active = false; window->WriteAccessed = false; + + // Garbage collect (this is totally functional but we may need decide if the side-effects are desirable) + if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) + GcCompactTransientWindowBuffers(window); } // Closing the focused window restore focus to the first active root window in descending z-order @@ -5391,6 +5431,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { window->Flags = (ImGuiWindowFlags)flags; window->LastFrameActive = current_frame; + window->LastTimeActive = (float)g.Time; window->BeginOrderWithinParent = 0; window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++); } @@ -5404,6 +5445,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + // We allow window memory to be compacted so recreate the base stack when needed. + if (window->IDStack.Size == 0) + window->IDStack.push_back(window->ID); + // Add to stack // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindowStack.push_back(window); @@ -5468,6 +5513,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); window->IDStack.resize(1); + // Restore buffer capacity when woken from a compacted state, to avoid + if (window->MemoryCompacted) + GcAwakeTransientWindowBuffers(window); + // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. bool window_title_visible_elsewhere = false; diff --git a/imgui.h b/imgui.h index 6b0de93a..28c7d20d 100644 --- a/imgui.h +++ b/imgui.h @@ -1360,6 +1360,7 @@ struct ImGuiIO bool ConfigInputTextCursorBlink; // = true // Set to false to disable blinking cursor, for users who consider it distracting. (was called: io.OptCursorBlink prior to 1.63) bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // [BETA] Set to true to only allow moving windows when clicked+dragged from the title bar. Windows without a title bar are not affected. + float ConfigWindowsMemoryCompactTimer;// = 60.0f // [BETA] Compact window memory usage when unused. Set to 0.0f to disable. //------------------------------------------------------------------ // Platform Functions diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 04214437..568da3b9 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3049,6 +3049,7 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.ConfigInputTextCursorBlink) ImGui::Text("io.ConfigInputTextCursorBlink"); if (io.ConfigWindowsResizeFromEdges) ImGui::Text("io.ConfigWindowsResizeFromEdges"); if (io.ConfigWindowsMoveFromTitleBarOnly) ImGui::Text("io.ConfigWindowsMoveFromTitleBarOnly"); + if (io.ConfigWindowsMemoryCompactTimer > 0.0f) ImGui::Text("io.ConfigWindowsMemoryCompactTimer = %.1ff", io.ConfigWindowsMemoryCompactTimer); ImGui::Text("io.BackendFlags: 0x%08X", io.BackendFlags); if (io.BackendFlags & ImGuiBackendFlags_HasGamepad) ImGui::Text(" HasGamepad"); if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); diff --git a/imgui_internal.h b/imgui_internal.h index a936f69e..1018b1e9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1321,8 +1321,8 @@ struct IMGUI_API ImGuiWindow ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack. (In theory this should be in the TempData structure) ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. - ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack // The best way to understand what those rectangles are is to use the 'Metrics -> Tools -> Show windows rectangles' viewer. // The main 'OuterRect', omitted as a field, is window->Rect(). @@ -1334,6 +1334,7 @@ struct IMGUI_API ImGuiWindow ImRect ContentsRegionRect; // FIXME: This is currently confusing/misleading. It is essentially WorkRect but not handling of scrolling. We currently rely on it as right/bottom aligned sizing operation need some size to rely on. int LastFrameActive; // Last frame number the window was Active. + float LastTimeActive; float ItemWidthDefault; ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items ImGuiStorage StateStorage; @@ -1352,6 +1353,10 @@ struct IMGUI_API ImGuiWindow ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space + bool MemoryCompacted; + int MemoryDrawListIdxCapacity; + int MemoryDrawListVtxCapacity; + public: ImGuiWindow(ImGuiContext* context, const char* name); ~ImGuiWindow(); @@ -1483,6 +1488,8 @@ namespace ImGui IMGUI_API void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond = 0); IMGUI_API void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0); IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); + IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); + IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; }