# Using ImDrawList for Custom 2D Rendering in Dear ImGui Windows: A Complete Guide

> Master custom 2D rendering in Dear ImGui using ImDrawList. Learn to add lines, rectangles, and images with this comprehensive guide.

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

---

**Dear ImGui generates a dedicated ImDrawList for every window to accumulate vertex and index buffers, exposing primitive methods like `AddLine()`, `AddRect()`, and `AddImage()` that backends convert into GPU draw calls for custom 2D graphics.**

Dear ImGui (ocornut/imgui) handles all UI rendering through an intermediate mesh-building layer rather than immediate API calls. When you need to render custom visualizations—charts, vector graphics, or image overlays—inside an ImGui window, you interface directly with the **ImDrawList** API. This guide examines the source code architecture in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp), and [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) to demonstrate how to inject custom geometry into Dear ImGui’s rendering pipeline.

## Understanding ImDrawList Architecture

### Creation and Ownership Model

Every ImGui window owns an independent `ImDrawList` instance stored in the `ImGuiWindow::DrawListInst` member. When a window opens, Dear ImGui allocates this draw list and registers it with the shared data structure (`ImDrawListSharedData`) defined in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h). You obtain the current window’s list through `ImGui::GetWindowDrawList()`, which returns a pointer to the active `ImDrawList`.

For global overlays, Dear ImGui provides two additional lists:
- `ImGui::GetBackgroundDrawList()` – rendered before all windows
- `ImGui::GetForegroundDrawList()` – rendered after all windows and tooltips

These global lists are useful for watermarking or screen-space annotations that must appear behind or in front of the entire UI.

### The Rendering Pipeline

An `ImDrawList` functions as a simple mesh builder. It accumulates vertices and indices in internal buffers (`VtxBuffer` and `IdxBuffer`) that the backend later consumes. Each frame, Dear ImGui populates an `ImDrawData` structure containing all active draw lists. The backend implementation—such as [`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)—iterates through these commands, binds the appropriate textures, and issues the actual GPU draw calls.

The draw list supports flags like `ImDrawListFlags_AllowVtxOffset` to enable 32-bit vertex offsets for large meshes, ensuring compatibility with backends that support indexed rendering beyond 16-bit limits.

## Accessing Draw Lists for Custom Rendering

### Window-Specific Coordinates

When using `ImGui::GetWindowDrawList()`, all coordinates are **absolute screen positions**, not relative to the window content area. To align custom geometry with ImGui’s layout system, query the current cursor position:

```cpp
ImVec2 origin = ImGui::GetCursorScreenPos();  // Top-left of current item in screen space

```

This returns the absolute screen coordinate where Dear ImGui will place the next widget, allowing you to synchronize custom drawing with UI elements.

### Practical Example: Drawing a Filled Triangle

The following example draws a red triangle inside the current window’s content region using the window’s draw list:

```cpp
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 p = ImGui::GetCursorScreenPos();
ImU32 col = ImGui::GetColorU32(ImGuiCol_Button);

draw->AddTriangleFilled(
    p + ImVec2(10, 10),
    p + ImVec2(100, 10),
    p + ImVec2(55, 80),
    col
);

```

This pushes six vertices (two triangles) into the draw list’s buffer, which the backend renders with the current texture atlas.

## Rendering Textures and Custom Images

The `AddImage()` method accepts an `ImTextureID` handle (typically an OpenGL texture ID, Vulkan descriptor set, or DirectX resource view) and maps it to a rectangle using UV coordinates. This allows you to display loaded textures or render targets within ImGui windows.

```cpp
ImTextureID tex_id = (ImTextureID)(intptr_t)my_gl_texture;
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 a = ImGui::GetCursorScreenPos();
ImVec2 b = a + ImVec2(128, 128);

draw->AddImage(
    tex_id, 
    a, 
    b,
    ImVec2(0, 0),           // UV0 (top-left)
    ImVec2(1, 1),           // UV1 (bottom-right)
    ImGui::GetColorU32(ImGuiCol_Text)  // Tint color
);

```

The implementation in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) handles vertex generation for the quad and references the white-pixel UV from `ImDrawListSharedData` when no texture is specified for color primitives.

## Implementing Clipping Regions

The `PushClipRect()` and `PopClipRect()` methods restrict rendering to a sub-region without affecting Dear ImGui’s interaction logic or layout calculations. This is essential when creating scrollable custom content or masking effects.

```cpp
ImDrawList* draw = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 size = ImVec2(200, 200);

// Restrict subsequent drawing to this rectangle
draw->PushClipRect(pos, pos + size, true);
draw->AddCircleFilled(
    pos + ImVec2(100, 100), 
    80.0f, 
    ImGui::GetColorU32(ImGuiCol_CheckMark)
);
draw->PopClipRect();

```

The boolean parameter to `PushClipRect()` specifies whether to intersect with the existing clip rectangle or replace it entirely.

## Layering: Foreground and Background Techniques

For elements that must render on top of all windows—such as custom tooltips, selection boxes, or debug overlays—use the foreground draw list. This list is processed after all window draw lists in `ImGui::Render()`.

```cpp
ImDrawList* fg = ImGui::GetForegroundDrawList();
ImVec2 mouse = ImGui::GetIO().MousePos;

// Draw a custom tooltip background
fg->AddRectFilled(
    mouse, 
    mouse + ImVec2(120, 30),
    ImGui::GetColorU32(ImGuiCol_PopupBg)
);

fg->AddText(
    mouse + ImVec2(5, 5), 
    ImGui::GetColorU32(ImGuiCol_Text),
    "Custom Tooltip"
);

```

Because this draw list exists outside any window, it persists across frames until you clear or overwrite its contents, making it ideal for transient visual feedback.

## Summary

- **ImDrawList** acts as a per-window mesh builder that accumulates vertices and indices for GPU rendering via the backend.
- Access the current window’s list with `ImGui::GetWindowDrawList()`, or use global foreground/background lists for overlays.
- Coordinates are absolute screen space; use `ImGui::GetCursorScreenPos()` to align with ImGui’s layout system.
- Primitive methods (`AddLine`, `AddRect`, `AddCircle`, `AddImage`) are implemented in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) and push data into `VtxBuffer`/`IdxBuffer`.
- **Clipping** via `PushClipRect()` affects only rendering, not interaction logic.
- The backend (e.g., [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)) consumes the final `ImDrawData` structure to execute GPU commands.

## Frequently Asked Questions

### What coordinate system does ImDrawList use?

ImDrawList operates in **absolute screen coordinates** (relative to the top-left of the display or framebuffer). Unlike ImGui’s window-relative cursor positioning, draw list vertices require screen-space coordinates. Always use `ImGui::GetCursorScreenPos()` to obtain the current absolute origin when aligning custom shapes with UI elements.

### Can I use ImDrawList outside of an ImGui window?

Yes. While every window owns a draw list accessible via `GetWindowDrawList()`, Dear ImGui provides `GetBackgroundDrawList()` and `GetForegroundDrawList()` for screen-space rendering independent of windows. These are allocated in `ImGuiContext` and rendered before or after the window list, respectively.

### How do I render large meshes without hitting vertex limits?

Enable the `ImDrawListFlags_AllowVtxOffset` flag on your draw list to support 32-bit vertex indices. This allows meshes exceeding 65,535 vertices, which is necessary for complex vector graphics or detailed plots. The backend must support `ImDrawCmd::VtxOffset`; most modern implementations in [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) and similar handle this automatically when the graphics API permits.

### Does PushClipRect affect ImGui's mouse interaction?

No. `PushClipRect()` and `PopClipRect()` modify only the rendering scissor rectangle stored in the draw command. They do **not** constrain ImGui’s hit-testing, `IsMouseHoveringRect()` checks, or widget interaction logic. If you need interactive custom content, you must manually test mouse positions against your geometry bounds using `ImGui::IsMouseHoveringRect()` or similar logic in your update code.