How to Implement a Custom Rendering Backend for Dear ImGui
To implement a custom rendering backend for ImGui, you must expose four core functions (Init, Shutdown, NewFrame, RenderDrawData), store GPU resources in a data structure attached to io.BackendRendererUserData, and translate ImDrawData into your graphics API's draw calls while preserving and restoring the previous graphics state.
Dear ImGui (ocornut/imgui) separates platform and renderer responsibilities into distinct backend modules, allowing integration with any graphics API. When you implement a custom rendering backend for ImGui, you provide the bridge between high-level draw lists and GPU command submission. This guide follows the exact patterns found in backends/imgui_impl_opengl3.cpp and the contract defined in docs/BACKENDS.md.
The Renderer Backend Contract
According to the ocornut/imgui source code, a renderer backend must expose a specific ABI that ImGui calls during the frame lifecycle. The reference OpenGL 3 implementation in backends/imgui_impl_opengl3.cpp demonstrates the required function signatures and behavior.
| Function | Purpose | Reference in imgui_impl_opengl3.cpp |
|---|---|---|
ImGui_ImplOpenGL3_Init |
Initialize the backend, load GL loader, compile shaders | Lines 31-33 |
ImGui_ImplOpenGL3_NewFrame |
Per-frame setup, ensure device objects exist | Lines 24-33 |
ImGui_ImplOpenGL3_RenderDrawData |
Core rendering routine, uploads buffers, issues draw calls | Lines 55-56 |
ImGui_ImplOpenGL3_Shutdown |
Release all GPU resources | Lines 16-24 |
The backend API is declared in imgui.h through ImGuiIO::BackendRendererUserData and documented in docs/BACKENDS.md. Your implementation must create and manage GPU resources (shaders, buffers, textures) and report capabilities through ImGuiIO::BackendRendererFlags (e.g., ImGuiBackendFlags_RendererHasVtxOffset).
Step-by-Step Implementation Guide
1. Define the Backend Data Structure
Create a structure to hold shader handles, buffer IDs, and capability flags. Store a pointer to this structure in io.BackendRendererUserData so ImGui can retrieve it during callbacks.
// imgui_impl_myapi.h
struct ImGui_ImplMyAPI_Data {
MyShaderHandle Shader;
MyBufferHandle Vbo, Ibo;
bool HasVtxOffset; // Advertise via ImGuiBackendFlags_RendererHasVtxOffset
ImGui_ImplMyAPI_Data() { memset(this, 0, sizeof(*this)); }
};
IMGUI_API bool ImGui_ImplMyAPI_Init();
IMGUI_API void ImGui_ImplMyAPI_Shutdown();
IMGUI_API void ImGui_ImplMyAPI_NewFrame();
IMGUI_API void ImGui_ImplMyAPI_RenderDrawData(ImDrawData* draw_data);
2. Initialize GPU Resources
The Init function creates shaders, generates buffers, and advertises renderer capabilities. Attach your data structure to io.BackendRendererUserData so ImGui can access it throughout the application lifetime.
bool ImGui_ImplMyAPI_Init()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplMyAPI_Data* bd = IM_NEW(ImGui_ImplMyAPI_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "myapi";
// Compile shaders (implement MyCreateShader for your API)
bd->Shader = MyCreateShader(vertex_src, fragment_src);
bd->Vbo = MyCreateBuffer();
bd->Ibo = MyCreateBuffer();
// Advertise support for base vertex offsets if available
io.BackendRendererFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
return bd->Shader != nullptr;
}
3. Handle New Frames
NewFrame is called each frame before any ImGui API usage. Ensure device objects are valid and recreate them if they were lost (e.g., due to device reset).
void ImGui_ImplMyAPI_NewFrame()
{
ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)ImGui::GetIO().BackendRendererUserData;
if (!bd->Shader) // Lost device scenario
ImGui_ImplMyAPI_Init();
}
4. Render Draw Data
The RenderDrawData function translates ImDrawData into your graphics API commands. As implemented in backends/imgui_impl_opengl3.cpp, you must backup the current graphics state, upload vertex and index buffers, iterate over command lists, then restore the previous state.
void ImGui_ImplMyAPI_RenderDrawData(ImDrawData* draw_data)
{
if (!draw_data) return;
ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)ImGui::GetIO().BackendRendererUserData;
// Backup current graphics state (viewport, blend mode, active texture, etc.)
MyStateBackup backup = MyCaptureState();
// Upload vertex and index buffers
size_t vtx_size = draw_data->TotalVtxCount * sizeof(ImDrawVert);
size_t idx_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx);
MyUpdateBuffer(bd->Vbo, draw_data->VtxBuffer.Data, vtx_size);
MyUpdateBuffer(bd->Ibo, draw_data->IdxBuffer.Data, idx_size);
// Process each command list
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
if (pcmd->UserCallback)
{
pcmd->UserCallback(cmd_list, pcmd);
continue;
}
// Set scissor rectangle and bind texture
MySetScissor(pcmd->ClipRect);
MyBindTexture((MyTexture)pcmd->GetTexID());
// Issue draw call with base vertex offset if supported
if (bd->HasVtxOffset && pcmd->VtxOffset)
MyDrawElementsBaseVertex(pcmd->ElemCount,
(pcmd->IdxOffset * sizeof(ImDrawIdx)),
pcmd->VtxOffset);
else
MyDrawElements(pcmd->ElemCount,
(pcmd->IdxOffset * sizeof(ImDrawIdx)));
}
}
// Restore previous graphics state to avoid side effects
MyRestoreState(backup);
}
5. Release Resources on Shutdown
Shutdown releases all GPU resources and frees the backend data structure. Set io.BackendRendererUserData to nullptr to indicate the backend is inactive.
void ImGui_ImplMyAPI_Shutdown()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplMyAPI_Data* bd = (ImGui_ImplMyAPI_Data*)io.BackendRendererUserData;
if (!bd) return;
MyDeleteShader(bd->Shader);
MyDeleteBuffer(bd->Vbo);
MyDeleteBuffer(bd->Ibo);
IM_DELETE(bd);
io.BackendRendererUserData = nullptr;
}
Integrating with Your Application
Include your backend header and call the lifecycle functions in your main loop. This pattern mirrors the examples in the examples/ folder of ocornut/imgui.
#include "imgui.h"
#include "backends/imgui_impl_myapi.h"
int main()
{
MyGraphicsDeviceInit(); // Your platform-specific initialization
ImGui::CreateContext();
ImGui_ImplMyAPI_Init(); // Initialize the renderer backend
while (!MyWindowShouldClose())
{
ImGui_ImplMyAPI_NewFrame(); // Prepare backend for new frame
ImGui::NewFrame();
// Your ImGui UI code here
ImGui::Render();
ImGui_ImplMyAPI_RenderDrawData(ImGui::GetDrawData());
MySwapBuffers();
}
ImGui_ImplMyAPI_Shutdown();
MyGraphicsDeviceShutdown();
return 0;
}
Summary
- Expose four required functions: Implement
Init,Shutdown,NewFrame, andRenderDrawDatawith signatures matching the backend contract inimgui.h. - Store resources in
BackendRendererUserData: Create a custom structure (likeImGui_ImplOpenGL3_Data) holding shaders, buffers, and flags, and attach it toImGuiIO. - Translate
ImDrawDatacorrectly: UploadVtxBufferandIdxBuffer, iterate overImDrawCmdentries, handle user callbacks, set scissor rectangles, and issue textured draw calls. - Preserve graphics state: Backup the entire relevant GPU state at the start of
RenderDrawDataand restore it before returning to avoid corrupting the host application's rendering. - Advertise capabilities: Set
ImGuiBackendFlags_RendererHasVtxOffsetif your API supports base-vertex offsets, enabling ImGui to render large meshes efficiently.
Frequently Asked Questions
What is the difference between a platform backend and a renderer backend?
The platform backend handles window creation, input polling, and time-keeping for the host operating system (e.g., SDL2, GLFW), while the renderer backend translates ImGui's draw lists into GPU commands for a specific graphics API (OpenGL, Vulkan, DirectX). These are separate concerns in ocornut/imgui's architecture, allowing you to mix any platform backend with any renderer backend according to docs/BACKENDS.md.
How do I handle textures in a custom ImGui rendering backend?
ImGui uses ImTextureID as an opaque handle. Cast your native texture type to ImTextureID (typically via (ImTextureID)(intptr_t)my_texture) when binding in RenderDrawData. In the draw loop, retrieve the texture using pcmd->GetTexID() and cast it back to your API's texture handle before binding. This pattern is used throughout imgui_impl_opengl3.cpp for font atlases and user images.
Do I need to support vertex offsets in my renderer?
You should support vertex offsets if your graphics API provides base-vertex draw functions (e.g., glDrawElementsBaseVertex or Vulkan's firstVertex). Advertise this capability by setting ImGuiBackendFlags_RendererHasVtxOffset in io.BackendRendererFlags. This allows Dear ImGui to render large meshes exceeding 16-bit index limits by splitting them into separate draw lists with byte offsets.
Why must I backup and restore graphics state when rendering ImGui?
The renderer backend must preserve the host application's graphics state—active textures, blend modes, viewport, scissor rectangles, and shader programs—to avoid side effects. As demonstrated in backends/imgui_impl_opengl3.cpp, capture the entire relevant state at the start of RenderDrawData and restore it before returning, ensuring your UI rendering does not interfere with the application's main rendering pipeline.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →