# Implementing a Custom Rendering Backend for Dear ImGui with Different Graphics APIs

> Implement a custom rendering backend for Dear ImGui. Learn to translate ImDrawData into API-specific draw calls via Init NewFrame RenderDrawData and Shutdown functions storing backend state in ImGuiIO::BackendRendererUserData.

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

---

**To implement a custom rendering backend for Dear ImGui, you must translate `ImDrawData` into API-specific draw calls through a standardized lifecycle of `Init`, `NewFrame`, `RenderDrawData`, and `Shutdown` functions, storing your backend state in `ImGuiIO::BackendRendererUserData`.**

Dear ImGui (ocornut/imgui) maintains strict separation between its core library and platform-specific code. The core engine in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) and [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) generates geometry and draw commands, but delegates all window management, input handling, and graphics API interaction to backend implementations located in the `backends/` directory. This architecture allows you to create custom rendering backends for OpenGL, Vulkan, DirectX, or any proprietary graphics API by implementing six standardized lifecycle functions.

## Backend Architecture and Lifecycle

A renderer backend’s primary responsibility is consuming `ImDrawData` structures and emitting API-specific draw calls. The typical lifecycle consists of the following functions, as demonstrated in the OpenGL3 implementation:

- **`ImGui_ImplXxx_Init()`** – Detect the graphics API version, create shaders or programs, and allocate persistent objects like vertex array objects (VAO), buffers, and samplers.
- **`ImGui_ImplXxx_NewFrame()`** – Called each frame before `ImGui::NewFrame()`. Ensures device objects exist and are ready for use.
- **`ImGui_ImplXxx_RenderDrawData(ImDrawData* draw_data)`** – The core rendering function that iterates over `draw_data->CmdLists`, sets rendering state, binds textures, and issues draw commands.
- **`ImGui_ImplXxx_CreateDeviceObjects()` / `ImGui_ImplXxx_DestroyDeviceObjects()`** – Recreate or free GPU resources when the device is lost or reset.
- **`ImGui_ImplXxx_Shutdown()`** – Clean up all resources and clear `BackendRendererUserData`.

You can see concrete implementations of these functions in [[`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp), [[`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp)](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_vulkan.cpp), and [[`backends/imgui_impl_dx12.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_dx12.cpp)](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_dx12.cpp).

## Registering Your Backend with ImGui

To integrate a custom backend, you must register it with the ImGui context and declare its capabilities through four specific steps:

1. **Store the backend pointer** – After allocating your backend data structure, store it in `ImGuiIO::BackendRendererUserData`:

```cpp
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererUserData = (void*)my_backend_data;
io.BackendRendererName = "MyCustomBackend";

```

2. **Declare capabilities** – Set flags in `io.BackendFlags` to tell ImGui which features you support:

```cpp
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // Support for 64k+ vertices
io.BackendFlags |= ImGuiBackendFlags_RendererHasTexture;   // Dynamic font texture updates

```

3. **Establish the frame loop** – Call your backend functions in the correct sequence:

```cpp
MyBackendNewFrame();          // Optional per-frame preparation
ImGui::NewFrame();
// ... build UI ...
ImGui::Render();
MyBackendRenderDrawData(ImGui::GetDrawData());

```

4. **Implement texture handlers** – Provide `UpdateTexture` and `DestroyTexture` functions to handle `ImTextureID` resources. The OpenGL backend demonstrates this pattern in `ImGui_ImplOpenGL3_UpdateTexture()`.

## Rendering Implementation Details

The `RenderDrawData` function is where API-specific code translates ImGui’s high-level draw lists into actual GPU commands. According to the OpenGL3 backend in [`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp), you must implement four critical behaviors:

**State backup and restore** – Save the current graphics pipeline state (shaders, blend modes, viewport, scissor rectangles) before rendering, then restore everything afterward. This ensures ImGui renders correctly without side effects to your existing pipeline.

**Shader version selection** – At runtime, detect the API version (e.g., parsing `bd->GlslVersionString` in OpenGL) and select appropriate vertex and fragment shaders. Apply similar logic for SPIR-V in Vulkan or HLSL in DirectX.

**Large mesh support** – If your API supports base vertex offsets (like `glDrawElementsBaseVertex` in OpenGL 3.2+), set `ImGuiBackendFlags_RendererHasVtxOffset` to enable rendering of meshes exceeding 65,536 vertices.

**Sampler management** – When `glBindSampler` is available, create linear and nearest samplers once, then rebind them through ImGui’s platform-IO callbacks (`DrawCallback_SetSamplerLinear/Nearest`).

## Complete Skeleton Implementation

Here is a minimal implementation template for a hypothetical graphics API:

```cpp
// my_backend.cpp – implements a renderer backend for MyAPI
#include "imgui.h"

struct MyBackendData
{
    MyDevice*   Device;
    MyPipeline* Pipeline;
};

static MyBackendData* GetBackendData()
{
    ImGuiIO& io = ImGui::GetIO();
    return (MyBackendData*)io.BackendRendererUserData;
}

bool MyBackend_Init(MyDevice* device)
{
    MyBackendData* bd = IM_NEW(MyBackendData);
    bd->Device = device;
    
    ImGuiIO& io = ImGui::GetIO();
    io.BackendRendererUserData = (void*)bd;
    io.BackendRendererName = "MyBackend";
    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
    io.BackendFlags |= ImGuiBackendFlags_RendererHasTexture;
    return true;
}

void MyBackend_NewFrame()
{
    GetBackendData(); // Ensure data exists
}

void MyBackend_RenderDrawData(ImDrawData* draw_data)
{
    MyBackendData* bd = GetBackendData();
    if (!bd) return;

    // 1. Backup current API state
    // 2. Setup viewport, scissor, blend, bind pipeline
    
    for (int n = 0; n < draw_data->CmdListsCount; n++)
    {
        const ImDrawList* cmd_list = draw_data->CmdLists[n];
        // Upload vertex/index buffers
        // Bind texture via ImTextureID
        // Issue draw call (vkCmdDrawIndexed, glDrawElements, etc.)
    }
    
    // 4. Restore original state
}

bool MyBackend_CreateTexture(ImTextureID* out_id, int w, int h, const void* pixels)
{
    // Allocate texture, upload pixels, store handle
    return true;
}

void MyBackend_DestroyTexture(ImTextureID tex_id)
{
    // Free GPU texture
}

void MyBackend_Shutdown()
{
    MyBackendData* bd = GetBackendData();
    if (!bd) return;
    IM_DELETE(bd);
    ImGui::GetIO().BackendRendererUserData = nullptr;
}

```

## Platform Integration Example

Wire your custom backend into the application main loop as shown in [`examples/example_glfw_opengl3/main.cpp`](https://github.com/ocornut/imgui/blob/main/examples/example_glfw_opengl3/main.cpp):

```cpp
int main()
{
    // Initialize platform and graphics device
    MyDevice* device = MyDevice_Create();
    
    ImGui::CreateContext();
    MyBackend_Init(device);
    
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        MyBackend_NewFrame();
        ImGui::NewFrame();
        
        ImGui::Begin("Demo");
        ImGui::Text("Hello from MyBackend!");
        ImGui::End();
        
        ImGui::Render();
        MyBackend_RenderDrawData(ImGui::GetDrawData());
        glfwSwapBuffers(window);
    }
    
    MyBackend_Shutdown();
    MyDevice_Destroy(device);
    ImGui::DestroyContext();
}

```

## Reference Implementations

Study these existing backends to understand API-specific patterns:

- **[`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)** – Demonstrates state backup/restore, GLSL shader selection, and `glDrawElementsBaseVertex` usage.
- **[`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp)** – Shows command buffer handling, descriptor set management, and pipeline layout creation.
- **[`backends/imgui_impl_dx12.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_dx12.cpp)** – Illustrates root signature setup, pipeline state objects, and descriptor heaps.
- **[`examples/example_vulkan/main.cpp`](https://github.com/ocornut/imgui/blob/main/examples/example_vulkan/main.cpp)** – Minimal working example of the Vulkan backend integration.

## Summary

- **Dear ImGui** delegates all graphics API calls to renderer backends stored in the `backends/` directory.
- **Backend lifecycle** requires implementing `Init`, `NewFrame`, `RenderDrawData`, `Shutdown`, and device object management functions.
- **State management** requires backing up and restoring the graphics pipeline state to ensure non-invasive rendering.
- **Registration** occurs through `ImGuiIO::BackendRendererUserData` and capability flags in `ImGuiIO::BackendFlags`.
- **Texture support** requires implementing create/destroy functions that translate `ImTextureID` into your API’s texture handles.

## Frequently Asked Questions

### Do I need to modify imgui.cpp to add a new backend?

No. The core library in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) intentionally knows nothing about graphics APIs. You create new backends entirely within separate files in the `backends/` directory or your own project, following the function signature patterns established by [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp).

### How do I handle texture resources in a custom backend?

Implement `CreateTexture` and `DestroyTexture` functions that allocate and free GPU textures. Store the API-specific texture handle in an `ImTextureID` (typically a `void*` or integer ID), which ImGui passes back to you during `RenderDrawData` via the `CmdList->CmdBuffer` texture binding commands.

### What is the purpose of BackendRendererUserData?

This pointer in `ImGuiIO` stores your backend’s custom data structure (containing device references, pipelines, and buffers). Retrieve it anywhere in your backend code using a helper function like `GetBackendData()` to access persistent state without global variables.

### How do I support vertex offsets for large meshes?

Check if your graphics API supports base vertex offset indexing (e.g., `glDrawElementsBaseVertex` in OpenGL 3.2+, or equivalent in Vulkan/DirectX). If available, set `ImGuiBackendFlags_RendererHasVtxOffset` during initialization to enable ImGui to generate meshes exceeding 65,536 vertices without vertex splitting.