# How to Implement a Custom Dear ImGui Backend for Non-Standard Rendering APIs

> Learn how to implement a custom Dear ImGui backend for non-standard rendering APIs. Initialize backend data, create shaders, and translate ImGui draw commands into your engine's API calls.

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

---

**To implement a custom Dear ImGui backend, you must initialize backend data in `ImGuiIO`, create shaders and buffers matching the `ImDrawVert` layout, then iterate `ImDrawData` commands in your render loop to translate ImGui's draw lists into your engine's API calls.**

Dear ImGui separates input handling (*platform* backends) from GPU rendering (*renderer* backends). According to the `ocornut/imgui` source code, a custom renderer backend bridges this gap by consuming `ImDrawData` structures and outputting commands compatible with your proprietary engine, software rasterizer, or specialized graphics API.

## Understanding the Dear ImGui Backend Architecture

The architecture centers on the **`ImGuiIO`** structure defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h). This structure exposes three critical fields for renderer backends:

- **`BackendRendererUserData`** – Stores a pointer to your backend-specific data struct (shaders, buffers, textures).
- **`BackendRendererName`** – A string identifier shown in the About window (e.g., `"MyEngine"`).
- **`BackendFlags`** – Capability flags such as `ImGuiBackendFlags_RendererHasVtxOffset` (for 64k+ vertex meshes) or `ImGuiBackendFlags_RendererHasTextures`.

Your backend must implement a consistent function naming convention: `ImGui_Impl<Backend>_Init()`, `ImGui_Impl<Backend>_NewFrame()`, `ImGui_Impl<Backend>_RenderDrawData()`, and `ImGui_Impl<Backend>_Shutdown()`.

## Core Backend Functions and API Structure

As implemented in [`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp), every renderer backend provides four essential functions:

| Function | Purpose | Reference Implementation |
|----------|---------|------------------------|
| `ImGui_Impl<Backend>_Init` | Allocate `BackendRendererUserData`, compile shaders, create vertex/index buffers, upload font texture | [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) (line 216) |
| `ImGui_Impl<Backend>_NewFrame` | Update per-frame state like viewport size and projection matrices | [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) (line 402) |
| `ImGui_Impl<Backend>_RenderDrawData` | Iterate `ImDrawData` and issue draw calls | [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) (line 462) |
| `ImGui_Impl<Backend>_Shutdown` | Free GPU resources and clear `BackendRendererUserData` | [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) (line 229) |

## Step-by-Step Implementation Guide

### Define Backend-Specific Data Structures

Create a struct to hold your engine's resources, then store it in `ImGuiIO`:

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

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

```

### Initialize the Backend and Create Resources

In your `Init` function, perform the following steps as shown in the OpenGL3 reference:

1. **Allocate backend data** and assign it to `io.BackendRendererUserData`.
2. **Create shaders** that match ImGui's vertex format (`ImDrawVert`).
3. **Allocate dynamic buffers** large enough for maximum draw-list size.
4. **Generate and upload the font texture** using `ImGui::GetIO().Fonts->GetTexDataAsRGBA32()`.
5. **Set capability flags**:

```cpp
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererUserData = (void*)IM_NEW(MyBackendData)();
io.BackendRendererName = "MyEngine";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset |
                   ImGuiBackendFlags_RendererHasTextures;

// Upload font texture
unsigned char* pixels; int w, h;
io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h);
// ... upload to GPU and store handle ...
io.Fonts->TexID = (ImTextureID)fontTextureHandle;

```

### Handle Per-Frame Preparation

The `NewFrame` function updates transient state. For basic implementations, this may only require updating the projection matrix to map `io.DisplayPos` and `io.DisplaySize` to your engine's clip space:

```cpp
void ImGui_ImplMyEngine_NewFrame() {
    // Update uniform buffer with orthographic projection
    // based on ImGui::GetIO().DisplaySize
}

```

For multi-viewport support (as demonstrated in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp)), expose render state via `ImGui::GetPlatformIO().Renderer_RenderState`.

### Render Draw Data Commands

The `RenderDrawData` function in [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) demonstrates the iteration pattern:

```cpp
void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data) {
    if (draw_data->TotalVtxCount == 0) return;
    
    MyBackendData* bd = GetBackendData();
    
    // Upload geometry to GPU
    // Map bd->vtxBuf and bd->idxBuf, copy draw_data vertex/index data
    
    // Setup pipeline state
    MyEngine::BindShader(bd->shader);
    MyEngine::SetBlendMode(BlendMode_Alpha);
    
    // Iterate command lists
    int vtxOffset = 0, idxOffset = 0;
    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 (e.g., DrawCallback_ResetRenderState)
            if (pcmd->UserCallback) {
                pcmd->UserCallback(cmd_list, pcmd);
            } else {
                // Bind texture (pcmd->TextureId is your engine's texture handle)
                MyEngine::BindTexture((MyEngine::Texture*)pcmd->TextureId);
                
                // Set scissor rectangle from pcmd->ClipRect
                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 with offsets
                MyEngine::DrawIndexed(
                    pcmd->ElemCount,
                    idxOffset + pcmd->IdxOffset,
                    vtxOffset + pcmd->VtxOffset
                );
            }
        }
        vtxOffset += cmd_list->VtxBuffer.Size;
        idxOffset += cmd_list->IdxBuffer.Size;
    }
}

```

### Cleanup and Shutdown

Follow the pattern in [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) lines 229-244:

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

```

## Example Implementation Skeleton

Here is a complete header and implementation skeleton following the patterns in `ocornut/imgui`:

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

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"

struct MyBackendData {
    MyEngine::Shader*  shader = nullptr;
    MyEngine::Buffer*  vtxBuf = nullptr;
    MyEngine::Buffer*  idxBuf = nullptr;
    MyEngine::Texture* fontTex = nullptr;
};

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(vertex_glsl, fragment_glsl);
    
    // Create dynamic buffers (2MB initial size)
    bd->vtxBuf = MyEngine::CreateBuffer(2*1024*1024, BufferUsage_DynamicVertex);
    bd->idxBuf = MyEngine::CreateBuffer(2*1024*1024, BufferUsage_DynamicIndex);
    
    // Upload font atlas
    unsigned char* pixels; int w, h;
    io.Fonts->GetTexDataAsRGBA32(&pixels, &w, &h);
    bd->fontTex = MyEngine::CreateTexture(w, h, pixels);
    io.Fonts->TexID = (ImTextureID)bd->fontTex;
    
    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset |
                       ImGuiBackendFlags_RendererHasTextures;
    return true;
}

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

void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data) {
    // Implementation as shown in Step 4 above
}

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

```

## Summary

- **Dear ImGui backends** separate into platform (input) and renderer (GPU) concerns, with the renderer translating `ImDrawData` into your API.
- **Four functions** form the backend contract: `Init`, `NewFrame`, `RenderDrawData`, and `Shutdown`, following the naming convention `ImGui_Impl<Backend>_*`.
- **Store state** in a custom struct pointed to by `ImGuiIO::BackendRendererUserData`, and declare capabilities via `BackendFlags`.
- **The render loop** iterates `ImDrawCmd` structures, binding textures and setting scissor rectangles before issuing indexed draw calls with `VtxOffset` and `IdxOffset`.
- **Reference implementations** in [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) and [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) provide battle-tested patterns for buffer management and multi-viewport support.

## Frequently Asked Questions

### What vertex format does Dear ImGui require for custom backends?

Dear ImGui uses the **`ImDrawVert`** structure defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h), which consists of a 2D position (`ImVec2 pos`), texture coordinates (`ImVec2 uv`), and a 32-bit color (`ImU32 col`). Your vertex shader must accept this exact layout to correctly render ImGui draw lists.

### How do I support drawing more than 64,000 vertices in a single draw call?

Set the **`ImGuiBackendFlags_RendererHasVtxOffset`** flag in `ImGuiIO::BackendFlags` during initialization. This tells Dear ImGui that your backend supports 32-bit index offsets via the `ImDrawCmd::VtxOffset` field, allowing meshes larger than 16-bit index limits.

### Can I use Dear ImGui with a software rasterizer instead of a GPU?

Yes. A custom Dear ImGui backend can target any rendering API, including software rasterizers. You would implement `RenderDrawData` to scan-convert the triangles in `ImDrawData` using your CPU rasterizer, sampling from the texture handle provided in `ImDrawCmd::TextureId`.

### Where should I handle DPI scaling and framebuffer size changes?

Handle DPI and framebuffer changes in your **platform backend** (e.g., [`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)), then update `ImGuiIO::DisplayFramebufferScale` and `ImGuiIO::DisplaySize` before calling `NewFrame`. The renderer backend consumes these values to calculate proper projection matrices and scissor rectangles.