How to Implement a Custom Renderer Backend for Dear ImGui: OpenGL, DirectX, Vulkan, and Metal
To implement a custom renderer backend for Dear ImGui, you must provide four functions (Init, NewFrame, RenderDrawData, and Shutdown) that manage GPU resources and hook into the ImGuiIO struct via BackendRendererUserData, following the architecture patterns found in the official imgui_impl_*.cpp reference files.
Dear ImGui (ocornut/imgui) strictly separates platform responsibilities (windowing and input) from rendering responsibilities (GPU draw calls). A custom renderer backend bridges this gap by consuming ImDrawData produced by the core library and translating it into API-specific commands for OpenGL, DirectX, Vulkan, or Metal. This guide explains the exact lifecycle, data structures, and rendering pipeline required to integrate Dear ImGui with any graphics API.
Understanding the Dear ImGui Backend Architecture
Dear ImGui interacts with your code through the ImGuiIO structure, which stores pointers to backend-specific data and function hooks. When implementing a custom renderer, you populate io.BackendRendererUserData with a pointer to your backend-specific state structure and set io.BackendRendererName to identify your implementation. Unlike the platform backend (which handles window events), the renderer backend focuses exclusively on compiling shaders, managing GPU buffers, and issuing draw calls based on the vertex and index data generated by ImGui.
The Four Essential Backend Functions
Every renderer backend must implement four lifecycle functions that Dear ImGui calls each frame.
Initialization (Init)
The Init function stores a pointer to your graphics device or context, detects shader capabilities, and creates persistent GPU resources such as shaders, pipeline states, and samplers. You must allocate a backend data structure (e.g., MyRendererBackendData) and store it in io.BackendRendererUserData. Set io.BackendRendererName (e.g., "imgui_impl_opengl3") to enable debugging and version checking.
New Frame Preparation (NewFrame)
Called before ImGui::NewFrame() each frame, this function handles transient resource creation. If device objects were lost (e.g., due to OpenGL context loss or Vulkan device reset), recreate them here. This is also where you may update the font texture atlas if it has been invalidated.
Rendering Draw Data (RenderDrawData)
This is the core function executed after ImGui::Render(). It receives an ImDrawData* pointer containing all geometry and draw commands for the frame. The implementation must:
- Check framebuffer dimensions via
draw_data->DisplaySizeanddraw_data->FramebufferScaleto skip rendering if minimized - Upload vertex and index buffers to GPU memory
- Set up orthographic projection (typically using
DisplayPosandDisplaySize) - Iterate through
draw_data->CmdListsand execute eachImDrawCmd:- Handle
UserCallbackif present - Set scissor rectangles using
ClipRect,DisplayPos, andFramebufferScale - Bind the texture from
ImDrawCmd::GetTexID() - Issue indexed draw calls using
ElemCount,IdxOffset, andVtxOffset
- Handle
- Restore all modified graphics state (blending, scissor, depth-stencil, etc.) to avoid corrupting the host application's rendering
Cleanup (Shutdown)
The Shutdown function destroys all GPU resources created during Init, deletes the backend data structure using IM_DELETE, and clears io.BackendRendererUserData and io.BackendRendererName to null.
Implementing the Backend Data Structure
Store all persistent renderer state in a dedicated structure to maintain clean separation between Dear ImGui and your graphics API:
// Pattern inspired by backends/imgui_impl_opengl3.cpp
struct MyRendererBackendData {
// API-specific handles (e.g., GLuint, ID3D11Device*, VkDevice, etc.)
void* device;
void* pipelineState;
void* vertexBuffer;
void* indexBuffer;
void* fontTexture;
// ... other resources
};
Access this data through a helper function to ensure type safety:
static MyRendererBackendData* MyBackend_GetData() {
return (MyRendererBackendData*)ImGui::GetIO().BackendRendererUserData;
}
Rendering Draw Data: The RenderDrawData Implementation
The following pattern shows the exact implementation required inside RenderDrawData, based on the reference backends:
void MyRenderer_RenderDrawData(ImDrawData* draw_data) {
MyRendererBackendData* bd = MyBackend_GetData();
// Skip if minimized
int fb_w = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_h = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_w <= 0 || fb_h <= 0) return;
// Upload vertex/index buffers to GPU
UploadBuffers(draw_data);
// Setup render state (blend, scissor, viewport, shader)
SetupRenderState(draw_data, fb_w, fb_h);
// Process command lists
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList* cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback) {
// Allow user to override rendering for specific draw commands
pcmd->UserCallback(const_cast<ImDrawList*>(cmd_list), pcmd);
} else {
// Set scissor rectangle (clip)
SetScissor(pcmd->ClipRect, draw_data->DisplayPos, draw_data->FramebufferScale);
// Bind texture handle stored in ImDrawCmd
BindTexture((MyTextureHandle)pcmd->GetTexID());
// Issue draw call: count, index offset, vertex offset
DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset, pcmd->VtxOffset);
}
}
}
// Restore modified state to avoid interfering with host application
RestoreRenderState();
}
Platform-Specific Implementation Details
Each graphics API requires specific handling for buffer management, state preservation, and shader setup.
OpenGL (backends/imgui_impl_opengl3.cpp)
The OpenGL backend handles multiple GL versions and ES variants via compile-time defines (IMGUI_IMPL_OPENGL_ES2, IMGUI_IMPL_OPENGL_ES3). It saves and restores the full OpenGL state (including polygon mode, viewport, scissor, blend modes, and shader bindings) before and after rendering. Vertex and index data are uploaded using glBufferSubData or mapped buffers.
DirectX 11 (backends/imgui_impl_dx11.cpp)
The DirectX 11 implementation uses a constant buffer to store the orthographic projection matrix (MVP). You must ensure the ID3D11DeviceContext is bound each frame. The backend creates vertex and index buffers with D3D11_USAGE_DYNAMIC and updates them via Map/Unmap. Textures are bound as shader resources using GetTexID().
DirectX 12 (backends/imgui_impl_dx12.cpp)
DirectX 12 requires you to provide a ID3D12GraphicsCommandList each frame. The backend manages descriptor heaps and pipeline state objects. You must supply a command-list allocator and ensure proper synchronization between frames, as ImGui does not manage the command queue directly.
Vulkan (backends/imgui_impl_vulkan.cpp)
The Vulkan backend requires initialization with a VkRenderPass, swap-chain image format, and a VkCommandBuffer for recording. It creates descriptor pools, pipeline layouts, and pipelines during initialization. The RenderDrawData function records commands into the provided command buffer, which your application must then submit to the graphics queue.
Metal (backends/imgui_impl_metal.h/.mm)
The Metal implementation uses MTLRenderPipelineState for the shader pipeline and MTLBuffer for vertex/index data. On iOS, you must ensure the view is backed by a CAMetalLayer. Rendering encodes draw commands into a MTLRenderCommandEncoder provided by the caller.
Complete Integration Example
The following example demonstrates the complete integration pattern for a hypothetical custom renderer:
// Initialization
bool MyRenderer_Init(void* gfxDevice) {
MyRendererBackendData* bd = IM_NEW(MyRendererBackendData);
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "my_custom_renderer";
bd->device = gfxDevice;
if (!CreateDeviceObjects()) return false;
return true;
}
// Per-frame setup
void MyRenderer_NewFrame() {
MyRendererBackendData* bd = MyBackend_GetData();
if (!bd->deviceObjectsCreated)
CreateDeviceObjects();
}
// Shutdown
void MyRenderer_Shutdown() {
MyRendererBackendData* bd = MyBackend_GetData();
DestroyDeviceObjects();
IM_DELETE(bd);
ImGui::GetIO().BackendRendererUserData = nullptr;
ImGui::GetIO().BackendRendererName = nullptr;
}
Simplified usage in your render loop:
// OpenGL example
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// DirectX 11 example
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
// Vulkan example
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), command_buffer);
// Metal example
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandEncoder);
Reference Implementations in the Repository
The Dear ImGui repository provides production-ready reference implementations for all major graphics APIs:
backends/imgui_impl_opengl3.cpp: Reference OpenGL renderer handling shader compilation, buffer uploads, and state backup/restorebackends/imgui_impl_dx11.cpp: DirectX 11 implementation demonstrating constant buffers and texture managementbackends/imgui_impl_vulkan.cpp: Vulkan renderer showing descriptor sets, pipeline creation, and command-buffer integrationbackends/imgui_impl_metal.h/.mm: Metal renderer for macOS/iOS with Objective-C++ bridge and render-pipeline setupexamples/: Sample applications demonstrating integration (e.g.,example_glfw_opengl3,example_win32_directx12)
Summary
- Dear ImGui requires four functions:
Init,NewFrame,RenderDrawData, andShutdownto manage the renderer lifecycle - Store state in
BackendRendererUserDatausing a custom struct containing your API-specific handles and resources - Preserve graphics state: Always save and restore modified state (blend modes, scissor, viewport) in
RenderDrawDatato avoid corrupting the host application - Handle command lists: Iterate
ImDrawData::CmdLists, processImDrawCmdentries, bind textures viaGetTexID(), and respect clip rectangles - Follow reference files: Use
imgui_impl_opengl3.cpp,imgui_impl_dx11.cpp, andimgui_impl_vulkan.cppas authoritative templates for your implementation
Frequently Asked Questions
What is the difference between a platform backend and a renderer backend in Dear ImGui?
A platform backend handles windowing, input devices, and clipboard integration (e.g., GLFW, SDL, Win32), while a renderer backend handles GPU draw calls, shaders, and buffer management. Dear ImGui requires both to display a functional UI, but they are implemented separately to allow mixing any platform with any renderer.
How do I handle texture binding in a custom Dear ImGui renderer?
ImGui stores texture handles in ImDrawCmd::GetTexID(), which returns a ImTextureID (typedef for void*). During RenderDrawData, cast this value to your native texture handle (e.g., GLuint, ID3D11ShaderResourceView*, VkDescriptorSet) and bind it before issuing the draw call for that command.
Why must I restore graphics state after rendering ImGui?
Dear ImGui is designed to be embedded inside existing applications and game engines. If your backend modifies the graphics pipeline state (blending, scissor rectangles, depth-stencil settings, shader bindings) and does not restore it, the host application's subsequent rendering will use incorrect state, causing visual corruption or crashes.
Can I use Dear ImGui with a custom game engine renderer?
Yes. Implement a custom renderer backend by following the four-function pattern (Init, NewFrame, RenderDrawData, Shutdown) in the imgui_impl_*.cpp files. Hook your engine's texture system, shader pipeline, and buffer management into these functions, ensuring you translate ImDrawData into your engine's native draw calls while respecting the clip rectangles and texture IDs provided by ImGui.
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 →