Tables: sharing transient buffers between tables, reducing memory footprints. (#3740)

This commit is contained in:
ocornut 2021-05-07 18:00:12 +02:00
parent cbcd89152b
commit 32c453ae53
3 changed files with 72 additions and 43 deletions

View File

@ -4204,7 +4204,9 @@ void ImGui::Shutdown(ImGuiContext* context)
g.ShrinkWidthBuffer.clear();
g.Tables.Clear();
g.CurrentTableStack.clear();
for (int i = 0; i < g.TablesTempDataStack.Size; i++)
g.TablesTempDataStack[i].~ImGuiTableTempData();
g.TablesTempDataStack.clear();
g.DrawChannelsTempMergeBuffer.clear();
g.ClipboardHandlerData.clear();

View File

@ -130,10 +130,11 @@ struct ImGuiTabBar; // Storage for a tab bar
struct ImGuiTabItem; // Storage for a tab item (within a tab bar)
struct ImGuiTable; // Storage for a table
struct ImGuiTableColumn; // Storage for one column of a table
struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables.
struct ImGuiTableSettings; // Storage for a table .ini settings
struct ImGuiTableColumnsSettings; // Storage for a column .ini settings
struct ImGuiWindow; // Storage for one window
struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame)
struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window)
struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session)
// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists.
@ -1516,8 +1517,9 @@ struct ImGuiContext
// Table
ImGuiTable* CurrentTable;
int CurrentTableStackIdx;
ImPool<ImGuiTable> Tables;
ImVector<ImGuiPtrOrIndex> CurrentTableStack;
ImVector<ImGuiTableTempData> TablesTempDataStack;
ImVector<float> TablesLastTimeActive; // Last used timestamp of each tables (SOA, for efficient GC)
ImVector<ImDrawChannel> DrawChannelsTempMergeBuffer;
@ -1694,6 +1696,7 @@ struct ImGuiContext
memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal));
CurrentTable = NULL;
CurrentTableStackIdx = -1;
CurrentTabBar = NULL;
LastValidMousePos = ImVec2(0.0f, 0.0f);
@ -2089,12 +2092,13 @@ struct ImGuiTableCellData
ImGuiTableColumnIdx Column; // Column number
};
// FIXME-TABLE: transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData
// FIXME-TABLE: more transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData
struct ImGuiTable
{
ImGuiID ID;
ImGuiTableFlags Flags;
void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[]
ImGuiTableTempData* TempData; // Transient data while table is active. Point within g.CurrentTableStack[]
ImSpan<ImGuiTableColumn> Columns; // Point within RawData[]
ImSpan<ImGuiTableColumnIdx> DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)
ImSpan<ImGuiTableCellData> RowCellData; // Point within RawData[]. Store cells background requests for current row.
@ -2146,22 +2150,13 @@ struct ImGuiTable
ImRect Bg0ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in BG0/BG1 is cpu-clipped
ImRect Bg2ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by widgets relying on regular ClipRect.
ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window.
ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable()
ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable()
ImRect HostBackupInnerClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground()
ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable()
ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable()
ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable()
ImVec2 UserOuterSize; // outer_size.x passed to BeginTable()
ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable()
float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()
int HostBackupItemWidthStackSize;// Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable()
ImGuiWindow* OuterWindow; // Parent window for the table
ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window)
ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names
ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (FIXME: could be stored outside, worst case we need 1 splitter per recursing table)
ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (should move to ImGuiTableTempDataB)
ImGuiTableColumnSortSpecs SortSpecsSingle;
ImVector<ImGuiTableColumnSortSpecs> SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would work be good.
ImVector<ImGuiTableColumnSortSpecs> SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good.
ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs()
ImGuiTableColumnIdx SortSpecsCount;
ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount)
@ -2208,6 +2203,26 @@ struct ImGuiTable
IMGUI_API ~ImGuiTable() { IM_FREE(RawData); }
};
// Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared.
// Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure.
// FIXME-TABLE: more transient data could be stored here: DrawSplitter (!), SortSpecs? incoming RowData?
struct ImGuiTableTempData
{
int TableIndex; // Index in g.Tables.Buf[] pool
ImVec2 UserOuterSize; // outer_size.x passed to BeginTable()
ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable()
ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable()
ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable()
ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable()
ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable()
ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable()
float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()
int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable()
IMGUI_API ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; }
};
// sizeof() ~ 12
struct ImGuiTableColumnSettings
{

View File

@ -343,6 +343,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
if (instance_no > 0)
IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
// Acquire temporary buffers
const int table_idx = g.Tables.GetIndex(table);
g.CurrentTableStackIdx++;
if (g.CurrentTableStackIdx + 1 > g.TablesTempDataStack.Size)
g.TablesTempDataStack.resize(g.CurrentTableStackIdx + 1, ImGuiTableTempData());
ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempDataStack[g.CurrentTableStackIdx];
temp_data->TableIndex = table_idx;
// Fix flags
table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0;
flags = TableFixFlags(flags, outer_window);
@ -356,7 +364,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->ColumnsCount = columns_count;
table->IsLayoutLocked = false;
table->InnerWidth = inner_width;
table->UserOuterSize = outer_size;
temp_data->UserOuterSize = outer_size;
// When not using a child window, WorkRect.Max will grow as we append contents.
if (use_child_window)
@ -405,14 +413,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->HostIndentX = inner_window->DC.Indent.x;
table->HostClipRect = inner_window->ClipRect;
table->HostSkipItems = inner_window->SkipItems;
table->HostBackupWorkRect = inner_window->WorkRect;
table->HostBackupParentWorkRect = inner_window->ParentWorkRect;
table->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;
table->HostBackupPrevLineSize = inner_window->DC.PrevLineSize;
table->HostBackupCurrLineSize = inner_window->DC.CurrLineSize;
table->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;
table->HostBackupItemWidth = outer_window->DC.ItemWidth;
table->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;
temp_data->HostBackupWorkRect = inner_window->WorkRect;
temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect;
temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;
temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize;
temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize;
temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;
temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth;
temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;
inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
// Padding and Spacing
@ -455,8 +463,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight);
// Make table current
const int table_idx = g.Tables.GetIndex(table);
g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx));
g.CurrentTable = table;
outer_window->DC.CurrentTableIdx = table_idx;
if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly.
@ -1205,6 +1211,7 @@ void ImGui::EndTable()
const ImGuiTableFlags flags = table->Flags;
ImGuiWindow* inner_window = table->InnerWindow;
ImGuiWindow* outer_window = table->OuterWindow;
ImGuiTableTempData* temp_data = table->TempData;
IM_ASSERT(inner_window == g.CurrentWindow);
IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow);
@ -1217,9 +1224,9 @@ void ImGui::EndTable()
TableOpenContextMenu((int)table->HoveredColumnBody);
// Finalize table height
inner_window->DC.PrevLineSize = table->HostBackupPrevLineSize;
inner_window->DC.CurrLineSize = table->HostBackupCurrLineSize;
inner_window->DC.CursorMaxPos = table->HostBackupCursorMaxPos;
inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize;
inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize;
inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos;
const float inner_content_max_y = table->RowPosY2;
IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y);
if (inner_window != outer_window)
@ -1266,10 +1273,11 @@ void ImGui::EndTable()
#endif
// Flatten channels and merge draw calls
table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0);
ImDrawListSplitter* splitter = &table->DrawSplitter;
splitter->SetCurrentChannel(inner_window->DrawList, 0);
if ((table->Flags & ImGuiTableFlags_NoClip) == 0)
TableMergeDrawChannels(table);
table->DrawSplitter.Merge(inner_window->DrawList);
splitter->Merge(inner_window->DrawList);
// Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable()
const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
@ -1311,18 +1319,18 @@ void ImGui::EndTable()
// Pop from id stack
IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!");
IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= table->HostBackupItemWidthStackSize, "Too many PopItemWidth!");
IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!");
PopID();
// Restore window data that we modified
const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos;
inner_window->WorkRect = table->HostBackupWorkRect;
inner_window->ParentWorkRect = table->HostBackupParentWorkRect;
inner_window->WorkRect = temp_data->HostBackupWorkRect;
inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect;
inner_window->SkipItems = table->HostSkipItems;
outer_window->DC.CursorPos = table->OuterRect.Min;
outer_window->DC.ItemWidth = table->HostBackupItemWidth;
outer_window->DC.ItemWidthStack.Size = table->HostBackupItemWidthStackSize;
outer_window->DC.ColumnsOffset = table->HostBackupColumnsOffset;
outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth;
outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize;
outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset;
// Layout in outer window
// (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding
@ -1345,20 +1353,20 @@ void ImGui::EndTable()
IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0);
outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth);
}
else if (table->UserOuterSize.x <= 0.0f)
else if (temp_data->UserOuterSize.x <= 0.0f)
{
const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f;
outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - table->UserOuterSize.x);
outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x);
outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth));
}
else
{
outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x);
}
if (table->UserOuterSize.y <= 0.0f)
if (temp_data->UserOuterSize.y <= 0.0f)
{
const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f;
outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - table->UserOuterSize.y);
outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y);
outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y));
}
else
@ -1374,8 +1382,12 @@ void ImGui::EndTable()
// Clear or restore current table, if any
IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table);
g.CurrentTableStack.pop_back();
g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL;
IM_ASSERT(g.CurrentTableStackIdx >= 0);
g.CurrentTableStackIdx--;
temp_data = g.CurrentTableStackIdx >= 0 ? &g.TablesTempDataStack[g.CurrentTableStackIdx] : NULL;
g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL;
if (g.CurrentTable)
g.CurrentTable->TempData = temp_data;
outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1;
}