How to Use the ImDrawList API for Custom Rendering in Dear ImGui
Dear ImGui's ImDrawList API provides a dynamic mesh builder interface that lets you inject custom geometry—lines, shapes, text, and textures—into vertex buffers using high-level primitives like AddLine() or low-level helpers like PrimWriteVtx(), which the backend renders as part of the standard ImDrawData pipeline.
Dear ImGui (ocornut/imgui) exposes a powerful immediate-mode drawing interface through the ImDrawList API, allowing developers to render custom 2D overlays, debug visualizations, and complex geometries directly into the UI pipeline. Every window maintains its own draw list, while global background and foreground lists enable screen-space rendering independent of widget hierarchies. Understanding this API unlocks the ability to visualize data, create custom widgets, and render debug information that integrates seamlessly with ImGui's rendering backend.
Understanding ImDrawList Architecture
An ImDrawList is essentially a dynamic mesh builder that accumulates geometry into GPU-friendly buffers. According to the class definition in imgui.h (lines 3327-3337), each draw list maintains:
- Vertex buffer (
ImVector<ImDrawVert>): Position, UV coordinates, and color for each point - Index buffer (
ImVector<ImDrawIdx>): Triangle indices connecting vertices - Command buffer (
ImVector<ImDrawCmd>): Draw calls with clipping rectangles and texture bindings - Flags (
ImDrawListFlags): Per-list anti-aliasing and line thickness settings
The draw list operates as an immediate-mode stream: you push commands, and the backend translates these into actual GPU draw calls via ImDrawData. The class references shared data through ImDrawListSharedData (defined in imgui_internal.h lines 890-904), which provides access to fonts, texture atlases, and global style settings.
Retrieving Draw List Instances
You must obtain a draw list pointer before issuing any commands. The retrieval method determines the render order and coordinate system, as defined in imgui.h (lines 468-1038):
ImGui::GetWindowDrawList(): Returns the draw list for the current window. Geometry renders in window-local coordinates and respects the window's Z-order.ImGui::GetBackgroundDrawList(): Returns a global draw list rendered behind all windows. Use this for full-screen background effects or world-space debug overlays.ImGui::GetForegroundDrawList(): Returns a global draw list rendered after all windows. Use this for HUDs, debug information, or overlays that must appear on top of the UI.
// Draw inside the current window
ImDrawList* window_draw = ImGui::GetWindowDrawList();
// Draw overlay on top of everything
ImDrawList* overlay_draw = ImGui::GetForegroundDrawList();
Rendering High-Level Primitives
The ImDrawList class provides convenience methods for common shapes, implemented in imgui_draw.cpp. These methods automatically handle vertex generation, indexing, and command recording:
AddLine(p1, p2, col, thickness): Anti-aliased line segmentsAddRect(min, max, col, rounding, flags, thickness): Outlined rectangles with optional roundingAddRectFilled(min, max, col, rounding, flags): Solid rectanglesAddCircle(center, radius, col, num_segments, thickness): Outlined circlesAddCircleFilled(center, radius, col, num_segments): Filled circlesAddText(pos, col, text): Text rendering using the current fontAddImage(texture_id, min, max, uv_min, uv_max, col): Textured quads
All color parameters use ImU32 packed as 0xAABBGGRR (or use the IM_COL32(r,g,b,a) macro). The draw list respects the global ImGuiIO::AntiAliasedLines and ImGuiIO::AntiAliasedFill settings, though you can override these per-list via ImDrawList::Flags.
Low-Level Mesh Construction
For complex custom geometry or when you need precise control over UVs and vertex attributes, use the low-level primitive API. This approach manually reserves buffer space and writes vertex/index data directly:
PrimReserve(idx_count, vtx_count): Pre-allocates space in the buffersPrimWriteVtx(pos, uv, col): Appends a vertex with position, texture coordinates, and colorPrimWriteIdx(idx): Appends an index referencing a vertex
This pattern is essential for rendering arbitrary polygons, custom shaders, or procedurally generated meshes that don't fit the standard primitive shapes.
void DrawCustomTriangle(ImDrawList* draw, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col)
{
draw->PrimReserve(3, 3);
// Write vertices
draw->PrimWriteVtx(p1, ImVec2(0,0), col);
draw->PrimWriteVtx(p2, ImVec2(1,0), col);
draw->PrimWriteVtx(p3, ImVec2(0.5f,1), col);
// Write indices (counter-clockwise winding)
draw->PrimWriteIdx(0);
draw->PrimWriteIdx(1);
draw->PrimWriteIdx(2);
}
Managing Clipping and State
Draw lists support render-time clipping independent of ImGui's UI logic. Use PushClipRect() and PopClipRect() (declared in imgui.h lines 993-1005) to restrict drawing to a specific screen region:
draw->PushClipRect(min_pos, max_pos, intersect_with_existing);
// ... drawing commands ...
draw->PopClipRect();
The intersect_with_existing parameter determines whether the new clip rectangle intersects with the current one or replaces it entirely. Always balance every PushClipRect with a corresponding PopClipRect to maintain stack integrity.
For complex rendering scenarios requiring out-of-order drawing (e.g., rendering a background, then content, then a border in a single draw list), use the Draw List Splitter (ImDrawListSplitter in imgui_internal.h lines 1901-1905) to partition commands into channels.
Practical Implementation Examples
Overlay Rendering with Foreground Draw List
Use GetForegroundDrawList() to create HUD elements that persist across all windows:
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 diagonal line
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));
// Centered text
ImVec2 center = (p0 + p1) * 0.5f;
draw->AddText(center, IM_COL32(255,255,255,255), "Custom Overlay");
}
Custom Textured Quad with Manual Vertices
When you need precise UV control or custom textures, use the low-level API:
void DrawTexturedQuad(ImTextureID tex_id, const ImVec2& a, const ImVec2& b)
{
ImDrawList* draw = ImGui::GetWindowDrawList();
draw->PrimReserve(6, 4);
ImVec2 uv0(0,0), uv1(1,0), uv2(1,1), uv3(0,1);
ImU32 col = IM_COL32_WHITE;
// Vertices: top-left, top-right, bottom-right, bottom-left
draw->PrimWriteVtx(a, uv0, col);
draw->PrimWriteVtx(ImVec2(b.x, a.y), uv1, col);
draw->PrimWriteVtx(b, uv2, col);
draw->PrimWriteVtx(ImVec2(a.x, b.y), uv3, col);
// Two triangles: 0-1-2 and 0-2-3
draw->PrimWriteIdx(0); draw->PrimWriteIdx(1); draw->PrimWriteIdx(2);
draw->PrimWriteIdx(0); draw->PrimWriteIdx(2); draw->PrimWriteIdx(3);
}
Clipped Rendering Within Widget Bounds
Restrict drawing to the area of a specific widget 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();
}
Summary
- Draw lists are mesh builders: They accumulate vertices and indices into buffers that backends render via
ImDrawData. - Three retrieval contexts: Use
GetWindowDrawList()for window-local content,GetBackgroundDrawList()for behind-UI rendering, andGetForegroundDrawList()for overlays. - Two abstraction levels: High-level
Add*primitives handle common shapes automatically, whilePrimReserve/PrimWriteVtxprovide full control for custom geometry. - Clipping is manual: Use
PushClipRectandPopClipRectto restrict rendering regions without affecting ImGui's interaction logic. - Source files: Core definitions reside in
imgui.h, implementations inimgui_draw.cpp, and shared data structures inimgui_internal.h.
Frequently Asked Questions
What is the difference between GetWindowDrawList and GetForegroundDrawList?
GetWindowDrawList() returns the draw list associated with the currently active ImGui window, using window-local coordinates and rendering within that window's Z-order layer. GetForegroundDrawList() returns a global draw list that renders in screen coordinates after all windows have been drawn, ensuring content appears on top of the entire UI. Use the former for custom widgets, the latter for debug overlays and HUDs.
How do I render a custom texture using the ImDrawList API?
Pass your ImTextureID (typically an OpenGL texture handle, Vulkan descriptor set, or DirectX shader resource view) to AddImage() for simple quads, or specify the texture in the ImDrawCmd when using low-level primitives. Ensure your backend implementation correctly handles texture binding in RenderDrawLists (see imgui_impl_opengl3.cpp for reference).
Can I use ImDrawList for 3D rendering or custom shaders?
While ImDrawList is designed for 2D geometry, you can hijack it for 3D by transforming vertices before submission or by using the low-level API to write custom positions that your vertex shader interprets as 3D coordinates. However, the draw list does not support custom shader uniforms per-command; you must modify the backend or use texture IDs to pass additional data.
How do I optimize performance when drawing thousands of primitives?
Minimize draw command fragmentation by batching calls with the same texture and clipping rectangle. Use PrimReserve to allocate buffer space once rather than calling high-level Add* functions in tight loops if you're generating procedural geometry. Consider using the ImDrawListSplitter to manage channels efficiently when rendering complex layered content in a single draw list.
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 →