How to Implement a Custom Rendering Backend for Dear ImGui

To implement a custom rendering backend for ImGui, you must expose four core functions (Init, Shutdown, NewFrame, RenderDrawData), store GPU resources in a data structure attached to io.BackendRendererUserData, and translate ImDrawData into your graphics API's draw calls while preserving and restoring the previous graphics state.

Dear ImGui (ocornut/imgui) separates platform and renderer responsibilities into distinct backend modules, allowing integration with any graphics API. When you implement a custom rendering backend for ImGui, you provide the bridge between high-level draw lists and GPU command submission. This guide follows the exact patterns found in backends/imgui_impl_opengl3.cpp and the contract defined in docs/BACKENDS.md.

The Renderer Backend Contract

According to the ocornut/imgui source code, a renderer backend must expose a specific ABI that ImGui calls during the frame lifecycle. The reference OpenGL 3 implementation in backends/imgui_impl_opengl3.cpp demonstrates the required function signatures and behavior.

Function Purpose Reference in imgui_impl_opengl3.cpp
ImGui_ImplOpenGL3_Init Initialize the backend, load GL loader, compile shaders Lines 31-33
ImGui_ImplOpenGL3_NewFrame Per-frame setup, ensure device objects exist Lines 24-33
ImGui_ImplOpenGL3_RenderDrawData Core rendering routine, uploads buffers, issues draw calls Lines 55-56
ImGui_ImplOpenGL3_Shutdown Release all GPU resources Lines 16-24

The backend API is declared in imgui.h through ImGuiIO::BackendRendererUserData and documented in docs/BACKENDS.md. Your implementation must create and manage GPU resources (shaders, buffers, textures) and report capabilities through ImGuiIO::BackendRendererFlags (e.g., ImGuiBackendFlags_RendererHasVtxOffset).

Step-by-Step Implementation Guide

1. Define the Backend Data Structure

Create a structure to hold shader handles, buffer IDs, and capability flags. Store a pointer to this structure in io.BackendRendererUserData so ImGui can retrieve it during callbacks.

// imgui_impl_myapi.h
struct ImGui_ImplMyAPI_Data {
    MyShaderHandle  Shader;
    MyBufferHandle  Vbo, Ibo;
    bool            HasVtxOffset;   // Advertise via ImGuiBackendFlags_RendererHasVtxOffset
    
    ImGui_ImplMyAPI_Data() { memset(this, 0, sizeof(*this)); }
};

IMGUI_API bool ImGui_ImplMyAPI_Init();
IMGUI_API void ImGui_ImplMyAPI_Shutdown();
IMGUI_API void ImGui_ImplMyAPI_NewFrame();
IMGUI_API void ImGui_ImplMyAPI_RenderDrawData(ImDrawData* draw_data);

2. Initialize GPU Resources

The Init function creates shaders, generates buffers, and advertises renderer capabilities. Attach your data structure to io.BackendRendererUserData so ImGui can access it throughout the application lifetime.

bool ImGui_ImplMyAPI_Init()
{
    ImGuiIO& io = ImGui::GetIO();
    ImGui_ImplMyAPI_Data* bd = IM_NEW(ImGui_ImplMyAPI_Data)();
    io.BackendRendererUserData = (void*)bd;
    io.BackendRendererName = "myapi";

    // Compile shaders (implement MyCreateShader for your API)
    bd->Shader = MyCreateShader(vertex_src, fragment_src);
    bd->Vbo = MyCreateBuffer();
    bd->Ibo = MyCreateBuffer();

    // Advertise support for base vertex offsets if available
    io.BackendRendererFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
    return bd->Shader != nullptr;
}

3. Handle New Frames

NewFrame is called each frame before any ImGui API usage. Ensure device objects are valid and recreate them if they were lost (e.g., due to device reset).

void ImGui_ImplMyAPI_NewFrame()
{
    ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)ImGui::GetIO().BackendRendererUserData;
    if (!bd->Shader)   // Lost device scenario
        ImGui_ImplMyAPI_Init();
}

4. Render Draw Data

The RenderDrawData function translates ImDrawData into your graphics API commands. As implemented in backends/imgui_impl_opengl3.cpp, you must backup the current graphics state, upload vertex and index buffers, iterate over command lists, then restore the previous state.

void ImGui_ImplMyAPI_RenderDrawData(ImDrawData* draw_data)
{
    if (!draw_data) return;
    ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)ImGui::GetIO().BackendRendererUserData;

    // Backup current graphics state (viewport, blend mode, active texture, etc.)
    MyStateBackup backup = MyCaptureState();

    // Upload vertex and index buffers
    size_t vtx_size = draw_data->TotalVtxCount * sizeof(ImDrawVert);
    size_t idx_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx);
    MyUpdateBuffer(bd->Vbo, draw_data->VtxBuffer.Data, vtx_size);
    MyUpdateBuffer(bd->Ibo, draw_data->IdxBuffer.Data, idx_size);

    // Process each command list
    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];
            
            // Handle user callbacks
            if (pcmd->UserCallback)
            {
                pcmd->UserCallback(cmd_list, pcmd);
                continue;
            }
            
            // Set scissor rectangle and bind texture
            MySetScissor(pcmd->ClipRect);
            MyBindTexture((MyTexture)pcmd->GetTexID());
            
            // Issue draw call with base vertex offset if supported
            if (bd->HasVtxOffset && pcmd->VtxOffset)
                MyDrawElementsBaseVertex(pcmd->ElemCount,
                                        (pcmd->IdxOffset * sizeof(ImDrawIdx)),
                                        pcmd->VtxOffset);
            else
                MyDrawElements(pcmd->ElemCount,
                              (pcmd->IdxOffset * sizeof(ImDrawIdx)));
        }
    }

    // Restore previous graphics state to avoid side effects
    MyRestoreState(backup);
}

5. Release Resources on Shutdown

Shutdown releases all GPU resources and frees the backend data structure. Set io.BackendRendererUserData to nullptr to indicate the backend is inactive.

void ImGui_ImplMyAPI_Shutdown()
{
    ImGuiIO& io = ImGui::GetIO();
    ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)io.BackendRendererUserData;
    if (!bd) return;
    
    MyDeleteShader(bd->Shader);
    MyDeleteBuffer(bd->Vbo);
    MyDeleteBuffer(bd->Ibo);
    IM_DELETE(bd);
    io.BackendRendererUserData = nullptr;
}

Integrating with Your Application

Include your backend header and call the lifecycle functions in your main loop. This pattern mirrors the examples in the examples/ folder of ocornut/imgui.

#include "imgui.h"
#include "backends/imgui_impl_myapi.h"

int main()
{
    MyGraphicsDeviceInit();               // Your platform-specific initialization
    ImGui::CreateContext();
    ImGui_ImplMyAPI_Init();               // Initialize the renderer backend
    
    while (!MyWindowShouldClose())
    {
        ImGui_ImplMyAPI_NewFrame();       // Prepare backend for new frame
        ImGui::NewFrame();
        
        // Your ImGui UI code here
        
        ImGui::Render();
        ImGui_ImplMyAPI_RenderDrawData(ImGui::GetDrawData());
        MySwapBuffers();
    }
    
    ImGui_ImplMyAPI_Shutdown();
    MyGraphicsDeviceShutdown();
    return 0;
}

Summary

  • Expose four required functions: Implement Init, Shutdown, NewFrame, and RenderDrawData with signatures matching the backend contract in imgui.h.
  • Store resources in BackendRendererUserData: Create a custom structure (like ImGui_ImplOpenGL3_Data) holding shaders, buffers, and flags, and attach it to ImGuiIO.
  • Translate ImDrawData correctly: Upload VtxBuffer and IdxBuffer, iterate over ImDrawCmd entries, handle user callbacks, set scissor rectangles, and issue textured draw calls.
  • Preserve graphics state: Backup the entire relevant GPU state at the start of RenderDrawData and restore it before returning to avoid corrupting the host application's rendering.
  • Advertise capabilities: Set ImGuiBackendFlags_RendererHasVtxOffset if your API supports base-vertex offsets, enabling ImGui to render large meshes efficiently.

Frequently Asked Questions

What is the difference between a platform backend and a renderer backend?

The platform backend handles window creation, input polling, and time-keeping for the host operating system (e.g., SDL2, GLFW), while the renderer backend translates ImGui's draw lists into GPU commands for a specific graphics API (OpenGL, Vulkan, DirectX). These are separate concerns in ocornut/imgui's architecture, allowing you to mix any platform backend with any renderer backend according to docs/BACKENDS.md.

How do I handle textures in a custom ImGui rendering backend?

ImGui uses ImTextureID as an opaque handle. Cast your native texture type to ImTextureID (typically via (ImTextureID)(intptr_t)my_texture) when binding in RenderDrawData. In the draw loop, retrieve the texture using pcmd->GetTexID() and cast it back to your API's texture handle before binding. This pattern is used throughout imgui_impl_opengl3.cpp for font atlases and user images.

Do I need to support vertex offsets in my renderer?

You should support vertex offsets if your graphics API provides base-vertex draw functions (e.g., glDrawElementsBaseVertex or Vulkan's firstVertex). Advertise this capability by setting ImGuiBackendFlags_RendererHasVtxOffset in io.BackendRendererFlags. This allows Dear ImGui to render large meshes exceeding 16-bit index limits by splitting them into separate draw lists with byte offsets.

Why must I backup and restore graphics state when rendering ImGui?

The renderer backend must preserve the host application's graphics state—active textures, blend modes, viewport, scissor rectangles, and shader programs—to avoid side effects. As demonstrated in backends/imgui_impl_opengl3.cpp, capture the entire relevant state at the start of RenderDrawData and restore it before returning, ensuring your UI rendering does not interfere with the application's main rendering pipeline.

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 →