# How to Create a Custom Renderer Backend for Dear ImGui: A Complete Implementation Guide

> Learn to create a custom Dear ImGui renderer backend. Implement the Init NewFrame RenderDrawData and Shutdown functions to integrate ImGui with your graphics API for powerful UI rendering.

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

---

**To create a custom Dear ImGui renderer backend, implement the four-function API (`Init`, `NewFrame`, `RenderDrawData`, `Shutdown`) that translates `ImDrawData` into your engine's graphics API calls while storing persistent state in `ImGuiIO::BackendRendererUserData`.**

Dear ImGui separates platform concerns (input, clipboard, OS integration) from renderer concerns (GPU draw calls). A custom rendering backend plugs into the renderer side of the `ocornut/imgui` repository and is responsible for converting ImGui's draw-lists into the API of your target engine, whether Vulkan, DirectX, Metal, or a custom software rasterizer.

## Understanding the Renderer Backend Architecture

The core architecture defining backend behavior lives in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** through the `ImGuiIO` structure. This structure acts as the contract between your application and Dear ImGui.

### The ImGuiIO Structure and Backend Flags

Your backend must populate specific fields in `ImGuiIO` to register capabilities and store state:

- **`BackendRendererUserData`** – Holds a pointer to your backend-specific data block (shaders, buffers, textures).
- **`BackendRendererName`** – Identifies your backend in the *About* window and debugging tools.
- **`BackendFlags`** – Declares supported features using bitflags like `ImGuiBackendFlags_RendererHasVtxOffset` for 64k+ vertex meshes or `ImGuiBackendFlags_RendererHasTextures` for dynamic texture updates.

### The Four-Function API Contract

Every renderer backend implements a consistent public API prefixed with `ImGui_Impl<Backend>`:

1. **`ImGui_Impl<Backend>_Init`** – Allocate backend data, create shaders, vertex/index buffers, and set `BackendRendererUserData`. See the reference implementation in [[`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp#L216).

2. **`ImGui_Impl<Backend>_NewFrame`** – Update per-frame state such as viewport size and projection matrix. Reference: [`ImGui_ImplOpenGL3_NewFrame`](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp#L402).

3. **`ImGui_Impl<Backend>_RenderDrawData`** – Iterate over `ImDrawData` and issue rendering commands to your GPU API. Reference: [`ImGui_ImplOpenGL3_RenderDrawData`](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp#L462).

4. **`ImGui_Impl<Backend>_Shutdown`** – Free all GPU resources and clear `BackendRendererUserData`. Reference: [`ImGui_ImplOpenGL3_Shutdown`](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp#L229).

## Step-by-Step Implementation

Follow this structured approach to build your backend, mirroring patterns from the official reference implementations.

### Step 1: Define Your Backend Data Structure

Create a struct to hold all renderer-specific state. Store a pointer to this struct in `io.BackendRendererUserData`, following the pattern used by `ImGui_ImplOpenGL3_GetBackendData()` in the OpenGL3 backend.

```cpp
struct MyBackendData {
    MyEngine::Shader*  shader = nullptr;
    MyEngine::Buffer*  vtxBuf = nullptr;
    MyEngine::Buffer*  idxBuf = nullptr;
    MyEngine::Texture* fontTex = nullptr;
    // Add any other engine-specific state
};

```

### Step 2: Initialize the Backend and Upload Resources

Your `Init` function must create GPU resources and configure ImGui's expectations:

- **Create shaders** matching the `ImDrawVert` vertex format (position, UV, color).
- **Allocate dynamic buffers** large enough for maximum draw-list size, or implement growth logic.
- **Generate the font texture** using `ImGui::GetIO().Fonts->GetTexDataAsRGBA32()` and upload to GPU.
- **Set backend flags** to declare capabilities:
  ```cpp
  io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset |
                     ImGuiBackendFlags_RendererHasTextures;
  io.BackendRendererName = "MyEngine";
  ```

### Step 3: Prepare Per-Frame State in NewFrame

In `ImGui_Impl<Backend>_NewFrame`, update the projection matrix to map ImGui's `DisplayPos` and `DisplaySize` to your engine's clip space. If supporting multi-viewport, expose render state via `ImGui::GetPlatformIO().Renderer_RenderState` as demonstrated in the Vulkan backend ([`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp)).

### Step 4: Render Draw Lists and Commands

Call `ImGui::GetDrawData()` to obtain `ImDrawData*`, then iterate through command lists:

1. **Upload geometry** – Copy `ImDrawVert` and `ImDrawIdx` data to your GPU buffers.
2. **Bind resources** – Set shader, vertex buffer, and index buffer.
3. **Process commands** – For each `ImDrawCmd`:
   - Bind texture using `pcmd->TextureId` (cast to your engine's handle type).
   - Set scissor rectangle using `pcmd->ClipRect`.
   - Issue draw call with `pcmd->ElemCount`, respecting `pcmd->VtxOffset` and `pcmd->IdxOffset`.
   - Handle `pcmd->UserCallback` for custom render-state resets (see `DrawCallback_ResetRenderState` handling in the OpenGL3 backend).

### Step 5: Shutdown and Resource Cleanup

Your `Shutdown` function must:
- Delete shaders, buffers, and textures.
- Clear `io.BackendRendererUserData` to null.

## Complete Code Example for a Custom Engine

This skeleton implements the four-function API for a hypothetical graphics engine, following patterns from [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp):

```cpp
// my_imgui_backend.h
#pragma once
#include "imgui.h"

struct MyBackendData;

bool ImGui_ImplMyEngine_Init();
void ImGui_ImplMyEngine_NewFrame();
void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data);
void ImGui_ImplMyEngine_Shutdown();

```

```cpp
// my_imgui_backend.cpp
#include "my_imgui_backend.h"
#include "my_engine.h"

static MyBackendData* GetBackendData() {
    return (MyBackendData*)ImGui::GetIO().BackendRendererUserData;
}

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

    // Create shader matching ImDrawVert layout
    bd->shader = MyEngine::CreateShader({
        .vertSource = vertex_glsl,
        .fragSource = fragment_glsl
    });

    // Create dynamic buffers (2MB each)
    bd->vtxBuf = MyEngine::CreateBuffer({
        .size = 2*1024*1024,
        .usage = DynamicVertex
    });
    bd->idxBuf = MyEngine::CreateBuffer({
        .size = 2*1024*1024,
        .usage = DynamicIndex
    });

    // Upload font texture
    unsigned char* texPixels;
    int texW, texH;
    io.Fonts->GetTexDataAsRGBA32(&texPixels, &texW, &texH);
    bd->fontTex = MyEngine::CreateTexture({
        .width = texW,
        .height = texH,
        .data = texPixels
    });
    io.Fonts->TexID = (ImTextureID)bd->fontTex;

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

void ImGui_ImplMyEngine_NewFrame() {
    // Update projection matrix based on io.DisplaySize if needed
}

void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data) {
    if (draw_data->TotalVtxCount == 0) return;
    
    MyBackendData* bd = GetBackendData();
    
    // Upload geometry data to GPU
    // ... map and copy ImDrawVert/ImDrawIdx data ...
    
    // Setup render state
    MyEngine::BindShader(bd->shader);
    MyEngine::BindVertexBuffer(bd->vtxBuf);
    MyEngine::BindIndexBuffer(bd->idxBuf);
    
    // Iterate draw lists
    int vtxOffset = 0, idxOffset = 0;
    for (int n = 0; n < draw_data->CmdListsCount; n++) {
        const ImDrawList* cmdList = draw_data->CmdLists[n];
        for (int cmd_i = 0; cmd_i < cmdList->CmdBuffer.Size; cmd_i++) {
            const ImDrawCmd* pcmd = &cmdList->CmdBuffer[cmd_i];
            
            // Bind texture and set scissor
            MyEngine::BindTexture((MyEngine::Texture*)pcmd->TextureId);
            ImVec4 clip = pcmd->ClipRect;
            MyEngine::SetScissor(
                (int)clip.x, (int)clip.y,
                (int)(clip.z - clip.x), (int)(clip.w - clip.y)
            );
            
            // Draw
            MyEngine::DrawIndexed(
                pcmd->ElemCount,
                idxOffset + pcmd->IdxOffset,
                vtxOffset + pcmd->VtxOffset
            );
        }
        vtxOffset += cmdList->VtxBuffer.Size;
        idxOffset += cmdList->IdxBuffer.Size;
    }
}

void ImGui_ImplMyEngine_Shutdown() {
    MyBackendData* bd = GetBackendData();
    MyEngine::DestroyShader(bd->shader);
    MyEngine::DestroyBuffer(bd->vtxBuf);
    MyEngine::DestroyBuffer(bd->idxBuf);
    MyEngine::DestroyTexture(bd->fontTex);
    IM_DELETE(bd);
    ImGui::GetIO().BackendRendererUserData = nullptr;
}

```

## Key Implementation Details from Reference Backends

Study these official backend files in the `ocornut/imgui` repository to handle edge cases:

- **[`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)** – Shows complete initialization, vertex layout handling, and `DrawCallback_ResetRenderState` processing.
- **[`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp)** – Demonstrates `Renderer_RenderState` management for multi-viewport support and complex resource binding.
- **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** – Contains `ImGuiIO` structure definitions, backend flags, and `ImGuiPlatformIO` for advanced viewport handling.

## Summary

- **Backend State** – Store all renderer data in a custom struct pointed to by `ImGuiIO::BackendRendererUserData` to maintain isolation between ImGui and your engine.
- **Four Functions** – Implement `Init`, `NewFrame`, `RenderDrawData`, and `Shutdown` to satisfy the backend contract.
- **Flag Declaration** – Set `ImGuiBackendFlags_RendererHasVtxOffset` and `ImGuiBackendFlags_RendererHasTextures` in `ImGuiIO::BackendFlags` to enable advanced features.
- **Draw Data Iteration** – Loop through `ImDrawData` → `ImDrawList` → `ImDrawCmd`, binding textures and setting scissor rectangles before each draw call.
- **Resource Management** – Create shaders and buffers during `Init`, upload geometry each frame in `RenderDrawData`, and destroy all resources in `Shutdown`.

## Frequently Asked Questions

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

A **platform backend** handles windowing, input events, clipboard access, and DPI scaling (e.g., [`imgui_impl_win32.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_win32.cpp) or [`imgui_impl_sdl.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_sdl.cpp)). A **renderer backend** handles GPU draw calls by translating `ImDrawData` into your graphics API commands. You need both to run Dear ImGui, but they are completely separate concerns that communicate only through the `ImGuiIO` structure.

### How do I handle large meshes with more than 64k vertices?

Set the `ImGuiBackendFlags_RendererHasVtxOffset` flag in `ImGuiIO::BackendFlags` during initialization. This tells Dear ImGui that your backend supports 32-bit indices and vertex offsetting. When rendering, use `pcmd->VtxOffset` to offset into your vertex buffer, allowing meshes to exceed the 16-bit index limit.

### Can I support multiple viewports with a custom renderer?

Yes. When `ImGuiConfigFlags_ViewportsEnable` is set, Dear ImGui creates multiple `ImDrawData` instances (one per viewport). Your `RenderDrawData` function will be called for each viewport's draw data. For shared per-frame render state across viewports, store a `Renderer_RenderState` pointer in `ImGuiPlatformIO` (see [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) for implementation details).

### How do I update the font texture dynamically?

If you set `ImGuiBackendFlags_RendererHasTextures`, Dear ImGui will use `ImTextureID` to reference textures. To update the font atlas at runtime, call `ImGui::GetIO().Fonts->Build()` to regenerate the atlas data, then retrieve the new pixels with `GetTexDataAsRGBA32()` and re-upload to your GPU texture. Update the `TexID` field with your new texture handle.