How to Create a Custom Renderer Backend for Dear ImGui: A Complete Implementation Guide
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 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 likeImGuiBackendFlags_RendererHasVtxOffsetfor 64k+ vertex meshes orImGuiBackendFlags_RendererHasTexturesfor dynamic texture updates.
The Four-Function API Contract
Every renderer backend implements a consistent public API prefixed with ImGui_Impl<Backend>:
-
ImGui_Impl<Backend>_Init– Allocate backend data, create shaders, vertex/index buffers, and setBackendRendererUserData. See the reference implementation in [imgui_impl_opengl3.cpp](https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_opengl3.cpp#L216). -
ImGui_Impl<Backend>_NewFrame– Update per-frame state such as viewport size and projection matrix. Reference:ImGui_ImplOpenGL3_NewFrame. -
ImGui_Impl<Backend>_RenderDrawData– Iterate overImDrawDataand issue rendering commands to your GPU API. Reference:ImGui_ImplOpenGL3_RenderDrawData. -
ImGui_Impl<Backend>_Shutdown– Free all GPU resources and clearBackendRendererUserData. Reference:ImGui_ImplOpenGL3_Shutdown.
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.
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
ImDrawVertvertex 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:
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).
Step 4: Render Draw Lists and Commands
Call ImGui::GetDrawData() to obtain ImDrawData*, then iterate through command lists:
- Upload geometry – Copy
ImDrawVertandImDrawIdxdata to your GPU buffers. - Bind resources – Set shader, vertex buffer, and index buffer.
- 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, respectingpcmd->VtxOffsetandpcmd->IdxOffset. - Handle
pcmd->UserCallbackfor custom render-state resets (seeDrawCallback_ResetRenderStatehandling in the OpenGL3 backend).
- Bind texture using
Step 5: Shutdown and Resource Cleanup
Your Shutdown function must:
- Delete shaders, buffers, and textures.
- Clear
io.BackendRendererUserDatato 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:
// 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();
// 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– Shows complete initialization, vertex layout handling, andDrawCallback_ResetRenderStateprocessing.backends/imgui_impl_vulkan.cpp– DemonstratesRenderer_RenderStatemanagement for multi-viewport support and complex resource binding.imgui.h– ContainsImGuiIOstructure definitions, backend flags, andImGuiPlatformIOfor advanced viewport handling.
Summary
- Backend State – Store all renderer data in a custom struct pointed to by
ImGuiIO::BackendRendererUserDatato maintain isolation between ImGui and your engine. - Four Functions – Implement
Init,NewFrame,RenderDrawData, andShutdownto satisfy the backend contract. - Flag Declaration – Set
ImGuiBackendFlags_RendererHasVtxOffsetandImGuiBackendFlags_RendererHasTexturesinImGuiIO::BackendFlagsto 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 inRenderDrawData, and destroy all resources inShutdown.
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 or 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 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.
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 →