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

> Learn to use the ImDrawList API for custom rendering in Dear ImGui. Inject lines, shapes, text, and textures directly into vertex buffers for unique UI elements. Master high-level and low-level primitives.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-22

---

**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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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.

```cpp
// 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`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp). These methods automatically handle vertex generation, indexing, and command recording:

- **`AddLine(p1, p2, col, thickness)`**: Anti-aliased line segments
- **`AddRect(min, max, col, rounding, flags, thickness)`**: Outlined rectangles with optional rounding
- **`AddRectFilled(min, max, col, rounding, flags)`**: Solid rectangles
- **`AddCircle(center, radius, col, num_segments, thickness)`**: Outlined circles
- **`AddCircleFilled(center, radius, col, num_segments)`**: Filled circles
- **`AddText(pos, col, text)`**: Text rendering using the current font
- **`AddImage(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:

1. **`PrimReserve(idx_count, vtx_count)`**: Pre-allocates space in the buffers
2. **`PrimWriteVtx(pos, uv, col)`**: Appends a vertex with position, texture coordinates, and color
3. **`PrimWriteIdx(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.

```cpp
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`](https://github.com/ocornut/imgui/blob/main/imgui.h) lines 993-1005) to restrict drawing to a specific screen region:

```cpp
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`](https://github.com/ocornut/imgui/blob/main/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:

```cpp
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:

```cpp
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:

```cpp
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, and `GetForegroundDrawList()` for overlays.
- **Two abstraction levels**: High-level `Add*` primitives handle common shapes automatically, while `PrimReserve`/`PrimWriteVtx` provide full control for custom geometry.
- **Clipping is manual**: Use `PushClipRect` and `PopClipRect` to restrict rendering regions without affecting ImGui's interaction logic.
- **Source files**: Core definitions reside in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), implementations in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp), and shared data structures in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_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`](https://github.com/ocornut/imgui/blob/main/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.