How to Optimize ImGui Performance for Large Lists Using ImGuiListClipper
ImGuiListClipper is a helper class that reduces CPU overhead by computing only the visible range of items in long lists, letting you render thousands of rows without per-frame iteration costs.
Dear ImGui (ocornut/imgui) provides the ImGuiListClipper utility to solve performance bottlenecks when displaying massive datasets. Instead of submitting every list item to the rendering pipeline each frame, this class calculates the visible viewport and skips invisible elements entirely. This optimization is essential for log viewers, file browsers, and large tables that would otherwise cause frame drops.
How ImGuiListClipper Works Internally
Range Calculation via Begin()
In imgui.h (lines 2876-2892), the ImGuiListClipper class defines the Begin() method that initiates the clipping calculation. When you call clipper.Begin(items_count, item_height), the clipper queries the current window’s clipping rectangle and calculates the indices for DisplayStart and DisplayEnd—the first and last items that can possibly be visible within the scroll region.
Step-Wise Iteration
The clipper exposes a while (clipper.Step()) loop that advances an internal step counter, as implemented in imgui.cpp (lines 1295-1319). Each call to Step() updates the display range for the current batch, enabling the clipper to handle variable-height items or tables using ImGuiListClipperFlags_NoSetTableRowCounters.
Automatic Cursor Positioning
After drawing the visible subset, the clipper internally advances the window cursor using ImGui::Dummy() to account for skipped items. This ensures scrollbars report the correct total height, as seen in imgui.cpp (lines 1320-1332) within the cleanup logic.
Table Integration
When used inside BeginTable/EndTable blocks, the clipper respects the ImGuiListClipperFlags_NoSetTableRowCounters flag defined in imgui.h (lines 2852-2854). This prevents corruption of table row indices during custom row handling, making it safe for complex table layouts.
When to Use ImGuiListClipper
Use the clipper when you need to optimize ImGui performance for large lists in these scenarios:
- Lists with thousands of entries (e.g., log windows, file browsers) to avoid iterating over every element each frame
- Tables with many rows to reduce the number of
TableNextRowcalls and draw commands for off-screen rows - Dynamic data where the total count is known so you can pass the exact
items_counttoBegin()and let the clipper handle unknown heights efficiently
Implementation Best Practices
- Provide a stable item height: Pass an explicit
item_heighttoBegin()if all rows share the same height. This lets the clipper compute ranges without scanning the first item. - Handle variable heights carefully: Set
ImGuiListClipperFlags_NoSetTableRowCountersand manually handle per-item offsets when mixing clipping with variable-height items. - Maintain consistent spacing: Combine with
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ...)to keep item heights predictable when changing spacing.
Code Examples
Basic List with Selectable Items
void ShowLargeList(const std::vector<std::string>& items)
{
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(items.size()));
while (clipper.Step())
{
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
{
ImGui::Selectable(items[i].c_str());
}
}
// No need to call clipper.End(); destructor does it automatically
}
High-Performance Table with Fixed Row Height
void ShowHugeTable()
{
const int rows = 50000;
ImGui::BeginTable("BigTable", 3, ImGuiTableFlags_Borders);
ImGuiListClipper clipper;
clipper.Begin(rows, 20.0f); // explicit item height
while (clipper.Step())
{
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::Text("Row %d – Col 0", row);
ImGui::TableSetColumnIndex(1); ImGui::Text("Col 1");
ImGui::TableSetColumnIndex(2); ImGui::Text("Col 2");
}
}
ImGui::EndTable();
}
Variable Height Tables with Custom Flags
void ShowVariableHeightTable()
{
const int rows = 2000;
ImGui::BeginTable("VarHeight", 2);
ImGuiListClipper clipper;
clipper.Begin(rows, -1.0f); // let ImGui infer height from first row
clipper.Flags |= ImGuiListClipperFlags_NoSetTableRowCounters;
while (clipper.Step())
{
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
ImGui::TableNextRow( (row % 10) ? 0.0f : 30.0f ); // every 10th row taller
ImGui::TableSetColumnIndex(0); ImGui::Text("Row %d", row);
ImGui::TableSetColumnIndex(1); ImGui::Text("Data");
}
}
ImGui::EndTable();
}
Summary
- ImGuiListClipper eliminates CPU bottlenecks by calculating visible ranges via
Begin()and iterating only over visible items usingStep(). - The clipper automatically handles scrollbar positioning and cursor advancement for skipped items.
- Pass explicit
item_heightvalues when possible to avoid inference overhead, and useImGuiListClipperFlags_NoSetTableRowCountersfor variable-height tables. - Reference implementations are available in
imgui_demo.cpp(lines 1870-1900) and internal data structures inimgui_internal.h(lines 1688-1710).
Frequently Asked Questions
Do I need to call End() on ImGuiListClipper?
No. The ImGuiListClipper destructor automatically handles cleanup, as implemented in imgui.cpp. The End() method is called internally when the clipper goes out of scope or when Step() completes the final iteration.
Can ImGuiListClipper handle items with different heights?
Yes, but with limitations. If items have variable heights, omit the item_height parameter (or pass -1.0f) to let the clipper infer height from the first item. For tables with variable row heights, you must set ImGuiListClipperFlags_NoSetTableRowCounters to prevent the clipper from corrupting table row counters.
Why is my scrollbar size incorrect when using the clipper?
This typically occurs when the total item count passed to Begin() does not match your actual data size, or when item heights vary without proper flag configuration. The clipper uses ImGui::Dummy() in imgui.cpp (lines 1320-1332) to advance the cursor for skipped items, ensuring the scrollbar reflects the total virtual height.
How do I integrate ImGuiListClipper with ImGui::BeginTable?
Place the clipper inside your BeginTable/EndTable block. Call clipper.Begin(rows, row_height) after BeginTable(), then iterate TableNextRow() only for indices between clipper.DisplayStart and clipper.DisplayEnd. For tables with custom row handling, set clipper.Flags |= ImGuiListClipperFlags_NoSetTableRowCounters as defined in imgui.h (lines 2852-2854).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →