# How to Use the Low-Level ImDrawList API to Render Custom Shapes in Dear ImGui

> Master Dear ImGui custom shapes using the low-level ImDrawList API. Learn to render lines, circles, and rectangles directly by pushing vertices into the command buffer.

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

---

**Use `ImGui::GetWindowDrawList()`, `GetBackgroundDrawList()`, or `GetForegroundDrawList()` to obtain an `ImDrawList` pointer, then call `Add*` methods like `AddLine()`, `AddCircleFilled()`, or `AddRect()` to push vertices directly into Dear ImGui's command buffer.**

Dear ImGui's immediate-mode architecture exposes a powerful low-level `ImDrawList` API that lets you bypass high-level widgets and issue raw drawing commands directly. Located in the `ocornut/imgui` repository, this interface sits just above the renderer-agnostic `ImDrawData` structure, allowing you to render custom geometry, images, and text that respects the current clipping and coordinate system.

## Obtaining a Draw List Instance

Before rendering custom shapes, you must acquire a valid `ImDrawList` pointer. Dear ImGui provides three primary helper functions depending on where you want your geometry to appear:

### Window-Relative Drawing

Call **`ImGui::GetWindowDrawList()`** to draw on top of the current window's content area. This is the most common entry point for custom widget rendering. Primitives added here respect the window's scrolling, clipping rectangle, and z-order relative to other ImGui widgets in that window.

### Global Background and Foreground Layers

For drawing outside the scope of any specific window, use:
- **`ImGui::GetBackgroundDrawList()`** – Renders behind all ImGui windows and content
- **`ImGui::GetForegroundDrawList()`** – Renders in front of all ImGui content

These global draw lists cover the entire viewport and are useful for overlays, debug visualization, or full-screen backgrounds.

### Stand-Alone Draw Lists

When creating an `ImDrawList` outside the standard ImGui context (for example, in a custom rendering pipeline), use **`ImGui::GetDrawListSharedData()`** to access the shared data structure required for initialization.

## Drawing Primitives with Add* Functions

The `ImDrawList` class exposes a comprehensive family of **`Add*`** methods to generate geometry. Each function accepts pixel coordinates, a packed 32-bit color (`ImU32`), and optional thickness or rounding parameters. According to the source code in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)**, these methods push vertices into `ImDrawList::VtxBuffer` and create corresponding `ImDrawCmd` entries in `CmdBuffer`.

Common primitives include:
- **`AddLine()`** – Defined at line 1478 in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)
- **`AddRect()`** and **`AddRectFilled()`**
- **`AddCircle()`** and **`AddCircleFilled()`**
- **`AddTriangle()`** and **`AddTriangleFilled()`** – Implemented around line 1593
- **`AddImage()`** – For texturing with `ImTextureID`
- **`AddText()`** – For custom font rendering

Here is a complete example rendering shapes inside a window:

```cpp
ImGui::Begin("Custom Shapes");

// Get the draw list for the current window
ImDrawList* draw = ImGui::GetWindowDrawList();

// Absolute screen position of the top-left corner of the window's content area
ImVec2 p = ImGui::GetCursorScreenPos();
float   radius = 40.0f;

// Draw a filled circle
draw->AddCircleFilled(p + ImVec2(60, 60), radius, IM_COL32(255, 0, 0, 255));

// Draw a thick line
draw->AddLine(p + ImVec2(120, 20), p + ImVec2(200, 80), IM_COL32(0, 255, 0, 255), 4.0f);

// Draw a triangle (outline)
draw->AddTriangle(p + ImVec2(250, 20), p + ImVec2(300, 80), p + ImVec2(200, 80),
                  IM_COL32(0, 0, 255, 255), 3.0f);

ImGui::End();

```

**Key points:** Use `GetCursorScreenPos()` to obtain absolute screen coordinates for positioning. The `IM_COL32` macro packs RGBA values into the `ImU32` format expected by the API.

## Managing Clipping Regions

To restrict rendering to a specific rectangular area, use **`PushClipRect()`** and **`PopClipRect()`**. These methods, implemented around line 592 in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp), modify the current command's clipping rectangle so the backend renderer discards fragments outside the bounds.

```cpp
ImDrawList* fg = ImGui::GetForegroundDrawList();   // rendered last
ImVec2 a(300, 300), b(500, 500);

// Define a clipping region (a square)
fg->PushClipRect(a, b, true);                     // intersect = true
fg->AddRectFilled(a, b, IM_COL32(255, 255, 0, 128)); // semi-transparent yellow
fg->PopClipRect();                                // restore previous clip

```

Passing `true` for the `intersect_with_current_clip_rect` parameter intersects the new rectangle with the existing clip region, ensuring nested clipping works correctly.

## Rendering Behind or In Front of UI

The background draw list renders first in the pipeline, making it ideal for viewport backgrounds or grid lines that should appear behind all ImGui windows.

```cpp
// This code can be placed anywhere after ImGui::NewFrame()
ImDrawList* bg = ImGui::GetBackgroundDrawList();

// Full-screen gradient background
ImVec2 screen_sz = ImGui::GetIO().DisplaySize;
bg->AddRectFilled(ImVec2(0, 0), screen_sz, IM_COL32(20, 20, 30, 255));
bg->AddRectFilled(ImVec2(0, 0), ImVec2(screen_sz.x, screen_sz.y * 0.5f),
                  IM_COL32(30, 30, 50, 255));

```

Conversely, the foreground draw list renders last, ensuring your custom geometry appears on top of every window, menu, and popup.

## Advanced Configuration and Callbacks

You can modify draw list behavior by adjusting **`drawList->Flags`**. Valid flags are defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) under `ImDrawListFlags_AntiAliasedLines` and `ImDrawListFlags_AntiAliasedLinesUseTex`, enabling hardware-accelerated anti-aliasing for primitives.

For injection of custom GPU commands or state changes, use **`AddCallback()`**:

```cpp
void MyCustomCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd);
// ...
draw_list->AddCallback(MyCustomCallback, my_user_data);

```

The callback receives the parent draw list and current command, allowing you to execute custom rendering logic that the backend will process when flushing the command buffer.

## Implementation Details in the Source

All draw list functionality is implemented in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** within the `ocornut/imgui` repository. The vertex generation and command management logic resides alongside the rendering backend interface. Key declarations are in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)**, while internal structures like `ImDrawListSharedData` and `ImDrawListSplitter` are detailed in **[`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h)**.

The rendering pipeline culminates in the `ImGui_Impl*` backend files (such as [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)), which iterate over `ImDrawData`, collect all `ImDrawList` instances, and submit the vertex/index buffers to your graphics API.

## Summary

- **Acquire** a draw list via `GetWindowDrawList()`, `GetBackgroundDrawList()`, or `GetForegroundDrawList()` depending on your target layer
- **Draw** primitives using `Add*` methods defined in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) (lines 1478-1593 for core shapes)
- **Clip** geometry with `PushClipRect()`/`PopClipRect()` to respect widget boundaries
- **Customize** rendering through `Flags` for anti-aliasing or `AddCallback()` for GPU state injection
- **Coordinate** all positions using absolute screen space (via `GetCursorScreenPos()`) to align with ImGui's clip rectangles

## Frequently Asked Questions

### What's the difference between GetWindowDrawList and GetBackgroundDrawList?

`GetWindowDrawList()` returns the draw list for the current window, clipping and positioning your geometry relative to that window's content region and scroll offset. `GetBackgroundDrawList()` returns a global draw list that renders behind all windows using absolute screen coordinates (0,0 to DisplaySize). Use the former for widget-internal decorations and the latter for viewport backgrounds or scene overlays.

### How do I convert RGB colors to ImU32 for ImDrawList?

Use the **`IM_COL32(R, G, B, A)`** macro defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h). It packs four 8-bit channel values into a single 32-bit unsigned integer (`ImU32`) in RGBA order. For example, opaque red is `IM_COL32(255, 0, 0, 255)`.

### Can I use ImDrawList outside of ImGui's NewFrame/Render cycle?

No. All draw list operations must occur between `ImGui::NewFrame()` and `ImGui::Render()`. The draw lists are cleared at the start of each frame and consumed by the backend during `Render()`. Attempting to use them outside this cycle results in undefined behavior or missing geometry.

### Where are the ImDrawList methods implemented?

All `ImDrawList::Add*` methods, clipping helpers, and callback handlers are implemented in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** in the Dear ImGui repository. For example, `AddLine` resides at line 1478, `AddTriangleFilled` at line 1593, and clip rect management around line 592. Public API declarations are in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)**.