How to Use ImDrawList for Custom Low-Level Rendering in Dear ImGui
ImDrawList is Dear ImGui's command buffer that records vertices, indices, and clipping rectangles, allowing you to bypass high-level widgets and render custom primitives directly via methods like AddLine(), AddRectFilled(), and AddCallback().
Dear ImGui (ocornut/imgui) renders its entire UI through a draw-list abstraction. Mastering ImDrawList for custom low-level rendering operations enables you to inject bespoke geometry, textured quads, and raw GPU commands directly into the frame output. This guide covers the architecture, state management stacks, and implementation patterns found in the official source code.
Obtaining an ImDrawList Instance
ImGui provides three entry points to access draw lists, depending on whether you want to render within a window or independently of the window hierarchy.
Window-local draw list. Each ImGuiWindow owns an ImDrawList that is automatically cleared and rebuilt every frame. Retrieve it inside any window block:
ImDrawList* draw = ImGui::GetWindowDrawList(); // valid only between Begin()/End()
Global foreground draw list. Render on top of all windows by using the overlay list returned by ImGui::GetForegroundDrawList(). This is ideal for debug overlays or tooltips that must appear above the UI.
Global background draw list. Render behind all windows using ImGui::GetBackgroundDrawList(). This is useful for custom backgrounds or world-space grids.
These functions are thin wrappers around the internal ImGuiContext::DrawListSharedData structure defined in imgui_draw.cpp.
Core ImDrawList Rendering API
The ImDrawList class in imgui.h exposes methods that append commands to an internal CmdBuffer and vertex data to VtxBuffer/IdxBuffer. Key methods include:
AddLine(p1, p2, col, thickness)– Emits an anti-aliased line segment by generating quad geometry. Defined inimgui_draw.cpp.AddRect(p_min, p_max, col, rounding, rounding_corners, thickness)– Draws an axis-aligned rectangle outline with optional corner rounding.AddRectFilled(p_min, p_max, col, rounding, rounding_corners)– Emits a solid quad, which is faster than the outlined version.AddPolyline(points, n, col, flags, thickness)– Draws a connected series of lines; passImDrawFlags_Closedto connect the last point to the first.AddConvexPolyFilled(points, n, col)– Efficiently triangulates and fills convex polygons.AddTriangle(p1, p2, p3, col, thickness)andAddTriangleFilled– Basic triangle primitives.AddImage(tex, a, b, uv_a, uv_b, tint_col)– Draws a textured quad using your ownImTextureID(typically a GPU texture handle).AddCallback(callback, userdata)– Inserts a user callback into the command stream to execute custom GPU code.
All high-level methods internally call PrimReserve to allocate vertex/index space, fill the buffers, and update the current ImDrawCmd. When the frame ends, the backend (e.g., ImGui_ImplOpenGL3_RenderDrawData) consumes these buffers to issue actual GPU draw calls.
Managing State: Clip Rectangles and Textures
ImDrawList maintains two state stacks that determine how primitives are batched into draw commands.
Clip-rect stack. Use PushClipRect(min, max, intersect) and PopClipRect() to restrict drawing to a screen-space region. The top of the stack is baked into _CmdHeader.ClipRect; changing it triggers AddDrawCmd() to start a new command with updated clipping.
Texture stack. Use PushTexture(tex) and PopTexture() to bind custom textures. The current texture ID (_CmdHeader.TexRef) is embedded in each draw command. Switching textures forces a new command, ensuring the backend binds the correct resource before drawing the batch.
These stacks allow you to nest clipping regions and texture switches without manually managing command boundaries.
Step-by-Step Workflow for Custom Rendering
A typical low-level rendering sequence follows this pattern:
// 1. Obtain a draw list (foreground draws over all windows)
ImDrawList* dl = ImGui::GetForegroundDrawList();
// 2. (Optional) Define a clipping rectangle
dl->PushClipRect(ImVec2(100, 100), ImVec2(400, 300), true);
// 3. (Optional) Bind a custom texture
dl->PushTexture(myTextureId); // ImTextureID is typically GLuint or void*
// 4. Issue primitives
dl->AddLine(ImVec2(120, 120), ImVec2(380, 280), IM_COL32(255, 0, 0, 255), 3.0f);
dl->AddRectFilled(ImVec2(150, 150), ImVec2(350, 250), IM_COL32(0, 128, 255, 200));
// 5. Restore state
dl->PopTexture();
dl->PopClipRect();
All calls are immediate-mode in the sense that they only record data; the actual GPU rendering occurs later when the backend processes ImDrawData.
Practical Code Examples
Drawing a Custom Shape
This example defines a five-pointed star using AddLine to connect every second vertex:
void DrawStar(ImDrawList* dl, ImVec2 centre, float radius, ImU32 col)
{
const int NUM_VERTS = 5;
ImVec2 pts[NUM_VERTS];
for (int i = 0; i < NUM_VERTS; ++i)
{
float a = IM_PI * 2.0f * i / NUM_VERTS - IM_PI / 2.0f;
pts[i] = ImVec2(centre.x + cosf(a) * radius,
centre.y + sinf(a) * radius);
}
for (int i = 0; i < NUM_VERTS; ++i)
dl->AddLine(pts[i], pts[(i + 2) % NUM_VERTS], col, 2.0f);
}
// Usage inside a window
ImDrawList* dl = ImGui::GetWindowDrawList();
DrawStar(dl, ImVec2(200, 150), 80.0f, IM_COL32(255, 215, 0, 255));
Batching Textured Sprites
Render multiple quads from the same texture by pushing the texture once and calling AddImage for each sprite:
void DrawSpriteBatch(ImDrawList* dl, ImTextureID tex, const ImVec2* pos, int count, float size)
{
dl->PushTexture(tex);
for (int i = 0; i < count; ++i)
{
ImVec2 a = pos[i];
ImVec2 b = ImVec2(a.x + size, a.y + size);
dl->AddImage(tex, a, b, ImVec2(0, 0), ImVec2(1, 1));
}
dl->PopTexture();
}
// Usage
ImTextureID myTex = (ImTextureID)(intptr_t)myOpenGLTexture;
ImVec2 positions[3] = { {50,50}, {150,70}, {250,120} };
DrawSpriteBatch(ImGui::GetForegroundDrawList(), myTex, positions, 3, 64.0f);
Injecting Custom GPU Callbacks
Use AddCallback to insert raw GPU commands, such as binding a custom shader or drawing a mesh that ImDrawList cannot represent natively:
void MyRenderCallback(const ImDrawList* parent_list, const ImDrawCmd* cmd)
{
// Example: render a triangle with a custom shader
glUseProgram(my_shader_program);
glBindVertexArray(my_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}
// Insert callback into the command stream
ImDrawList* dl = ImGui::GetBackgroundDrawList();
dl->AddCallback(MyRenderCallback, nullptr);
Advanced ImDrawList Techniques
Custom Vertex Formats
If your backend requires additional per-vertex attributes (e.g., custom UV channels or tangent data), redefine ImDrawVert in imgui.h before including ImGui headers. The draw list automatically respects the vertex size reported by ImGui::GetIO().MetricsRenderVertices.
User Callbacks for Specialized Rendering
The AddCallback mechanism allows you to break out of the standard triangle-list rendering. Your callback receives the parent ImDrawList and the current ImDrawCmd, enabling you to issue compute dispatches, bind specific framebuffer targets, or render complex 3D geometry that shares the Z-buffer with the UI.
Handling Large Meshes
Enable ImDrawListFlags_AllowVtxOffset on your draw list to support vertex indices larger than 16-bit without splitting the draw list. This is essential when submitting large procedural meshes that exceed the default index limit. The flag is checked inside PrimReserve to determine buffer allocation strategy.
Key Source Files in the ImGui Repository
Understanding the following files is essential for debugging and extending ImDrawList behavior:
imgui.h– Public API declarations forImDrawList,ImDrawCmd, andImDrawVert.imgui_internal.h– Internal structures likeImDrawListSharedDataand low-level primitive helpers.imgui_draw.cpp– Full implementation ofImDrawList, including primitive generation (AddRect,AddLine), path filling, and command buffering.imgui_demo.cpp– Reference implementations demonstrating draw list usage for grids, polylines, and custom widgets.imgui_impl_opengl3.cpp(or your specific backend) – TranslatesImDrawDatainto GPU draw calls, consuming the buffers produced byImDrawList.
Summary
- ImDrawList records low-level draw commands in
CmdBufferand vertex data inVtxBuffer/IdxBuffer. - Access via
GetWindowDrawList(),GetForegroundDrawList(), orGetBackgroundDrawList()depending on layering needs. - Use
PushClipRect/PopClipRectandPushTexture/PopTextureto manage state without manual command splitting. - Primitives like
AddLine,AddRectFilled, andAddImageroute throughPrimReservefor vertex allocation. - Insert raw GPU code with
AddCallback, or customize vertex formats by redefiningImDrawVertbefore compilation.
Frequently Asked Questions
What is the difference between GetWindowDrawList and GetForegroundDrawList?
GetWindowDrawList() returns the draw list owned by the currently active window, clipped to that window's boundaries and drawn at the window's Z-level. GetForegroundDrawList() returns a global draw list rendered after all windows, making it suitable for overlays, debug graphics, or tooltips that must appear on top of the entire UI.
How do I clip rendering to a specific region using ImDrawList?
Call PushClipRect(ImVec2 min, ImVec2 max, bool intersect_with_current) before your draw commands, then PopClipRect() afterward. As implemented in imgui_draw.cpp, this pushes the rectangle onto an internal stack and encodes the top value into subsequent draw commands, ensuring the GPU scissor test limits rasterization to that region.
Can I use custom shaders with ImDrawList?
Yes. While ImDrawList itself generates standard triangle lists for the default shader, you can inject custom shader binds using AddCallback(). Your callback function receives the draw list and command pointer, allowing you to execute platform-specific code (e.g., glUseProgram) before or after ImGui's geometry.
Where are the vertex buffers stored when using ImDrawList?
Vertex data is stored in ImDrawList::VtxBuffer (array of ImDrawVert) and index data in ImDrawList::IdxBuffer (array of ImDrawIdx). These vectors are populated during the frame and then referenced by the backend renderer (such as ImGui_ImplOpenGL3_RenderDrawData) to upload to GPU memory or map directly into command buffers.
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 →