# How to Use ImDrawList for Custom Low-Level Rendering in Dear ImGui

> Learn to use ImDrawList for custom low-level rendering in Dear ImGui. Directly render primitives bypassing widgets with AddLine, AddRectFilled, and AddCallback.

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

---

**ImDrawList** is Dear ImGui's command buffer that records vertices, indices, and clipping rectangles, allowing you to bypass high-level widgets and render custom primitives directly via methods like `AddLine()`, `AddRectFilled()`, and `AddCallback()`.

Dear ImGui (`ocornut/imgui`) renders its entire UI through a draw-list abstraction. Mastering **ImDrawList** for custom low-level rendering operations enables you to inject bespoke geometry, textured quads, and raw GPU commands directly into the frame output. This guide covers the architecture, state management stacks, and implementation patterns found in the official source code.

## Obtaining an ImDrawList Instance

ImGui provides three entry points to access draw lists, depending on whether you want to render within a window or independently of the window hierarchy.

**Window-local draw list.** Each `ImGuiWindow` owns an `ImDrawList` that is automatically cleared and rebuilt every frame. Retrieve it inside any window block:

```cpp
ImDrawList* draw = ImGui::GetWindowDrawList();   // valid only between Begin()/End()

```

**Global foreground draw list.** Render on top of all windows by using the overlay list returned by `ImGui::GetForegroundDrawList()`. This is ideal for debug overlays or tooltips that must appear above the UI.

**Global background draw list.** Render behind all windows using `ImGui::GetBackgroundDrawList()`. This is useful for custom backgrounds or world-space grids.

These functions are thin wrappers around the internal `ImGuiContext::DrawListSharedData` structure defined in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)**.

## Core ImDrawList Rendering API

The `ImDrawList` class in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** exposes methods that append commands to an internal `CmdBuffer` and vertex data to `VtxBuffer`/`IdxBuffer`. Key methods include:

- **`AddLine(p1, p2, col, thickness)`** – Emits an anti-aliased line segment by generating quad geometry. Defined in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)**.
- **`AddRect(p_min, p_max, col, rounding, rounding_corners, thickness)`** – Draws an axis-aligned rectangle outline with optional corner rounding.
- **`AddRectFilled(p_min, p_max, col, rounding, rounding_corners)`** – Emits a solid quad, which is faster than the outlined version.
- **`AddPolyline(points, n, col, flags, thickness)`** – Draws a connected series of lines; pass `ImDrawFlags_Closed` to connect the last point to the first.
- **`AddConvexPolyFilled(points, n, col)`** – Efficiently triangulates and fills convex polygons.
- **`AddTriangle(p1, p2, p3, col, thickness)`** and **`AddTriangleFilled`** – Basic triangle primitives.
- **`AddImage(tex, a, b, uv_a, uv_b, tint_col)`** – Draws a textured quad using your own `ImTextureID` (typically a GPU texture handle).
- **`AddCallback(callback, userdata)`** – Inserts a user callback into the command stream to execute custom GPU code.

All high-level methods internally call **`PrimReserve`** to allocate vertex/index space, fill the buffers, and update the current `ImDrawCmd`. When the frame ends, the backend (e.g., `ImGui_ImplOpenGL3_RenderDrawData`) consumes these buffers to issue actual GPU draw calls.

## Managing State: Clip Rectangles and Textures

`ImDrawList` maintains two state stacks that determine how primitives are batched into draw commands.

**Clip-rect stack.** Use `PushClipRect(min, max, intersect)` and `PopClipRect()` to restrict drawing to a screen-space region. The top of the stack is baked into `_CmdHeader.ClipRect`; changing it triggers `AddDrawCmd()` to start a new command with updated clipping.

**Texture stack.** Use `PushTexture(tex)` and `PopTexture()` to bind custom textures. The current texture ID (`_CmdHeader.TexRef`) is embedded in each draw command. Switching textures forces a new command, ensuring the backend binds the correct resource before drawing the batch.

These stacks allow you to nest clipping regions and texture switches without manually managing command boundaries.

## Step-by-Step Workflow for Custom Rendering

A typical low-level rendering sequence follows this pattern:

```cpp
// 1. Obtain a draw list (foreground draws over all windows)
ImDrawList* dl = ImGui::GetForegroundDrawList();

// 2. (Optional) Define a clipping rectangle
dl->PushClipRect(ImVec2(100, 100), ImVec2(400, 300), true);

// 3. (Optional) Bind a custom texture
dl->PushTexture(myTextureId);   // ImTextureID is typically GLuint or void*

// 4. Issue primitives
dl->AddLine(ImVec2(120, 120), ImVec2(380, 280), IM_COL32(255, 0, 0, 255), 3.0f);
dl->AddRectFilled(ImVec2(150, 150), ImVec2(350, 250), IM_COL32(0, 128, 255, 200));

// 5. Restore state
dl->PopTexture();
dl->PopClipRect();

```

All calls are immediate-mode in the sense that they only record data; the actual GPU rendering occurs later when the backend processes `ImDrawData`.

## Practical Code Examples

### Drawing a Custom Shape

This example defines a five-pointed star using `AddLine` to connect every second vertex:

```cpp
void DrawStar(ImDrawList* dl, ImVec2 centre, float radius, ImU32 col)
{
    const int NUM_VERTS = 5;
    ImVec2 pts[NUM_VERTS];
    for (int i = 0; i < NUM_VERTS; ++i)
    {
        float a = IM_PI * 2.0f * i / NUM_VERTS - IM_PI / 2.0f;
        pts[i] = ImVec2(centre.x + cosf(a) * radius,
                        centre.y + sinf(a) * radius);
    }
    for (int i = 0; i < NUM_VERTS; ++i)
        dl->AddLine(pts[i], pts[(i + 2) % NUM_VERTS], col, 2.0f);
}

// Usage inside a window
ImDrawList* dl = ImGui::GetWindowDrawList();
DrawStar(dl, ImVec2(200, 150), 80.0f, IM_COL32(255, 215, 0, 255));

```

### Batching Textured Sprites

Render multiple quads from the same texture by pushing the texture once and calling `AddImage` for each sprite:

```cpp
void DrawSpriteBatch(ImDrawList* dl, ImTextureID tex, const ImVec2* pos, int count, float size)
{
    dl->PushTexture(tex);
    for (int i = 0; i < count; ++i)
    {
        ImVec2 a = pos[i];
        ImVec2 b = ImVec2(a.x + size, a.y + size);
        dl->AddImage(tex, a, b, ImVec2(0, 0), ImVec2(1, 1));
    }
    dl->PopTexture();
}

// Usage
ImTextureID myTex = (ImTextureID)(intptr_t)myOpenGLTexture;
ImVec2 positions[3] = { {50,50}, {150,70}, {250,120} };
DrawSpriteBatch(ImGui::GetForegroundDrawList(), myTex, positions, 3, 64.0f);

```

### Injecting Custom GPU Callbacks

Use `AddCallback` to insert raw GPU commands, such as binding a custom shader or drawing a mesh that ImDrawList cannot represent natively:

```cpp
void MyRenderCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
    // Example: render a triangle with a custom shader
    glUseProgram(my_shader_program);
    glBindVertexArray(my_vao);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glBindVertexArray(0);
}

// Insert callback into the command stream
ImDrawList* dl = ImGui::GetBackgroundDrawList();
dl->AddCallback(MyRenderCallback, nullptr);

```

## Advanced ImDrawList Techniques

### Custom Vertex Formats

If your backend requires additional per-vertex attributes (e.g., custom UV channels or tangent data), redefine `ImDrawVert` in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** before including ImGui headers. The draw list automatically respects the vertex size reported by `ImGui::GetIO().MetricsRenderVertices`.

### User Callbacks for Specialized Rendering

The `AddCallback` mechanism allows you to break out of the standard triangle-list rendering. Your callback receives the parent `ImDrawList` and the current `ImDrawCmd`, enabling you to issue compute dispatches, bind specific framebuffer targets, or render complex 3D geometry that shares the Z-buffer with the UI.

### Handling Large Meshes

Enable `ImDrawListFlags_AllowVtxOffset` on your draw list to support vertex indices larger than 16-bit without splitting the draw list. This is essential when submitting large procedural meshes that exceed the default index limit. The flag is checked inside `PrimReserve` to determine buffer allocation strategy.

## Key Source Files in the ImGui Repository

Understanding the following files is essential for debugging and extending `ImDrawList` behavior:

- **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** – Public API declarations for `ImDrawList`, `ImDrawCmd`, and `ImDrawVert`.
- **[`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h)** – Internal structures like `ImDrawListSharedData` and low-level primitive helpers.
- **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** – Full implementation of `ImDrawList`, including primitive generation (`AddRect`, `AddLine`), path filling, and command buffering.
- **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp)** – Reference implementations demonstrating draw list usage for grids, polylines, and custom widgets.
- **[`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)** (or your specific backend) – Translates `ImDrawData` into GPU draw calls, consuming the buffers produced by `ImDrawList`.

## Summary

- **ImDrawList** records low-level draw commands in `CmdBuffer` and vertex data in `VtxBuffer`/`IdxBuffer`.
- Access via `GetWindowDrawList()`, `GetForegroundDrawList()`, or `GetBackgroundDrawList()` depending on layering needs.
- Use `PushClipRect`/`PopClipRect` and `PushTexture`/`PopTexture` to manage state without manual command splitting.
- Primitives like `AddLine`, `AddRectFilled`, and `AddImage` route through `PrimReserve` for vertex allocation.
- Insert raw GPU code with `AddCallback`, or customize vertex formats by redefining `ImDrawVert` before compilation.

## Frequently Asked Questions

### What is the difference between GetWindowDrawList and GetForegroundDrawList?

`GetWindowDrawList()` returns the draw list owned by the currently active window, clipped to that window's boundaries and drawn at the window's Z-level. `GetForegroundDrawList()` returns a global draw list rendered after all windows, making it suitable for overlays, debug graphics, or tooltips that must appear on top of the entire UI.

### How do I clip rendering to a specific region using ImDrawList?

Call `PushClipRect(ImVec2 min, ImVec2 max, bool intersect_with_current)` before your draw commands, then `PopClipRect()` afterward. As implemented in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)**, this pushes the rectangle onto an internal stack and encodes the top value into subsequent draw commands, ensuring the GPU scissor test limits rasterization to that region.

### Can I use custom shaders with ImDrawList?

Yes. While `ImDrawList` itself generates standard triangle lists for the default shader, you can inject custom shader binds using `AddCallback()`. Your callback function receives the draw list and command pointer, allowing you to execute platform-specific code (e.g., `glUseProgram`) before or after ImGui's geometry.

### Where are the vertex buffers stored when using ImDrawList?

Vertex data is stored in `ImDrawList::VtxBuffer` (array of `ImDrawVert`) and index data in `ImDrawList::IdxBuffer` (array of `ImDrawIdx`). These vectors are populated during the frame and then referenced by the backend renderer (such as `ImGui_ImplOpenGL3_RenderDrawData`) to upload to GPU memory or map directly into command buffers.