How to Implement a Custom Dear ImGui Backend for Your Rendering Engine
A custom Dear ImGui backend requires implementing four core functions—Init, NewFrame, RenderDrawData, and Shutdown—that translate ImGui's ImDrawData into your engine's rendering API commands.
Creating a custom backend allows you to integrate Dear ImGui into proprietary engines or unsupported graphics APIs. According to the ocornut/imgui source code, the library strictly separates platform concerns (input, windowing) from renderer concerns (GPU draw calls), enabling you to plug into any rendering pipeline by consuming the generated draw lists.
Understanding the Backend Architecture
Dear ImGui communicates with your code through the ImGuiIO structure defined in imgui.h. This structure acts as the contract between the library and your backend implementation.
Your backend must populate three critical fields in ImGuiIO:
BackendRendererUserData– A pointer to your backend-specific data structure (shaders, buffers, textures).BackendRendererName– A string identifier displayed in ImGui's About window (e.g., "MyEngine").BackendFlags– Capability flags such asImGuiBackendFlags_RendererHasVtxOffsetfor supporting 64k+ vertex meshes orImGuiBackendFlags_RendererHasTexturesfor dynamic texture updates.
The architecture ensures isolation: ImGui never directly accesses your engine's resources. Instead, you control all GPU state through the user data pointer, following the pattern established in backends/imgui_impl_opengl3.cpp.
Core Backend API Functions
Every custom rendering backend exposes a consistent public API prefixed with ImGui_Impl<Backend>. These four functions handle the complete lifecycle:
-
ImGui_Impl<Backend>_Init– Allocates your backend data structure, creates shaders matchingImDrawVertlayout, uploads the default font texture usingImGui::GetIO().Fonts->GetTexDataAsRGBA32(), and setsBackendRendererUserData. -
ImGui_Impl<Backend>_NewFrame– Updates per-frame state such as viewport dimensions and projection matrices based onio.DisplaySize. -
ImGui_Impl<Backend>_RenderDrawData– The heart of the backend. Iterates overImDrawData, binds textures viaImTextureIDhandles, sets clip rectangles fromImDrawCmd::ClipRect, and issues draw calls respectingVtxOffsetandIdxOffset. -
ImGui_Impl<Backend>_Shutdown– Releases all GPU resources (shaders, buffers, textures) and clearsBackendRendererUserDatato prevent dangling pointers.
Step-by-Step Implementation Guide
Define Backend Data Structure
Create a private struct to hold all renderer-specific state. Store a pointer to this struct in io.BackendRendererUserData, mirroring the helper pattern ImGui_ImplOpenGL3_GetBackendData() found in the OpenGL3 reference backend.
struct MyBackendData {
MyEngine::Shader* shader = nullptr;
MyEngine::Buffer* vtxBuf = nullptr;
MyEngine::Buffer* idxBuf = nullptr;
MyEngine::Texture* fontTex = nullptr;
int bufferSize = 0; // Track current allocation
};
Initialize the Backend
In your initialization function, create resources and declare capabilities to ImGui:
bool ImGui_ImplMyEngine_Init() {
ImGuiIO& io = ImGui::GetIO();
MyBackendData* bd = new MyBackendData();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "MyEngine";
// Create shader with vertex layout matching ImDrawVert (pos + uv + color)
bd->shader = MyEngine::CreateShader(vertex_glsl, fragment_glsl);
// Allocate dynamic buffers (growable approach recommended)
bd->vtxBuf = MyEngine::CreateBuffer(2*1024*1024, MyEngine::VertexBuffer);
bd->idxBuf = MyEngine::CreateBuffer(2*1024*1024, MyEngine::IndexBuffer);
// Upload font atlas
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
bd->fontTex = MyEngine::CreateTexture(width, height, pixels);
io.Fonts->TexID = (ImTextureID)bd->fontTex;
// Declare supported features
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset |
ImGuiBackendFlags_RendererHasTextures;
return true;
}
Handle Per-Frame Preparation
The NewFrame function prepares rendering state. For single-viewport applications, this may only require updating a uniform buffer with the orthographic projection matrix mapping io.DisplayPos and io.DisplaySize to clip space.
If supporting multi-viewport (multiple ImGui windows outside the main application window), expose a render state pointer via ImGui::GetPlatformIO().Renderer_RenderState, as demonstrated in backends/imgui_impl_vulkan.cpp.
Render the Draw Data
Process the command lists generated by ImGui:
void ImGui_ImplMyEngine_RenderDrawData(ImDrawData* draw_data) {
if (draw_data->TotalVtxCount == 0) return;
MyBackendData* bd = (MyBackendData*)ImGui::GetIO().BackendRendererUserData;
// Upload vertex/index data to GPU
MyEngine::UpdateBuffer(bd->vtxBuf, draw_data->VtxBuffer.Data,
draw_data->VtxBuffer.Size * sizeof(ImDrawVert));
MyEngine::UpdateBuffer(bd->idxBuf, draw_data->IdxBuffer.Data,
draw_data->IdxBuffer.Size * sizeof(ImDrawIdx));
// Setup render pipeline state
MyEngine::SetBlendMode(MyEngine::BlendAlpha);
MyEngine::DisableDepthTest();
MyEngine::BindShader(bd->shader);
MyEngine::BindVertexBuffer(bd->vtxBuf);
MyEngine::BindIndexBuffer(bd->idxBuf);
// Process command lists
int vtxOffset = 0, idxOffset = 0;
for (int i = 0; i < draw_data->CmdListsCount; i++) {
const ImDrawList* cmdList = draw_data->CmdLists[i];
for (int cmd_i = 0; cmd_i < cmdList->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd* pcmd = &cmdList->CmdBuffer[cmd_i];
if (pcmd->UserCallback) {
// Handle custom callbacks (e.g., DrawCallback_ResetRenderState)
pcmd->UserCallback(cmdList, pcmd);
} else {
// Bind texture ID (cast back to your engine's texture handle)
MyEngine::BindTexture((MyEngine::Texture*)pcmd->TextureId);
// Set scissor rectangle (ClipRect is x1,y1,x2,y2 in screen coordinates)
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
MyEngine::DrawIndexed(
pcmd->ElemCount,
idxOffset + pcmd->IdxOffset,
vtxOffset + pcmd->VtxOffset
);
}
}
vtxOffset += cmdList->VtxBuffer.Size;
idxOffset += cmdList->IdxBuffer.Size;
}
}
Shutdown and Cleanup
Properly release all allocated resources:
void ImGui_ImplMyEngine_Shutdown() {
MyBackendData* bd = (MyBackendData*)ImGui::GetIO().BackendRendererUserData;
if (!bd) return;
MyEngine::DestroyShader(bd->shader);
MyEngine::DestroyBuffer(bd->vtxBuf);
MyEngine::DestroyBuffer(bd->idxBuf);
MyEngine::DestroyTexture(bd->fontTex);
delete bd;
ImGui::GetIO().BackendRendererUserData = nullptr;
}
Reference Implementation Files
When implementing your custom Dear ImGui backend, consult these official reference files from the ocornut/imgui repository:
imgui.h– ContainsImGuiIOstructure,ImGuiBackendFlagsdefinitions, andImDrawDatastructures.backends/imgui_impl_opengl3.cpp– Complete reference showing initialization,RenderDrawDataloop, font texture creation, and buffer management.backends/imgui_impl_vulkan.cpp– Demonstrates handling ofRenderer_RenderStatefor multi-viewport support and advanced synchronization patterns.backends/imgui_impl_win32.cpp– Example platform backend showing input handling, though you only need this if implementing a platform layer rather than just a renderer.
Summary
- Dear ImGui backends bridge the gap between ImGui's immediate mode API and your engine's retained mode GPU API by translating
ImDrawDatainto native draw calls. - Four functions form the complete backend API:
Init,NewFrame,RenderDrawData, andShutdown. - Store all state in a private struct pointed to by
io.BackendRendererUserDatato maintain clean separation of concerns. - Always set
BackendFlagsto declare capabilities likeRendererHasVtxOffsetfor large meshes. - Handle
ImDrawCmd::UserCallbackto support special reset commands, and respectClipRectfor proper widget clipping.
Frequently Asked Questions
What is the difference between a platform backend and a renderer backend?
Dear ImGui separates windowing/input from GPU rendering. A platform backend (like imgui_impl_win32.cpp or imgui_impl_glfw.cpp) handles OS window creation, mouse/keyboard input, and clipboard access, feeding data into ImGuiIO. A renderer backend (like imgui_impl_opengl3.cpp) only handles translating ImDrawData into graphics API calls. You can mix any platform backend with any renderer backend.
How do I handle texture binding in a custom Dear ImGui backend?
ImGui uses ImTextureID (a void* typedef) to reference textures. In your ImGui_Impl<Backend>_Init, upload the font atlas via GetTexDataAsRGBA32() and store your engine's texture handle in io.Fonts->TexID. During RenderDrawData, cast pcmd->TextureId back to your native texture handle type. For user-loaded textures, call ImGui::Image((ImTextureID)myTexture, ...) and your backend will receive that same pointer in the draw command.
What vertex format does ImGui use for ImDrawData?
The vertex structure is ImDrawVert, defined in imgui.h, containing a 2D position (ImVec2), texture coordinates (ImVec2), and a 32-bit color (ImU32 in RGBA format). Your shader must match this exact layout. Indices are either 16-bit (ImDrawIdx defaults to unsigned short) or 32-bit if you define ImDrawIdx as unsigned int before including ImGui headers.
How do I support multi-viewport rendering in my custom backend?
Multi-viewport requires your backend to render ImGui windows into separate OS windows or framebuffers. Enable the feature by setting io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable and io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports. You must then handle ImGuiPlatformIO callbacks or, for simpler integration, use the Renderer_RenderState pointer in ImGuiPlatformIO to pass per-viewport render state, following the pattern in backends/imgui_impl_vulkan.cpp at lines referencing Renderer_RenderState.
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 →