How to Use the Low-Level ImDrawList API to Render Custom Shapes in Dear ImGui
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 contentImGui::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, these methods push vertices into ImDrawList::VtxBuffer and create corresponding ImDrawCmd entries in CmdBuffer.
Common primitives include:
AddLine()– Defined at line 1478 inimgui_draw.cppAddRect()andAddRectFilled()AddCircle()andAddCircleFilled()AddTriangle()andAddTriangleFilled()– Implemented around line 1593AddImage()– For texturing withImTextureIDAddText()– For custom font rendering
Here is a complete example rendering shapes inside a window:
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, modify the current command's clipping rectangle so the backend renderer discards fragments outside the bounds.
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.
// 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 under ImDrawListFlags_AntiAliasedLines and ImDrawListFlags_AntiAliasedLinesUseTex, enabling hardware-accelerated anti-aliasing for primitives.
For injection of custom GPU commands or state changes, use AddCallback():
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 within the ocornut/imgui repository. The vertex generation and command management logic resides alongside the rendering backend interface. Key declarations are in imgui.h, while internal structures like ImDrawListSharedData and ImDrawListSplitter are detailed in imgui_internal.h.
The rendering pipeline culminates in the ImGui_Impl* backend files (such as 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(), orGetForegroundDrawList()depending on your target layer - Draw primitives using
Add*methods defined inimgui_draw.cpp(lines 1478-1593 for core shapes) - Clip geometry with
PushClipRect()/PopClipRect()to respect widget boundaries - Customize rendering through
Flagsfor anti-aliasing orAddCallback()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. 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →