Using ImDrawList for Custom 2D Rendering in Dear ImGui Windows: A Complete Guide
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, imgui_draw.cpp, and 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. 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 windowsImGui::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—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:
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:
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.
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 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.
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().
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 inimgui_draw.cppand push data intoVtxBuffer/IdxBuffer. - Clipping via
PushClipRect()affects only rendering, not interaction logic. - The backend (e.g.,
imgui_impl_opengl3.cpp) consumes the finalImDrawDatastructure 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 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.
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 →