How to Create a Custom Platform Backend for Dear ImGui: A Complete Implementation Guide
Dear ImGui separates platform concerns (window management, input handling) from rendering by requiring backends to populate the ImGuiIO structure with display metrics, translate native OS events into ImGui input calls, and manage platform-specific resources through a dedicated backend data structure.
Dear ImGui (ocornut/imgui) uses a platform-agnostic core that delegates all window system interactions to pluggable backends. Creating a custom platform backend involves implementing a minimal C++ interface that bridges your native windowing system with ImGui's input abstraction layer. This guide references the actual source code in the official repository to show you exactly how to build a production-ready backend.
Understanding the Platform Backend Architecture
A platform backend acts as a translation layer between your operating system's windowing API and Dear ImGui's internal input system. The architecture centers on the ImGuiIO structure, which you populate each frame with timing data, display dimensions, and input events.
Every platform backend must maintain a backend data structure (following the pattern of ImGui_ImplWin32_Data in imgui_impl_win32.cpp) that persists between frames. This struct stores native window handles, timing counters, mouse state, and optional gamepad handles. You attach this structure to io.BackendPlatformUserData during initialization and clear it during shutdown.
The backend also advertises its capabilities through ImGuiBackendFlags. Common flags include ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos, which you set in io.BackendFlags during initialization to inform ImGui that your backend can manipulate the system cursor.
Step 1: Create the Backend Data Structure
Define a data structure that holds all platform-specific state. This pattern appears consistently across official backends like imgui_impl_win32.cpp (lines 62-80) and imgui_impl_glfw.cpp.
// imgui_impl_mybackend.h
#pragma once
#include "imgui.h"
struct MyBackendData
{
MyWindowHandle Window;
double Time;
float MousePos[2];
bool MouseButtons[5];
// Add clipboard handles, cursor handles, etc.
};
// Function declarations
IMGUI_IMPL_API bool ImGui_ImplMyBackend_Init(void* native_window);
IMGUI_IMPL_API void ImGui_ImplMyBackend_Shutdown();
IMGUI_IMPL_API void ImGui_ImplMyBackend_NewFrame();
IMGUI_IMPL_API void ImGui_ImplMyBackend_HandleEvent(const MyEvent& ev);
Store this structure in io.BackendPlatformUserData to make your backend re-entrant for multiple ImGui contexts.
Step 2: Implement Initialization
The initialization function must allocate your backend data, set the backend name, and advertise supported capabilities. Reference imgui_impl_win32.cpp (lines 62-80) for the exact initialization sequence.
bool ImGui_ImplMyBackend_Init(void* native_window)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized!");
MyBackendData* bd = IM_NEW(MyBackendData)();
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_mybackend";
// Advertise capabilities
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
bd->Window = (MyWindowHandle)native_window;
bd->Time = GetPlatformTime(); // Platform-specific timer
return true;
}
Step 3: Handle Per-Frame Updates
Each frame, the backend must update timing information, display size, and mouse cursor state. The NewFrame function in imgui_impl_win32.cpp (lines 219-267) demonstrates this pattern.
void ImGui_ImplMyBackend_NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
MyBackendData* bd = (MyBackendData*)io.BackendPlatformUserData;
IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplMyBackend_Init()?");
// Update display size
io.DisplaySize = GetWindowSize(bd->Window);
// Update delta time
double current_time = GetPlatformTime();
io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f/60.0f);
bd->Time = current_time;
// Update mouse cursor if requested
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
HideSystemCursor();
else
SetSystemCursor(imgui_cursor);
}
Step 4: Translate Native Events
The critical responsibility is converting your platform's native events into ImGui input calls. Create a handler function that your application calls for each window event, following the event translation patterns in imgui_impl_glfw.cpp and imgui_impl_sdl2.cpp.
void ImGui_ImplMyBackend_HandleEvent(const MyEvent& ev)
{
ImGuiIO& io = ImGui::GetIO();
switch (ev.type)
{
case MyEvent::MouseMove:
io.AddMousePosEvent((float)ev.x, (float)ev.y);
break;
case MyEvent::MouseButtonDown:
io.AddMouseButtonEvent(ev.button, true);
break;
case MyEvent::MouseButtonUp:
io.AddMouseButtonEvent(ev.button, false);
break;
case MyEvent::MouseWheel:
io.AddMouseWheelEvent(ev.wheel_x, ev.wheel_y);
break;
case MyEvent::KeyDown:
io.AddKeyEvent(ev.key, true);
break;
case MyEvent::KeyUp:
io.AddKeyEvent(ev.key, false);
break;
case MyEvent::Char:
io.AddInputCharacterUTF16(ev.codepoint);
break;
}
}
Call io.AddInputCharacterUTF16() for text input rather than direct key events to properly handle international keyboards and dead keys.
Step 5: Implement Shutdown
Cleanup must release all allocated resources and clear backend-specific ImGuiIO fields to prevent dangling pointers. Reference imgui_impl_win32.cpp (lines 400-426).
void ImGui_ImplMyBackend_Shutdown()
{
ImGuiIO& io = ImGui::GetIO();
MyBackendData* bd = (MyBackendData*)io.BackendPlatformUserData;
if (bd)
{
// Free cursors, unload DLLs, etc.
IM_DELETE(bd);
io.BackendPlatformUserData = nullptr;
io.BackendPlatformName = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos);
}
}
Step 6: Integrate Into Your Main Loop
Wire the backend into your application's event loop, ensuring correct ordering of ImGui function calls. The examples/ folder in the repository contains reference implementations like example_win32_opengl3/main.cpp.
// Initialization
MyWindow* window = CreateNativeWindow();
ImGui_ImplMyBackend_Init(window);
ImGui_ImplOpenGL3_Init("#version 330");
// Main loop
while (!ShouldClose(window))
{
// Process events - forward to backend handler
PollEvents(window, ImGui_ImplMyBackend_HandleEvent);
// Start ImGui frame
ImGui_ImplMyBackend_NewFrame();
ImGui_ImplOpenGL3_NewFrame();
ImGui::NewFrame();
// Build UI here
ImGui::ShowDemoWindow();
// Render
ImGui::Render();
int display_w, display_h;
GetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SwapBuffers(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplMyBackend_Shutdown();
Optional: Clipboard, Gamepad, and Multi-Viewport
Clipboard support requires setting io.SetClipboardTextFn and io.GetClipboardTextFn callbacks. The Win32 backend (lines 120-150) shows a reference implementation using native clipboard APIs.
Gamepad support involves loading XInput or similar libraries dynamically and pushing controller states via io.AddKeyEvent() for mapped buttons and io.AddAnalogEvent() for axes. See the #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD section in imgui_impl_win32.cpp.
Multi-viewport support requires additional ImGuiPlatformIO callbacks: Platform_CreateWindow, Platform_DestroyWindow, and Platform_ShowWindow. These enable ImGui to create floating tool windows outside the main application window. The Win32 backend (lines 300-340) contains the platform window management implementation.
Summary
- Backend data persistence: Store platform state in a dedicated structure attached to
io.BackendPlatformUserData, following theImGui_ImplWin32_Datapattern. - Capability advertisement: Set
ImGuiBackendFlagsinio.BackendFlagsduring initialization to indicate support for mouse cursors and cursor positioning. - Event translation: Convert native window events to ImGui input using
io.AddMousePosEvent(),io.AddKeyEvent(), andio.AddInputCharacterUTF16(). - Frame updates: Update
io.DisplaySizeandio.DeltaTimeevery frame in yourNewFramefunction. - Resource cleanup: Release all handles and clear
ImGuiIObackend-specific fields in yourShutdownfunction.
Frequently Asked Questions
What is the difference between a platform backend and a renderer backend in Dear ImGui?
A platform backend handles window system integration—managing input events, window focus, clipboard, and timing—while a renderer backend handles graphics API calls to draw ImGui's vertex buffers. You typically combine one platform backend (e.g., Win32, GLFW, SDL) with one renderer backend (e.g., OpenGL3, DirectX11, Vulkan) to create a complete implementation.
How do I handle high-DPI displays in a custom platform backend?
Update io.DisplayFramebufferScale with the appropriate DPI scaling factors (e.g., obtained from GetDpiForWindow on Windows or glfwGetMonitorContentScale on GLFW). ImGui uses this scale to adjust font rasterization and vertex projection. You must also scale io.DisplaySize by the inverse of the framebuffer scale so that ImGui operates in logical screen coordinates.
Can I support multiple windows with one platform backend?
Yes, but you must implement multi-viewport support by populating the ImGuiPlatformIO structure with callbacks for Platform_CreateWindow, Platform_DestroyWindow, and Platform_ShowWindow. The backend data structure should track multiple window handles, and you must ensure each viewport's PlatformUserData points to the correct native window. Reference imgui_impl_win32.cpp (lines 300-340) for the platform window management implementation.
What is the correct order of function calls in the main loop?
First, poll and translate native events using your backend's event handler. Second, call your platform backend's NewFrame function to update io.DeltaTime and io.DisplaySize. Third, call the renderer backend's NewFrame function. Fourth, call ImGui::NewFrame(). After building the UI, call ImGui::Render() followed by the renderer's RenderDrawData function. Shutdown must occur in reverse order: renderer first, then platform.
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 →