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->DisplaySize and draw_data->FramebufferScale to skip rendering if minimized
  • Upload vertex and index buffers to GPU memory
  • Set up orthographic projection (typically using DisplayPos and DisplaySize)
  • Iterate through draw_data->CmdLists and execute each ImDrawCmd:
    • Handle UserCallback if present
    • Set scissor rectangles using ClipRect, DisplayPos, and FramebufferScale
    • Bind the texture from ImDrawCmd::GetTexID()
    • Issue indexed draw calls using ElemCount, IdxOffset, and VtxOffset
  • 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/restore
  • backends/imgui_impl_dx11.cpp: DirectX 11 implementation demonstrating constant buffers and texture management
  • backends/imgui_impl_vulkan.cpp: Vulkan renderer showing descriptor sets, pipeline creation, and command-buffer integration
  • backends/imgui_impl_metal.h / .mm: Metal renderer for macOS/iOS with Objective-C++ bridge and render-pipeline setup
  • examples/: Sample applications demonstrating integration (e.g., example_glfw_opengl3, example_win32_directx12)

Summary

  • Dear ImGui requires four functions: Init, NewFrame, RenderDrawData, and Shutdown to manage the renderer lifecycle
  • Store state in BackendRendererUserData using a custom struct containing your API-specific handles and resources
  • Preserve graphics state: Always save and restore modified state (blend modes, scissor, viewport) in RenderDrawData to avoid corrupting the host application
  • Handle command lists: Iterate ImDrawData::CmdLists, process ImDrawCmd entries, bind textures via GetTexID(), and respect clip rectangles
  • Follow reference files: Use imgui_impl_opengl3.cpp, imgui_impl_dx11.cpp, and imgui_impl_vulkan.cpp as 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:

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 →