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

> Unlock custom rendering in Dear ImGui with the ImDrawList API. Inject lines, shapes, text, and textures directly into the rendering pipeline using GetWindowDrawList, GetForegroundDrawList, or GetBackgroundDrawList.

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

---

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

### Key Data Structures

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

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

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

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

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

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