# How to Create a Custom ImGui Renderer for a Proprietary Graphics API

> Learn to create a custom ImGui renderer for your proprietary graphics API. Implement four backend functions to integrate ImGui's UI logic with your custom drawing commands.

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

---

**ImGui separates core UI logic from backend rendering; you only need to implement four backend functions (`Init`, `Shutdown`, `NewFrame`, and `RenderDrawData`) to translate `ImDrawData` into your proprietary graphics API's command buffers.**

The ocornut/imgui repository provides a modular immediate-mode GUI library where the core UI logic remains completely separate from graphics API-specific rendering code. To integrate ImGui with a proprietary or custom graphics engine, you must implement a backend that converts ImGui's draw lists into your native rendering commands. This architecture allows you to leverage ImGui's full widget library—including tables, plots, and docking—while maintaining complete control over the rendering pipeline.

## Architecture Overview

ImGui's design strictly divides responsibilities between the **core** UI system and the **backend** renderer. According to the ocornut/imgui source code, the core handles state management, widget generation, and layout in files like [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp), [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp), [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp), and [`imgui_tables.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_tables.cpp). The backend, typically implemented in `backends/imgui_impl_*.cpp` files (such as [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) or [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp)), translates ImGui's platform-agnostic draw data into native graphics API calls.

Each frame, the application calls two critical entry points that bridge these layers:

1. **`ImGui::NewFrame()`** – Called after the platform backend populates `ImGuiIO` with input data (mouse, keyboard, clipboard).
2. **`ImGui::Render()`** – Called after your UI code executes; this generates an `ImDrawData` structure that your custom backend must render.

| Layer | Responsibility | Key Files |
|-------|----------------|-----------|
| **Core** | UI state, widget generation, layout, draw list creation | [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp), [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp), [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp) |
| **Backend** | Translate `ImDrawData` to native commands, handle GPU resources | `backends/imgui_impl_*.cpp` |
| **Application** | Window creation, device setup, per-frame orchestration | `examples/example_*/main.cpp` |

A custom renderer implements the same small function set exposed by existing backends:

```cpp
bool MyBackend_Init(void* device, void* render_target);
void MyBackend_Shutdown();
void MyBackend_NewFrame();               // Optional, for per-frame state updates
void MyBackend_RenderDrawData(ImDrawData* draw_data);

```

## Implementation Steps

### 1. Create the Backend Source File

Create a new source file in the `backends/` directory, such as [`backends/imgui_impl_myapi.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_myapi.cpp). Include the core headers to access ImGui's public and internal APIs:

```cpp
#include "imgui.h"
#include "imgui_internal.h"

```

### 2. Define Backend Data Structures

Define a static structure to hold your graphics API objects. As implemented in existing backends like [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp), store handles for the device, command queue, and font texture:

```cpp
struct MyBackendData
{
    void*       Device = nullptr;
    void*       FontTexture = nullptr;
    // Add API-specific handles here
};
static MyBackendData* GMyBackend = nullptr;

```

### 3. Initialize the Font Texture

During initialization, generate and upload ImGui's default font atlas. Retrieve the pixel data using `io.Fonts->GetTexDataAsRGBA32`, upload it to your GPU, and store the resulting texture identifier in `io.Fonts->TexID`:

```cpp
bool MyBackend_Init(void* device)
{
    GMyBackend = IM_NEW(MyBackendData);
    GMyBackend->Device = device;
    
    ImGuiIO& io = ImGui::GetIO();
    unsigned char* pixels;
    int width, height;
    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
    
    // Upload to your proprietary API
    GMyBackend->FontTexture = MyAPI_CreateTexture(device, pixels, width, height);
    io.Fonts->TexID = (ImTextureID)GMyBackend->FontTexture;
    
    return true;
}

```

### 4. Implement the Render Loop

The `MyBackend_RenderDrawData` function iterates over `ImDrawData::CmdLists`, where each `ImDrawList` contains vertex and index buffers. For each `ImDrawCmd`, bind the texture specified in `cmd.TextureId`, set the scissor rectangle using `cmd.ClipRect`, and issue the draw call:

```cpp
void MyBackend_RenderDrawData(ImDrawData* draw_data)
{
    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
        return;
    
    void* cmd_buf = MyAPI_BeginCommandBuffer(GMyBackend->Device);
    
    // Set viewport covering the full display size
    MyAPI_SetViewport(cmd_buf, 0, 0, 
        (int)draw_data->DisplaySize.x, 
        (int)draw_data->DisplaySize.y);
    
    for (int n = 0; n < draw_data->CmdListsCount; n++)
    {
        const ImDrawList* cmd_list = draw_data->CmdLists[n];
        
        // Upload vertex/index data to GPU-accessible memory
        MyAPI_UpdateVertexBuffer(GMyBackend->Device, 
            cmd_list->VtxBuffer.Data, 
            cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
        MyAPI_UpdateIndexBuffer(GMyBackend->Device, 
            cmd_list->IdxBuffer.Data, 
            cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
        
        // Process each command
        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
        {
            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
            
            // Bind texture and set scissor rectangle
            MyAPI_BindTexture(cmd_buf, (void*)pcmd->TextureId);
            MyAPI_SetScissor(cmd_buf,
                (int)pcmd->ClipRect.x,
                (int)pcmd->ClipRect.y,
                (int)(pcmd->ClipRect.z - pcmd->ClipRect.x),
                (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
            
            // Issue indexed draw call
            MyAPI_DrawIndexed(cmd_buf, pcmd->ElemCount, pcmd->IdxOffset, pcmd->VtxOffset);
        }
    }
    
    MyAPI_EndCommandBuffer(cmd_buf);
    MyAPI_Submit(GMyBackend->Device, cmd_buf);
}

```

### 5. Handle Shutdown and Cleanup

Release all GPU resources when the application exits:

```cpp
void MyBackend_Shutdown()
{
    if (!GMyBackend) return;
    MyAPI_DestroyTexture(GMyBackend->Device, GMyBackend->FontTexture);
    IM_DELETE(GMyBackend);
    GMyBackend = nullptr;
}

```

## Integration Example

Wire your custom backend into the application's main loop. You can combine your custom renderer with existing platform backends (such as [`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)) that handle input and window events:

```cpp
// Initialization
MyDevice* device = MyAPI_CreateDevice();
MyBackend_Init(device);

// Main loop
while (running)
{
    // Platform backend updates ImGuiIO with input events
    ImGui_ImplWin32_NewFrame();  // or your platform equivalent
    MyBackend_NewFrame();        // Optional per-frame setup
    
    ImGui::NewFrame();
    
    // Your UI code here
    ImGui::ShowDemoWindow();
    
    ImGui::Render();
    MyBackend_RenderDrawData(ImGui::GetDrawData());
}

```

## Key Reference Files in the Repository

When implementing your backend, reference these specific files from the ocornut/imgui repository to understand the exact patterns used:

- **[`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)** – Contains `ImGui::Render()` which constructs the final `ImDrawData` structure.
- **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** – Implements low-level draw list construction and vertex buffer management.
- **[`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp)** – Reference for `Init`, `RenderDrawData` implementation, and command buffer submission patterns.
- **[`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp)** – Demonstrates font texture creation via `ImGui_ImplOpenGL3_CreateDeviceObjects` and texture ID handling.
- **[`backends/imgui_impl_dx12.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_dx12.cpp)** – Shows command-list recording and resource barrier management.
- **[`backends/imgui_impl_null.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_null.cpp)** – Minimal no-op implementation useful as a starting template.
- **[`examples/example_win32_vulkan/main.cpp`](https://github.com/ocornut/imgui/blob/main/examples/example_win32_vulkan/main.cpp)** – Complete integration example showing how platform and renderer backends work together.

## Summary

- **ImGui's core is API-agnostic**: The UI logic in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) never touches graphics API code; it only produces `ImDrawData` structures.
- **Backend requirements are minimal**: You only need to implement initialization, shutdown, optional per-frame setup, and the main `RenderDrawData` function.
- **Font atlas handling is mandatory**: Your `Init` function must create a texture from `io.Fonts->GetTexDataAsRGBA32` and assign it to `io.Fonts->TexID`.
- **Command translation follows a fixed pattern**: Iterate `CmdLists`, upload vertex/index buffers, then for each `ImDrawCmd` bind the texture, set the scissor from `ClipRect`, and draw.
- **Platform independence**: You can mix your custom renderer with existing platform backends 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) to avoid reimplementing input handling.

## Frequently Asked Questions

### Do I need to modify ImGui's core files to support a custom graphics API?

No. According to the ocornut/imgui architecture, you should never modify [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) or other core files. The backend interface is designed specifically to allow external implementations. Create new files in the `backends/` directory following the naming convention `imgui_impl_[platform].cpp` and `imgui_impl_[renderer].cpp`.

### How do I handle the font atlas texture in a custom renderer?

Call `io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height)` during initialization to retrieve the raw RGBA32 pixel data. Upload this to your GPU using your proprietary API's texture creation functions, then store the resulting handle in `io.Fonts->TexID`. This identifier is passed back to you in `ImDrawCmd::TextureId` during rendering.

### Can I use existing platform backends with my custom renderer?

Yes. Platform backends (such as [`imgui_impl_win32.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_win32.cpp), [`imgui_impl_glfw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_glfw.cpp), or [`imgui_impl_sdl2.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_sdl2.cpp)) only populate the `ImGuiIO` structure with input data and window events. They are completely separate from renderer backends. You can combine `ImGui_ImplWin32_NewFrame()` with your custom `MyBackend_RenderDrawData()` without conflicts.

### What is the performance cost of the backend abstraction layer?

The backend abstraction has negligible overhead. The core ImGui library generates vertex and index buffers once per frame in `ImGui::Render()`. Your backend performs a straightforward translation of these buffers into native API calls. The existing backends in the repository demonstrate that this approach achieves high performance even with complex UIs, as the heavy lifting (clipping, culling, batching) happens in the core, not the backend.