# How to Implement a Custom Dear ImGui Backend for Your Rendering Engine

> Learn to implement a custom Dear ImGui backend by mastering four key functions Init NewFrame RenderDrawData and Shutdown Translate ImGui draw data into your engine's rendering API commands

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-25

---

**A custom Dear ImGui backend requires implementing four core functions—`Init`, `NewFrame`, `RenderDrawData`, and `Shutdown`—that translate ImGui's `ImDrawData` into your engine's rendering API commands.**

Creating a custom backend allows you to integrate Dear ImGui into proprietary engines or unsupported graphics APIs. According to the ocornut/imgui source code, the library strictly separates platform concerns (input, windowing) from renderer concerns (GPU draw calls), enabling you to plug into any rendering pipeline by consuming the generated draw lists.

## Understanding the Backend Architecture

Dear ImGui communicates with your code through the **`ImGuiIO`** structure defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h). This structure acts as the contract between the library and your backend implementation.

Your backend must populate three critical fields in `ImGuiIO`:

- **`BackendRendererUserData`** – A pointer to your backend-specific data structure (shaders, buffers, textures).
- **`BackendRendererName`** – A string identifier displayed in ImGui's *About* window (e.g., "MyEngine").
- **`BackendFlags`** – Capability flags such as `ImGuiBackendFlags_RendererHasVtxOffset` for supporting 64k+ vertex meshes or `ImGuiBackendFlags_RendererHasTextures` for dynamic texture updates.

The architecture ensures isolation: ImGui never directly accesses your engine's resources. Instead, you control all GPU state through the user data pointer, following the pattern established in [`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp).

## Core Backend API Functions

Every custom rendering backend exposes a consistent public API prefixed with `ImGui_Impl<Backend>`. These four functions handle the complete lifecycle:

- **`ImGui_Impl<Backend>_Init`** – Allocates your backend data structure, creates shaders matching `ImDrawVert` layout, uploads the default font texture using `ImGui::GetIO().Fonts->GetTexDataAsRGBA32()`, and sets `BackendRendererUserData`.

- **`ImGui_Impl<Backend>_NewFrame`** – Updates per-frame state such as viewport dimensions and projection matrices based on `io.DisplaySize`.

- **`ImGui_Impl<Backend>_RenderDrawData`** – The heart of the backend. Iterates over `ImDrawData`, binds textures via `ImTextureID` handles, sets clip rectangles from `ImDrawCmd::ClipRect`, and issues draw calls respecting `VtxOffset` and `IdxOffset`.

- **`ImGui_Impl<Backend>_Shutdown`** – Releases all GPU resources (shaders, buffers, textures) and clears `BackendRendererUserData` to prevent dangling pointers.

## Step-by-Step Implementation Guide

### Define Backend Data Structure

Create a private struct to hold all renderer-specific state. Store a pointer to this struct in `io.BackendRendererUserData`, mirroring the helper pattern `ImGui_ImplOpenGL3_GetBackendData()` found in the OpenGL3 reference backend.

```cpp
struct MyBackendData {
    MyEngine::Shader*     shader = nullptr;
    MyEngine::Buffer*     vtxBuf = nullptr;
    MyEngine::Buffer*     idxBuf = nullptr;
    MyEngine::Texture*    fontTex = nullptr;
    int                   bufferSize = 0;  // Track current allocation
};

```

### Initialize the Backend

In your initialization function, create resources and declare capabilities to ImGui:

```cpp
bool ImGui_ImplMyEngine_Init() {
    ImGuiIO& io = ImGui::GetIO();
    MyBackendData* bd = new MyBackendData();
    io.BackendRendererUserData = (void*)bd;
    io.BackendRendererName = "MyEngine";

    // Create shader with vertex layout matching ImDrawVert (pos + uv + color)
    bd->shader = MyEngine::CreateShader(vertex_glsl, fragment_glsl);

    // Allocate dynamic buffers (growable approach recommended)
    bd->vtxBuf = MyEngine::CreateBuffer(2*1024*1024, MyEngine::VertexBuffer);
    bd->idxBuf = MyEngine::CreateBuffer(2*1024*1024, MyEngine::IndexBuffer);

    // Upload font atlas
    unsigned char* pixels;
    int width, height;
    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
    bd->fontTex = MyEngine::CreateTexture(width, height, pixels);
    io.Fonts->TexID = (ImTextureID)bd->fontTex;

    // Declare supported features
    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset |
                       ImGuiBackendFlags_RendererHasTextures;
    return true;
}

```

### Handle Per-Frame Preparation

The `NewFrame` function prepares rendering state. For single-viewport applications, this may only require updating a uniform buffer with the orthographic projection matrix mapping `io.DisplayPos` and `io.DisplaySize` to clip space.

If supporting multi-viewport (multiple ImGui windows outside the main application window), expose a render state pointer via `ImGui::GetPlatformIO().Renderer_RenderState`, as demonstrated in [`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp).

### Render the Draw Data

Process the command lists generated by ImGui:

```cpp
void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data) {
    if (draw_data->TotalVtxCount == 0) return;
    
    MyBackendData* bd = (MyBackendData*)ImGui::GetIO().BackendRendererUserData;
    
    // Upload vertex/index data to GPU
    MyEngine::UpdateBuffer(bd->vtxBuf, draw_data->VtxBuffer.Data, 
                          draw_data->VtxBuffer.Size * sizeof(ImDrawVert));
    MyEngine::UpdateBuffer(bd->idxBuf, draw_data->IdxBuffer.Data,
                          draw_data->IdxBuffer.Size * sizeof(ImDrawIdx));

    // Setup render pipeline state
    MyEngine::SetBlendMode(MyEngine::BlendAlpha);
    MyEngine::DisableDepthTest();
    MyEngine::BindShader(bd->shader);
    MyEngine::BindVertexBuffer(bd->vtxBuf);
    MyEngine::BindIndexBuffer(bd->idxBuf);

    // Process command lists
    int vtxOffset = 0, idxOffset = 0;
    for (int i = 0; i < draw_data->CmdListsCount; i++) {
        const ImDrawList* cmdList = draw_data->CmdLists[i];
        
        for (int cmd_i = 0; cmd_i < cmdList->CmdBuffer.Size; cmd_i++) {
            const ImDrawCmd* pcmd = &cmdList->CmdBuffer[cmd_i];
            
            if (pcmd->UserCallback) {
                // Handle custom callbacks (e.g., DrawCallback_ResetRenderState)
                pcmd->UserCallback(cmdList, pcmd);
            } else {
                // Bind texture ID (cast back to your engine's texture handle)
                MyEngine::BindTexture((MyEngine::Texture*)pcmd->TextureId);
                
                // Set scissor rectangle (ClipRect is x1,y1,x2,y2 in screen coordinates)
                MyEngine::SetScissor(
                    (int)pcmd->ClipRect.x, 
                    (int)pcmd->ClipRect.y,
                    (int)(pcmd->ClipRect.z - pcmd->ClipRect.x),
                    (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)
                );
                
                // Issue draw call
                MyEngine::DrawIndexed(
                    pcmd->ElemCount,
                    idxOffset + pcmd->IdxOffset,
                    vtxOffset + pcmd->VtxOffset
                );
            }
        }
        vtxOffset += cmdList->VtxBuffer.Size;
        idxOffset += cmdList->IdxBuffer.Size;
    }
}

```

### Shutdown and Cleanup

Properly release all allocated resources:

```cpp
void ImGui_ImplMyEngine_Shutdown() {
    MyBackendData* bd = (MyBackendData*)ImGui::GetIO().BackendRendererUserData;
    if (!bd) return;
    
    MyEngine::DestroyShader(bd->shader);
    MyEngine::DestroyBuffer(bd->vtxBuf);
    MyEngine::DestroyBuffer(bd->idxBuf);
    MyEngine::DestroyTexture(bd->fontTex);
    
    delete bd;
    ImGui::GetIO().BackendRendererUserData = nullptr;
}

```

## Reference Implementation Files

When implementing your custom Dear ImGui backend, consult these official reference files from the ocornut/imgui repository:

- **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** – Contains `ImGuiIO` structure, `ImGuiBackendFlags` definitions, and `ImDrawData` structures.
- **[`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)** – Complete reference showing initialization, `RenderDrawData` loop, font texture creation, and buffer management.
- **[`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp)** – Demonstrates handling of `Renderer_RenderState` for multi-viewport support and advanced synchronization patterns.
- **[`backends/imgui_impl_win32.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_win32.cpp)** – Example platform backend showing input handling, though you only need this if implementing a platform layer rather than just a renderer.

## Summary

- Dear ImGui backends bridge the gap between ImGui's immediate mode API and your engine's retained mode GPU API by translating `ImDrawData` into native draw calls.
- **Four functions** form the complete backend API: `Init`, `NewFrame`, `RenderDrawData`, and `Shutdown`.
- Store all state in a private struct pointed to by `io.BackendRendererUserData` to maintain clean separation of concerns.
- Always set `BackendFlags` to declare capabilities like `RendererHasVtxOffset` for large meshes.
- Handle `ImDrawCmd::UserCallback` to support special reset commands, and respect `ClipRect` for proper widget clipping.

## Frequently Asked Questions

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

Dear ImGui separates windowing/input from GPU rendering. A **platform backend** (like [`imgui_impl_win32.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_win32.cpp) or [`imgui_impl_glfw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_glfw.cpp)) handles OS window creation, mouse/keyboard input, and clipboard access, feeding data into `ImGuiIO`. A **renderer backend** (like [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)) only handles translating `ImDrawData` into graphics API calls. You can mix any platform backend with any renderer backend.

### How do I handle texture binding in a custom Dear ImGui backend?

ImGui uses `ImTextureID` (a `void*` typedef) to reference textures. In your `ImGui_Impl<Backend>_Init`, upload the font atlas via `GetTexDataAsRGBA32()` and store your engine's texture handle in `io.Fonts->TexID`. During `RenderDrawData`, cast `pcmd->TextureId` back to your native texture handle type. For user-loaded textures, call `ImGui::Image((ImTextureID)myTexture, ...)` and your backend will receive that same pointer in the draw command.

### What vertex format does ImGui use for ImDrawData?

The vertex structure is `ImDrawVert`, defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), containing a 2D position (`ImVec2`), texture coordinates (`ImVec2`), and a 32-bit color (`ImU32` in RGBA format). Your shader must match this exact layout. Indices are either 16-bit (`ImDrawIdx` defaults to `unsigned short`) or 32-bit if you define `ImDrawIdx` as `unsigned int` before including ImGui headers.

### How do I support multi-viewport rendering in my custom backend?

Multi-viewport requires your backend to render ImGui windows into separate OS windows or framebuffers. Enable the feature by setting `io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable` and `io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports`. You must then handle `ImGuiPlatformIO` callbacks or, for simpler integration, use the `Renderer_RenderState` pointer in `ImGuiPlatformIO` to pass per-viewport render state, following the pattern in [`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp) at lines referencing `Renderer_RenderState`.