How to Use the ImDrawList API for Custom Rendering in Dear ImGui

The ImDrawList API provides a dynamic mesh builder that lets you inject custom 2D primitives—lines, shapes, text, and textures—into Dear ImGui's rendering pipeline using GetWindowDrawList(), GetForegroundDrawList(), or GetBackgroundDrawList().

The ImDrawList API is the backbone of all rendering in Dear ImGui, exposed in the ocornut/imgui repository to enable custom drawing beyond standard widgets. Every window maintains its own draw list, while global foreground and background lists allow you to render overlays that exist outside the widget hierarchy. Understanding this API unlocks the ability to create custom visualizations, debug overlays, and hardware-accelerated primitives that integrate seamlessly with ImGui's existing rendering backend.

Understanding the ImDrawList Architecture

What is ImDrawList?

An ImDrawList is essentially a dynamic mesh builder that accumulates vertex and index data for GPU submission. According to the source in imgui.h (lines 3327-3337), each draw list stores vertices (ImDrawVert), indices (ImDrawIdx), and a sequence of draw commands (ImDrawCmd). The library automatically batches these commands into ImDrawData, which your rendering backend consumes through implementations like imgui_impl_opengl3.cpp.

Key Data Structures

The draw list relies on several critical structures defined in the source:

  • ImDrawListSharedData (imgui_internal.h lines 890-904): Provides fonts, texture IDs, and global anti-aliasing settings shared across draw lists.
  • ImDrawListFlags: Configures per-list behavior such as ImDrawListFlags_AntiAliasedLines, respecting the global ImGuiIO::AntiAliasedLines settings.
  • Draw List Splitter (imgui_internal.h lines 1901-1905): Enables channel splitting for out-of-order rendering, though most custom usage sticks to the default channel.

Retrieving a Draw List

You must obtain a draw list pointer before issuing any draw commands. The imgui.h header (lines 468-1038) defines three primary retrieval methods:

  • ImGui::GetWindowDrawList(): Returns the draw list for the current window, clipping output to the window boundaries automatically.
  • ImGui::GetForegroundDrawList(): Provides a global draw list rendered after all UI windows, ideal for overlays and debug graphics.
  • ImGui::GetBackgroundDrawList(): Provides a global draw list rendered before all UI windows, useful for background effects.
ImDrawList* window_draw = ImGui::GetWindowDrawList();      // Per-window
ImDrawList* overlay_draw = ImGui::GetForegroundDrawList(); // Top-most overlay

High-Level Primitive Methods

Once you have a draw list, the API provides high-level helpers in imgui_draw.cpp for common shapes. These methods automatically handle vertex generation, indexing, and command recording:

  • AddLine(p1, p2, col, thickness): Draws anti-aliased lines with configurable thickness.
  • AddRectFilled/AddRect(): Draws filled or outlined rectangles.
  • AddCircleFilled/AddCircle(): Generates triangulated circles with anti-aliasing.
  • AddText(): Renders font atlas glyphs with proper kerning.
  • AddImage(): Draws textured quads using ImTextureID.

These functions respect the current style colors and anti-aliasing settings defined in ImGuiIO.

Low-Level Custom Geometry

For complex shapes or custom shaders, the API exposes mesh-building primitives that let you write vertices and indices directly:

  1. PrimReserve(int idx_count, int vtx_count): Allocates space in the index and vertex buffers.
  2. PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col): Appends a vertex with position, UV coordinates, and color.
  3. PrimWriteIdx(ImDrawIdx idx): Appends an index referencing the vertex buffer.

This low-level access is defined in imgui_draw.cpp and allows you to construct arbitrary triangles, custom UV mappings, or instanced geometry that the high-level API cannot express.

Managing Clipping Regions

To restrict rendering to specific screen regions, the draw list provides explicit clipping controls (imgui.h lines 993-1005):

  • PushClipRect(screen_min, screen_max, intersect_with_current): Creates a clipping rectangle that masks subsequent draw commands.
  • PopClipRect(): Removes the last pushed clip rectangle.

This clipping is render-only and independent of ImGui's logical UI clipping, meaning you can define custom masks for visual effects without affecting input handling.

Practical Code Examples

The following snippets demonstrate production-ready patterns for the ImDrawList API.

Drawing a Screen-Space Overlay

Use the foreground draw list to render a line, rectangle, and text that appear above all UI elements:

void ShowCustomOverlay()
{
    ImDrawList* draw = ImGui::GetForegroundDrawList();
    
    ImVec2 p0 = ImGui::GetIO().DisplaySize * ImVec2(0.1f, 0.1f);
    ImVec2 p1 = ImGui::GetIO().DisplaySize * ImVec2(0.9f, 0.9f);
    
    // Red line with 3px thickness
    draw->AddLine(p0, p1, IM_COL32(255, 0, 0, 255), 3.0f);
    
    // Semi-transparent green rectangle
    draw->AddRectFilled(p0, p1, IM_COL32(0, 255, 0, 128));
    
    // White text centered
    ImVec2 center = (p0 + p1) * 0.5f;
    draw->AddText(center, IM_COL32(255, 255, 255, 255), "Custom Overlay");
}

Rendering a Custom Textured Quad

For direct mesh control, manually reserve space and write vertices for a textured quad:

void DrawTexturedQuad(ImTextureID tex_id, const ImVec2& a, const ImVec2& b)
{
    ImDrawList* draw = ImGui::GetWindowDrawList();
    
    // Reserve 4 vertices and 6 indices (2 triangles)
    draw->PrimReserve(4, 6);
    
    ImVec2 uv0(0, 0), uv1(1, 1);
    ImU32 col = IM_COL32_WHITE;
    
    // Write vertices: position, UV, color
    draw->PrimWriteVtx(a, ImVec2(0, 0), col);
    draw->PrimWriteVtx(ImVec2(b.x, a.y), ImVec2(1, 0), col);
    draw->PrimWriteVtx(b, ImVec2(1, 1), col);
    draw->PrimWriteVtx(ImVec2(a.x, b.y), ImVec2(0, 1), col);
    
    // Write indices for two triangles
    draw->PrimWriteIdx(0); draw->PrimWriteIdx(1); draw->PrimWriteIdx(2);
    draw->PrimWriteIdx(0); draw->PrimWriteIdx(2); draw->PrimWriteIdx(3);
}

Applying Clipped Rendering

Restrict drawing to a specific widget region using clip rectangles:

void DrawClippedCircle()
{
    ImDrawList* draw = ImGui::GetWindowDrawList();
    
    ImVec2 pos = ImGui::GetCursorScreenPos();
    ImVec2 size = ImGui::GetItemRectSize();
    
    draw->PushClipRect(pos, pos + size, true);
    
    ImVec2 center = pos + size * 0.5f;
    draw->AddCircleFilled(center, size.x * 0.4f, IM_COL32(0, 0, 255, 200));
    
    draw->PopClipRect(); // Always balance Push with Pop
}

Summary

  • ImDrawList acts as a dynamic mesh builder accumulating vertices, indices, and draw commands that feed into ImDrawData for GPU submission.
  • Obtain draw lists via GetWindowDrawList() for window-local content, or GetForegroundDrawList()/GetBackgroundDrawList() for global overlays.
  • High-level methods like AddLine, AddRect, and AddCircle provide immediate anti-aliased primitives.
  • Low-level access through PrimReserve, PrimWriteVtx, and PrimWriteIdx enables custom geometry and texture mapping.
  • Use PushClipRect and PopClipRect to constrain rendering to specific screen regions without affecting input logic.

Frequently Asked Questions

What is the difference between GetWindowDrawList and GetForegroundDrawList?

GetWindowDrawList() returns the draw list associated with the current ImGui window, automatically clipping output to that window's visible region and scissor area. GetForegroundDrawList() returns a global draw list rendered after all windows, making it ideal for debug overlays, tooltips, or visualizations that must appear on top of the entire UI. According to imgui.h (lines 468-1038), foreground and background draw lists bypass window-specific clipping unless you manually apply it.

How do I draw custom shapes that aren't covered by the Add* functions?

Use the low-level primitive API: call PrimReserve() to allocate vertex and index capacity, then populate the buffers with PrimWriteVtx() for position/UV/color data and PrimWriteIdx() to define triangle indices. This pattern, implemented in imgui_draw.cpp, allows you to submit arbitrary triangles, custom UV coordinates, or geometry compatible with specialized shaders while still leveraging ImGui's batching and rendering infrastructure.

Can I use ImDrawList to render 3D geometry or modify shaders?

While ImDrawList is fundamentally a 2D mesh builder, you can encode 3D vertices in the ImDrawVert position field if your backend shader transforms them. However, the API does not provide direct shader uniform setting or pipeline state changes—those must be handled in your backend implementation (e.g., imgui_impl_opengl3.cpp). The draw list simply accumulates vertex data and texture IDs that the backend interprets during the RenderDrawLists phase.

Why are my custom draw commands not appearing on screen?

First verify you retrieved the draw list pointer correctly and called the methods between ImGui::NewFrame() and ImGui::Render(). If using GetWindowDrawList(), ensure the window is actually visible and not collapsed. For global draw lists, check that your backend correctly processes ImDrawData after ImGui::Render(). Finally, confirm that ImTextureID values are valid in your backend and that you haven't unbalanced PushClipRect calls without corresponding PopClipRect calls, which can zero out the scissor region.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →