How to Use ImGuiListClipper for Efficient Virtual Scrolling Lists
Use ImGuiListClipper to render massive lists by calculating only the visible index range and iterating through DisplayStart to DisplayEnd, reducing ImGui calls from millions to dozens per frame.
Dear ImGui’s ImGuiListClipper is a helper class designed for high-performance rendering of large datasets in the ocornut/imgui library. It implements virtual scrolling by clipping items outside the current viewport, allowing you to display thousands or millions of entries without performance degradation. This article explains the exact implementation pattern used in the official source code.
Why Use ImGuiListClipper?
Traditional list rendering calls ImGui functions for every item, creating CPU bottlenecks when handling large datasets. The clipper solves this through three core mechanisms:
- Rendering efficiency – Only processes items intersecting the current clipping rectangle, typically reducing draw calls from millions to a few dozen per frame.
- Memory agility – Keeps your data in any container (vector, array, or database) while accessing only the visible subset during the render loop.
- Seamless integration – Works with standard ImGui widgets like
TextandSelectable, automatically managing cursor position restoration between steps.
How ImGuiListClipper Works
The clipper uses an iterator-style interface that calculates visible ranges based on the current scroll position and window dimensions. The implementation spans imgui.cpp (lines 3327-3610) and relies on internal structures defined in imgui_internal.h (lines 1689-1704).
Step 1: Initialize the Clipper
Create an instance of ImGuiListClipper on the stack. The destructor automatically calls End() to clean up temporary buffers and restore cursor state, as implemented in imgui.cpp lines 3327-3335.
ImGuiListClipper clipper;
Step 2: Begin the Clipping Session
Call Begin() to initialize the clipping calculation. In imgui.cpp (lines 3377-3390), this method sets up the clipper data and calculates initial positioning.
clipper.Begin(item_count, item_height);
item_count– Total number of items in your dataset. PassINT_MAXif the count is unknown upfront.item_height– Height of each row in pixels. Use-1.0fto let the clipper infer height from the first visible row.
Step 3: Iterate with Step()
The Step() method (implemented in imgui.cpp lines 3573-3610) updates DisplayStart and DisplayEnd indices for the current frame. It returns true while items remain to process.
while (clipper.Step())
{
// Render loop here
}
This method handles height inference, range calculation, and clipping rectangle intersection tests automatically.
Step 4: Render Visible Items
Inside the Step() loop, render only items between clipper.DisplayStart and clipper.DisplayEnd:
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
ImGui::Text("%04d: %s", i, items[i].c_str());
}
Complete Usage Example
This pattern from imgui_demo.cpp demonstrates the standard implementation for fixed-height lists:
// items is a std::vector<std::string> or similar container
ImGuiListClipper clipper;
clipper.Begin(items.size()); // item_height = -1.0f (auto-detect)
while (clipper.Step())
{
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
ImGui::Text("%04d: %s", i, items[i].c_str());
}
}
// clipper.End() called automatically by destructor
Advanced Techniques
Handling Variable-Height Rows
For lists where row heights vary, pass a known average height to Begin() or use IncludeItemsByIndex() to specify exact ranges. The IncludeItemsByIndex() method (defined in the imgui.cpp implementation block) allows you to force specific indices into the visible set when height prediction is inaccurate.
Using with ImGui Tables
When integrating with tables, add the ImGuiListClipperFlags_NoSetTableRowCounters flag to prevent conflicts between the clipper's row tracking and table row advancement. This flag is processed in the clipper's step logic (referenced around imgui.cpp lines 3178-3182).
clipper.Begin(row_count, row_height, ImGuiListClipperFlags_NoSetTableRowCounters);
Indeterminate Item Counts
If your data source is streaming or paginated, initialize with INT_MAX and later call SeekCursorForItem() after the loop to position the cursor for subsequent UI elements:
clipper.Begin(INT_MAX);
while (clipper.Step())
{
// Render known items
}
clipper.SeekCursorForItem(known_item_count); // Position for footer
Implementation Details
The ImGuiListClipper class is declared in imgui.h (lines 196-210) and relies on ImGuiListClipperData and ImGuiListClipperRange structures defined in imgui_internal.h (lines 1689-1704). The Step() method performs binary searches to locate visible ranges efficiently, while Begin() handles initialization of temporary draw data stored in the window's clipper stack.
Key methods in imgui.cpp:
Begin()– Lines 3377-3390: Validates inputs, stores data in window storage, calculates initial offsets.Step()– Lines 3573-3610: Main logic for range calculation, height measurement, and iteration state management.End()– Lines 3327-3335: Cleanup and cursor restoration (called by destructor).
Summary
- ImGuiListClipper virtualizes list rendering by calculating only visible index ranges via
Step()iterations. - Initialize with
Begin(count, height), iterate whileStep()returns true, and render items betweenDisplayStartandDisplayEnd. - Pass
-1.0fforitem_heightto enable automatic height detection from the first visible row. - Use
ImGuiListClipperFlags_NoSetTableRowCounterswhen clipping inside table contexts to avoid row counter conflicts. - The destructor automatically calls
End(), though manual cleanup is available if needed.
Frequently Asked Questions
What is the performance benefit of using ImGuiListClipper?
The clipper reduces ImGui drawing calls from O(n) to O(visible items), typically processing only 20-50 items regardless of whether your dataset contains 1,000 or 1,000,000 entries. According to the ocornut/imgui source code, this prevents CPU bottlenecks when calling functions like Text or Selectable by skipping invisible items entirely.
How do I handle items with different heights in a clipped list?
Provide an average height to Begin() that approximates your variable row sizes. For precise control, use IncludeItemsByIndex(begin, end) to manually specify ranges that must be evaluated, or measure heights during the first Step() iteration and store them for subsequent frames. The clipper's height inference system (lines 3573-3610 in imgui.cpp) handles minor variations automatically.
Can I use ImGuiListClipper with tables?
Yes, but you must pass the ImGuiListClipperFlags_NoSetTableRowCounters flag to Begin(). Without this flag, the clipper conflicts with ImGui's internal table row advancement logic. The flag tells the clipper to skip row counter management, allowing the table to control row state while the clipper handles vertical positioning.
Do I need to manually call End() on the clipper?
No. The destructor automatically invokes End() (implemented in imgui.cpp lines 3327-3335), which restores the cursor position and cleans up temporary data stored in the window's internal clipper stack. Manual calls to End() are only necessary if you need to terminate the clipping session early before the object goes out of scope.
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 →