Overview
Dear ImGui is designed to be fast and lightweight, but understanding its performance characteristics helps you build responsive UIs even with complex interfaces.
Most applications won’t need aggressive optimization. Start with these techniques only if you experience performance issues.
Dear ImGui’s immediate mode design means:
UI code runs every frame
Only visible elements generate draw commands
The library is optimized for common use cases
Simple UIs are extremely fast
Built-in Metrics
ImGui :: ShowMetricsWindow ();
The metrics window shows:
Active windows count
Active widgets count
Vertices and indices count
Draw calls
Frame time
Custom Measurements
ImGuiIO & io = ImGui :: GetIO ();
// Frame rate
float fps = io . Framerate ;
// Delta time
float dt = io . DeltaTime ;
// Render stats
ImDrawData * draw_data = ImGui :: GetDrawData ();
int total_vtx = draw_data -> TotalVtxCount ;
int total_idx = draw_data -> TotalIdxCount ;
int cmd_lists = draw_data -> CmdListsCount ;
ImGui :: Text ( "FPS: %.1f ( %.2f ms)" , fps, dt * 1000.0 f );
ImGui :: Text ( "Vertices: %d , Indices: %d " , total_vtx, total_idx);
ImGui :: Text ( "Draw Lists: %d " , cmd_lists);
CPU Optimization
Early Exit for Collapsed Windows
// Good: Skip expensive content when collapsed
if ( ImGui :: Begin ( "Complex Window" , & open)) {
// Only execute this if window is visible
RenderExpensiveContent ();
}
ImGui :: End ();
// The Begin() return value indicates if window is not collapsed
Use ListClipper for Large Lists
ListClipper only renders visible items:
ImGuiListClipper clipper;
clipper . Begin ( 10000 ); // 10,000 items
while ( clipper . Step ()) {
for ( int i = clipper . DisplayStart ; i < clipper . DisplayEnd ; i ++ ) {
ImGui :: Text ( "Item %d " , i);
// Only ~20-30 items actually rendered!
}
}
Benefits:
Constant-time performance regardless of list size
Smooth scrolling
Lower CPU and GPU usage
Advanced usage with custom items:
ImGuiListClipper clipper;
clipper . Begin ( items . size (), item_height);
while ( clipper . Step ()) {
for ( int i = clipper . DisplayStart ; i < clipper . DisplayEnd ; i ++ ) {
ImGui :: PushID (i);
bool selected = selection . contains (i);
if ( ImGui :: Selectable ( items [i]. name , & selected)) {
selection . toggle (i);
}
ImGui :: PopID ();
}
}
Minimize Function Calls
// Less efficient: Many small UI updates
for ( int i = 0 ; i < 1000 ; i ++ ) {
ImGui :: Text ( "Item %d " , i);
}
// Better: Use Text for simple cases, use ListClipper for large lists
ImGuiListClipper clipper;
clipper . Begin ( 1000 );
while ( clipper . Step ()) {
for ( int i = clipper . DisplayStart ; i < clipper . DisplayEnd ; i ++ ) {
ImGui :: Text ( "Item %d " , i);
}
}
Reduce String Operations
// Expensive: Format string every frame
for ( int i = 0 ; i < 100 ; i ++ ) {
ImGui :: Text ( "Item %d : %s " , i, GetItemName (i). c_str ());
}
// Better: Cache formatted strings when data changes
struct CachedItem {
std ::string name;
char label [ 128 ];
bool dirty = true ;
void Update () {
if (dirty) {
snprintf (label, sizeof (label), "Item %d : %s " , id, name . c_str ());
dirty = false ;
}
}
};
// In render loop
for ( auto & item : items) {
item . Update ();
ImGui :: Text ( " %s " , item . label );
}
Conditional UI Updates
// Good: Only update complex UI when needed
static bool needs_rebuild = true ;
static std ::vector < Node *> visible_nodes;
if (needs_rebuild) {
visible_nodes . clear ();
CollectVisibleNodes (root, visible_nodes);
needs_rebuild = false ;
}
// Render cached visible nodes
for (Node * node : visible_nodes) {
RenderNode (node);
}
// Mark for rebuild when data changes
if (data_changed) {
needs_rebuild = true ;
}
GPU Optimization
Minimize State Changes
Each ImDrawCmd can potentially cause a state change:
// Good: Batch similar items
ImGui :: PushStyleColor (ImGuiCol_Button, red);
for ( int i = 0 ; i < 10 ; i ++ ) {
ImGui :: Button ( "Red Button" );
}
ImGui :: PopStyleColor ();
// Potentially fewer draw calls
// Less efficient: Change state every iteration
for ( int i = 0 ; i < 10 ; i ++ ) {
ImGui :: PushStyleColor (ImGuiCol_Button, red);
ImGui :: Button ( "Red Button" );
ImGui :: PopStyleColor ();
}
// More draw calls
Reduce Window Count
// Less efficient: Many separate windows
ImGui :: Begin ( "Window1" );
ImGui :: End ();
ImGui :: Begin ( "Window2" );
ImGui :: End ();
ImGui :: Begin ( "Window3" );
ImGui :: End ();
// Better: Use child windows or sections
ImGui :: Begin ( "Main Window" );
if ( ImGui :: CollapsingHeader ( "Section 1" )) {
// Content 1
}
if ( ImGui :: CollapsingHeader ( "Section 2" )) {
// Content 2
}
if ( ImGui :: CollapsingHeader ( "Section 3" )) {
// Content 3
}
ImGui :: End ();
Optimize Rounding and Anti-Aliasing
ImGuiStyle & style = ImGui :: GetStyle ();
// Reduce for better performance
style . WindowRounding = 0.0 f ; // From 5.0f
style . FrameRounding = 0.0 f ; // From 3.0f
style . ScrollbarRounding = 0.0 f ; // From 2.0f
// Disable anti-aliasing on low-end hardware
style . AntiAliasedLines = false ;
style . AntiAliasedLinesUseTex = false ;
style . AntiAliasedFill = false ;
Impact:
No rounding = fewer vertices
No AA = simpler rendering
Can improve performance by 20-40% on low-end GPUs
Adjust Tessellation Quality
ImGuiStyle & style = ImGui :: GetStyle ();
// Reduce quality for better performance
style . CurveTessellationTol = 2.5 f ; // Default: 1.25f (higher = lower quality, fewer polygons)
style . CircleTessellationMaxError = 0.5 f ; // Default: 0.3f (higher = lower quality)
Minimize Transparency
// Transparent windows are more expensive to render
ImGui :: PushStyleColor (ImGuiCol_WindowBg, ImVec4 ( 0.0 f , 0.0 f , 0.0 f , 1.0 f )); // Opaque
// vs
ImGui :: PushStyleColor (ImGuiCol_WindowBg, ImVec4 ( 0.0 f , 0.0 f , 0.0 f , 0.5 f )); // Transparent
Memory Optimization
Font Atlas Size
ImFontConfig config;
config . OversampleH = 1 ; // Default: 3 (uses 3x horizontal memory)
config . OversampleV = 1 ; // Default: 1
io . Fonts -> AddFontFromFileTTF ( "font.ttf" , 16.0 f , & config);
Trade-off: Lower quality at small sizes, but smaller atlas.
Limit Glyph Ranges (Pre-1.92)
Before v1.92, loading only needed glyphs reduces atlas size: // Load only ASCII
io . Fonts -> AddFontFromFileTTF ( "font.ttf" , 16.0 f , nullptr ,
io . Fonts -> GetGlyphRangesDefault ());
// Custom ranges
ImVector < ImWchar > ranges;
ImFontGlyphRangesBuilder builder;
builder . AddText ( "YourGameText" ); // Add all text from your game
builder . BuildRanges ( & ranges);
io . Fonts -> AddFontFromFileTTF ( "font.ttf" , 16.0 f , nullptr , ranges . Data );
Disable Power-of-Two Height
io . Fonts -> Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;
Saves memory by using exact needed height instead of rounding up.
Reduce Buffer Sizes
// For large text inputs, only allocate what you need
static char small_buffer [ 64 ]; // For short inputs
static char medium_buffer [ 256 ]; // For medium inputs
// Don't default to huge buffers
// static char buffer[4096]; // Wasteful for most cases
Draw Call Optimization
Understanding Draw Calls
Each ImDrawCmd potentially triggers:
State changes (blend mode, scissor rect)
Texture binding
Draw call submission
View draw calls:
ImDrawData * draw_data = ImGui :: GetDrawData ();
for ( int n = 0 ; n < draw_data -> CmdListsCount ; n ++ ) {
const ImDrawList * cmd_list = draw_data -> CmdLists [n];
ImGui :: Text ( "DrawList %d : %d commands" , n, cmd_list -> CmdBuffer . Size );
}
Minimize Clipping Changes
// Good: Group items with same clipping
ImGui :: BeginChild ( "Region" , ImVec2 ( 0 , 300 ));
for ( int i = 0 ; i < 100 ; i ++ ) {
ImGui :: Text ( "Item %d " , i);
}
ImGui :: EndChild ();
// Less efficient: Many child regions
for ( int i = 0 ; i < 10 ; i ++ ) {
ImGui :: BeginChild ( "Region" , ImVec2 ( 0 , 30 ));
ImGui :: Text ( "Item %d " , i);
ImGui :: EndChild ();
}
Avoid Frequent Texture Switches
// If you're using custom images, batch by texture:
// Good: Same texture
for ( int i = 0 ; i < 10 ; i ++ ) {
ImGui :: Image (texture_atlas, size, uv0, uv1); // Same texture
}
// Less efficient: Different textures
for ( int i = 0 ; i < 10 ; i ++ ) {
ImGui :: Image ( textures [i], size); // Texture switch each iteration
}
Desktop (High-End)
// Enable quality features
ImGuiStyle & style = ImGui :: GetStyle ();
style . AntiAliasedLines = true ;
style . AntiAliasedLinesUseTex = true ;
style . AntiAliasedFill = true ;
style . WindowRounding = 5.0 f ;
style . FrameRounding = 3.0 f ;
// Use higher oversampling
ImFontConfig config;
config . OversampleH = 3 ;
config . OversampleV = 1 ;
Mobile / Low-End
// Reduce quality for performance
ImGuiStyle & style = ImGui :: GetStyle ();
style . AntiAliasedLines = false ;
style . AntiAliasedFill = false ;
style . WindowRounding = 0.0 f ;
style . FrameRounding = 0.0 f ;
style . CurveTessellationTol = 3.0 f ;
// Minimal oversampling
ImFontConfig config;
config . OversampleH = 1 ;
config . OversampleV = 1 ;
// Touch-friendly
style . TouchExtraPadding = ImVec2 ( 5 , 5 );
style . FramePadding = ImVec2 ( 8 , 6 ); // Larger hit boxes
Web (Emscripten)
// Keep draw calls low
// Minimize window count
// Reduce transparency
// Use simpler styles
// Consider frame rate limiting
Emscripten_WebGLContextAttributes attrs;
attrs . majorVersion = 2 ;
attrs . minorVersion = 0 ;
attrs . enableExtensionsByDefault = 1 ;
attrs . stencil = 0 ; // Disable if not needed
Profiling
Identify Bottlenecks
#include <chrono>
auto start = std :: chrono :: high_resolution_clock :: now ();
// Your ImGui code
RenderComplexUI ();
auto end = std :: chrono :: high_resolution_clock :: now ();
auto duration = std :: chrono :: duration_cast < std :: chrono :: microseconds >(end - start);
ImGui :: Text ( "UI Time: %.2f ms" , duration . count () / 1000.0 f );
Use Graphics Debuggers
RenderDoc (Windows, Linux) - https://renderdoc.org
Xcode Instruments (macOS, iOS)
NVIDIA Nsight (NVIDIA GPUs)
PIX (Windows, DirectX)
Symptom: High draw call countSolution: Use child windows, collapsing headers, or tabs instead of separate windows.
Large Lists Without Clipper
Symptom: Low FPS with long listsSolution: Always use ImGuiListClipper for lists with >100 items.
Excessive String Formatting
Symptom: High CPU usageSolution: Cache formatted strings, update only when data changes.
Symptom: High draw call countSolution: Batch similar items, minimize PushStyleColor/PushStyleVar calls.
Symptom: Slow initialization, high memory usageSolution: Reduce oversampling, limit glyph ranges (pre-1.92), or use multiple smaller fonts.
Benchmarking Example
struct PerformanceMetrics {
float avg_fps = 0.0 f ;
float min_fps = FLT_MAX;
float max_fps = 0.0 f ;
int total_vertices = 0 ;
int total_indices = 0 ;
int draw_calls = 0 ;
void Update () {
ImGuiIO & io = ImGui :: GetIO ();
float fps = io . Framerate ;
avg_fps = avg_fps * 0.95 f + fps * 0.05 f ; // Exponential moving average
min_fps = std :: min (min_fps, fps);
max_fps = std :: max (max_fps, fps);
ImDrawData * draw_data = ImGui :: GetDrawData ();
total_vertices = draw_data -> TotalVtxCount ;
total_indices = draw_data -> TotalIdxCount ;
draw_calls = 0 ;
for ( int n = 0 ; n < draw_data -> CmdListsCount ; n ++ ) {
draw_calls += draw_data -> CmdLists [n]-> CmdBuffer . Size ;
}
}
void Display () {
ImGui :: Begin ( "Performance" );
ImGui :: Text ( "FPS: %.1f (avg: %.1f , min: %.1f , max: %.1f )" ,
ImGui :: GetIO (). Framerate , avg_fps, min_fps, max_fps);
ImGui :: Text ( "Vertices: %d , Indices: %d " , total_vertices, total_indices);
ImGui :: Text ( "Draw Calls: %d " , draw_calls);
if ( ImGui :: Button ( "Reset Stats" )) {
min_fps = FLT_MAX;
max_fps = 0.0 f ;
}
ImGui :: End ();
}
};
Profile first - Identify actual bottlenecks before optimizing
Use ListClipper - For any list with >50 items
Early exit - Check Begin() return value
Batch style changes - Group similar items
Minimize windows - Use child windows instead
Cache data - Don’t recompute every frame
Reduce rounding - If targeting low-end hardware
Monitor metrics - Keep metrics window open during development
Test on target - Profile on actual target hardware
Optimize atlas - Reduce oversampling or glyph ranges if needed
Good performance:
60 FPS on target hardware
Less than 1000 vertices per frame for simple UIs
Less than 50 draw calls per frame
Less than 1ms CPU time for UI
Acceptable for complex UIs:
30-60 FPS
Less than 10000 vertices per frame
Less than 200 draw calls per frame
Less than 5ms CPU time for UI
If you’re significantly exceeding these numbers, investigate using the techniques in this guide.
See Also