How to Use the Dear ImGui ImDrawList API to Render Custom Shapes

The ImDrawList API provides direct access to Dear ImGui's vertex buffer, allowing you to render custom lines, rectangles, circles, and images by obtaining a draw list pointer and calling its Add* methods.

Dear ImGui (ocornut/imgui) exposes a low-level ImDrawList API that sits just above the renderer-agnostic ImDrawData structure, enabling direct manipulation of vertex buffers for custom rendering. Unlike high-level widgets such as ImGui::Button(), this interface lets you issue raw drawing commands using screen-space coordinates. All primitive functions are implemented in imgui_draw.cpp and operate by pushing vertices into ImDrawList::VtxBuffer while creating corresponding ImDrawCmd entries in ImDrawList::CmdBuffer.

Obtaining a Draw List Context

Before issuing draw commands, you must obtain a pointer to an ImDrawList instance. Dear ImGui provides three primary helper functions that return properly initialized lists tied to the current frame context:

  • ImGui::GetWindowDrawList() – Retrieves the draw list for the current window, automatically applying the window's scroll offset and clipping rectangle.
  • ImGui::GetBackgroundDrawList() – Returns a draw list rendered before all ImGui windows, useful for full-screen backgrounds or effects behind the UI.
  • ImGui::GetForegroundDrawList() – Returns a draw list rendered after all ImGui windows, suitable for overlays, selection boxes, or debug visualization on top of everything.

For advanced use cases involving standalone ImDrawList objects outside the standard context, use ImGui::GetDrawListSharedData() to access the shared font atlas and rendering data required for initialization.

Window-Scoped Drawing

When you need shapes to move with a specific window and respect its boundaries, call ImGui::GetWindowDrawList() after ImGui::Begin(). Primitives drawn here use screen-space coordinates, so always offset positions using ImGui::GetCursorScreenPos() to align with the window's content region.

Global Background and Foreground Layers

The background and foreground draw lists exist outside the window hierarchy. According to the source code in imgui.cpp, these are persistent lists maintained by the ImGuiContext that bypass individual window clipping, making them ideal for global effects.

Rendering Primitive Shapes

The ImDrawList class exposes a family of Add* functions defined in imgui_draw.cpp for emitting geometry. Each function accepts packed 32-bit colors (ImU32) created with the IM_COL32(r, g, b, a) macro, and optional parameters for thickness or rounding. Key methods include:

  • AddLine() – Defined at line 1478 in imgui_draw.cpp.
  • AddRect() and AddRectFilled() – Outlined and filled rectangles.
  • AddCircle() and AddCircleFilled() – Parametric circles with specified segment counts.
  • AddTriangle() and AddTriangleFilled() – With AddTriangleFilled implemented at line 1593 in imgui_draw.cpp.
  • AddText() and AddImage() – For custom font rendering and texture sampling.

The following example demonstrates drawing inside a window using screen-space coordinates:

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: GetWindowDrawList() guarantees that primitives respect the window's scrolling and clipping. The IM_COL32 macro packs RGBA components into the ImU32 format expected by the vertex buffer.

Managing Clipping Regions

To restrict drawing to a specific region, use PushClipRect() and PopClipRect(), implemented around line 592 in imgui_draw.cpp. These functions modify the clipping state stored per draw command, which the backend renderer evaluates during GPU submission.

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

Only geometry falling within the axis-aligned bounding box defined by a and b will be rasterized. Setting the intersectWithCurrentClipRect parameter to true performs an intersection with the existing clip region, ensuring nested clipping behaves correctly.

Drawing Behind or In Front of All UI

For effects that must appear behind or above the entire Dear ImGui interface, use the global draw lists. The background list is processed first by the backend, while the foreground list is processed last.

// 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));

This renders a two-tone background that remains behind all windows and widgets, as the ImDrawData structure processes background lists before window lists during the ImGui::Render() phase.

Advanced Configuration and Callbacks

You can temporarily modify rendering behavior by adjusting drawList->Flags, defined in imgui.h under the ImDrawListFlags_ enumeration. For example, enabling ImDrawListFlags_AntiAliasedLines affects subsequent line primitives.

For advanced effects requiring custom GPU state changes or shader switches, use AddCallback(callback, user_data). This pushes a special command into CmdBuffer that instructs the backend renderer (such as imgui_impl_opengl3.cpp) to execute your callback function, receiving the parent ImDrawList* and current ImDrawCmd* as parameters.

Implementation Details in the Source Code

Understanding the internal structure helps debug custom rendering issues. The relevant files in the ocornut/imgui repository include:

  • imgui.h – Public API declarations for ImDrawList, helper getters, color macros, and flag enumerations.
  • imgui_draw.cpp – Contains implementations of all Add* primitives, clipping logic, and callback handling at the line numbers referenced above.
  • imgui_internal.h – Defines internal structures such as ImDrawListSharedData and ImDrawListSplitter used for managing vertex buffers and command lists.
  • backends/imgui_impl_*.cpp (e.g., imgui_impl_opengl3.cpp) – Backend renderers that consume the ImDrawData structure and convert command buffers into GPU draw calls.

When you call an Add* method, the implementation writes vertex data (position, UV coordinates, and color) into VtxBuffer, then appends an ImDrawCmd to CmdBuffer containing the draw range and texture ID. At frame end, the ImGui_Impl* backend iterates these buffers to submit batches to the GPU.

Summary

  • Obtain a context using ImGui::GetWindowDrawList(), GetBackgroundDrawList(), or GetForegroundDrawList() depending on whether shapes should respect window bounds or render globally.
  • Emit geometry via Add* methods (implemented in imgui_draw.cpp) such as AddLine (line 1478) and AddTriangleFilled (line 1593), using IM_COL32 for color packing.
  • Control visibility with PushClipRect() and PopClipRect() (around line 592 in imgui_draw.cpp) to restrict rendering to specific screen regions.
  • Respect the coordinate system by using ImGui::GetCursorScreenPos() for absolute positioning within windows.
  • Advanced users can inject custom rendering logic using AddCallback() or modify anti-aliasing flags via drawList->Flags.

Frequently Asked Questions

How do I convert RGB float values to the ImU32 format required by ImDrawList?

Use the IM_COL32(r, g, b, a) macro defined in imgui.h. This macro packs four 8-bit unsigned integer channels into a single 32-bit value (0xRRGGBBAA). For example, opaque red is IM_COL32(255, 0, 0, 255). You can also use ImGui::ColorConvertFloat4ToU32() to convert from ImVec4 float components.

What is the difference between GetWindowDrawList and GetForegroundDrawList?

GetWindowDrawList() returns the list associated with the current window, applying that window's scroll offset and clipping rectangle automatically. Shapes drawn here move with the window and respect its boundaries. GetForegroundDrawList() returns a global list rendered on top of all ImGui content regardless of window order, useful for system-wide overlays or drag-and-drop visuals that must persist across multiple windows.

Can I use ImDrawList for rendering outside of the standard ImGui frame loop?

Yes, but you must manually manage the ImDrawList lifecycle and shared data. Create a standalone ImDrawList instance and initialize it using ImGui::GetDrawListSharedData() to access the font atlas and curve tessellation data required for text and anti-aliased primitives. You will also need to handle the ImDrawData assembly and backend submission yourself, mimicking the process in ImGui::Render().

Why are my custom shapes being clipped incorrectly or not appearing at all?

First, verify you are using screen-space coordinates (absolute pixels relative to the display top-left) rather than window-local coordinates. Use ImGui::GetCursorScreenPos() to obtain the correct offset. Second, check active clipping rectangles set by previous PushClipRect calls or parent widgets; call PopClipRect() to restore previous states. Finally, ensure you call draw list methods between ImGui::NewFrame() and ImGui::Render(), as the backend only processes buffers during the render phase.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →