# Best Practices for Using Dear ImGui in Game Development

> Master Dear ImGui for game development. Learn best practices for side-effect-free UI, proper initialization, and rendering order to optimize your game loops. Enhance your workflow today.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: best-practices
- Published: 2026-07-21

---

**Dear ImGui is an immediate-mode GUI library designed for real-time game loops, and following best practices means keeping UI construction side-effect-free, initializing backends in the exact frame order shown in the official examples, and rendering the draw data after your game scene but before post-processing.**

Dear **ImGui**, maintained in the **ocornut/imgui** repository, is an **immediate-mode GUI library** purpose-built for real-time applications such as games. Because it redraws every frame, integrating it correctly requires adhering to patterns defined in the **core source** ([`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) and [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)) and the **official backend files**. This article outlines the essential **best practices for using ImGui in game development**, derived directly from the library's source code and example projects.

## Understand the Three-Layer Architecture

Dear ImGui is divided into three loosely coupled layers. Recognizing this separation prevents common integration mistakes.

- **Core API** — The `ImGui::` namespace in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) and [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp) defines widgets, layout logic, and internal state. This layer is platform and renderer agnostic.

- **Backend** — Platform-specific code that feeds input and handles rendering. Each backend follows the `imgui_impl_XXX` naming convention, such as [`backends/imgui_impl_glfw.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_glfw.cpp) for input and [`backends/imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp) for rendering.

- **Examples** — Minimal demos such as `examples/example_glfw_opengl3/` show the full startup, frame, and shutdown flow. These are the canonical reference for integration order.

## Follow the Canonical Frame Lifecycle

As documented in [`docs/EXAMPLES.md`](https://github.com/ocornut/imgui/blob/main/docs/EXAMPLES.md), the frame lifecycle must be followed precisely to avoid input lag or rendering errors.

1. **Create a context** with `ImGui::CreateContext()`.

2. **Initialize each backend** with `ImGui_ImplXXX_Init()`.

3. **Each frame**, call the backend's `NewFrame()` helpers first, then `ImGui::NewFrame()`.

4. **Build the UI** using `ImGui::` widget calls.

5. **Finalize and render** by calling `ImGui::Render()`, then `ImGui_ImplXXX_RenderDrawData()`.

6. **On shutdown**, call `ImGui_ImplXXX_Shutdown()` followed by `ImGui::DestroyContext()`.

Calling `ImGui::NewFrame()` before the backend's `NewFrame()` is a frequent source of input offset and lag.

## Keep UI Code Side-Effect-Free

**Immediate-mode** UI runs every frame, so mutating game state during UI construction can produce inconsistent frames. The safest pattern is to read state into temporary variables and apply changes after the frame ends.

```cpp
// UI layer only reads
bool wantPause = ImGui::Checkbox("Pause", &gamePaused);

// Apply side effects after ImGui::Render()
if (wantPause) SetPaused(gamePaused);

```

## Avoid Per-Frame Heap Allocations

Allocation overhead can stall the frame budget, especially on consoles. Use static buffers or stack storage for text inputs and temporary arrays rather than constructing standard containers every frame.

```cpp
static char nameBuf[64];
ImGui::InputText("Name", nameBuf, IM_ARRAYSIZE(nameBuf));

```

## Batch Draw Calls and Minimize State Changes

Dear ImGui already batches vertices internally in its draw lists. The provided OpenGL3, Vulkan, and DirectX backends are optimized to emit a minimal number of draw calls. Avoid switching shaders or viewports inside the ImGui render pass, and keep the backend implementations largely unchanged unless your engine has specific constraints.

## Manage Fonts and High-DPI Scaling

Large font atlases increase texture memory and bandwidth. Load only the fonts you need, and respect display scaling by setting `io.FontGlobalScale` for high-DPI displays.

```cpp
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("Roboto-Medium.ttf", 16.0f);
io.FontGlobalScale = 1.0f / io.DisplayFramebufferScale.x;

```

## Separate the UI Render Pass

Render ImGui after your main scene geometry but before post-processing effects such as bloom or tone-mapping. This keeps UI elements crisp and on top without being affected by world-space shaders.

In a typical forward renderer, the order is:

```cpp
RenderScene();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
PostProcessUI(); // if applying any UI-specific effects

```

## Persist UI State Across Sessions

Storing window open states, collapse flags, and user preferences prevents players from rearranging the UI every session. You can use `ImGui::GetStateStorage()` to cache values by ID.

```cpp
ImGuiStorage* storage = ImGui::GetStateStorage();
bool open = storage->GetBool(storage->GetIntId("MyWindowOpen"), true);
ImGui::Begin("MyWindow", &open);
storage->SetBool(storage->GetIntId("MyWindowOpen"), open);
ImGui::End();

```

## Profile UI Cost Using Built-In Metrics

Keep an eye on vertex throughput and draw-list growth using `ImGuiIO` metrics counters. Spikes in vertex count often indicate overly complex panels.

```cpp
ImGuiIO& io = ImGui::GetIO();
printf("Vertices: %d\n", io.MetricsRenderVertices);

```

## Backend-Specific Best Practices

### GLFW and OpenGL3

Use the modern [`imgui_impl_opengl3.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) backend for programmable-pipeline engines. The legacy OpenGL2 backend is only appropriate for fixed-function pipelines. The minimal integration in `examples/example_glfw_opengl3/` is the canonical starting point.

### DirectX 12

In [`backends/imgui_impl_dx12.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_dx12.cpp), the application must manage command-list and descriptor-heap lifetimes. Create a persistent command list and reuse descriptor heaps rather than allocating them per frame.

### Vulkan

The Vulkan backend in [`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp) caches pipelines per render pass. Pre-create a `VkDescriptorSetLayout` for ImGui textures, and ensure your render pass is compatible across frames to avoid pipeline recreation.

### Metal

The Metal backend in `backends/imgui_impl_metal.mm` uses a single `MTLRenderPassDescriptor`. Set `clearColor` to transparent when overlaying UI so that the game scene remains visible underneath.

## Common Pitfalls to Avoid

Several recurring mistakes are documented in the source comments and [`docs/FAQ.md`](https://github.com/ocornut/imgui/blob/main/docs/FAQ.md).

- **Calling `ImGui::NewFrame()` before the backend `NewFrame()`** produces offset UI and input lag. Always call `ImGui_ImplXXX_NewFrame()` first.

- **Mixing OpenGL2 and OpenGL3 backends** causes crashes or missing shaders. Match the backend to your engine's graphics API and pipeline model.

- **Enabling `io.MouseDrawCursor` continuously** creates sluggish cursor behavior at high frame rates. Enable it only during drag operations; otherwise, let the OS handle cursor drawing.

- **Using large per-frame buffers in `InputText`** leads to excessive heap traffic. Prefer fixed-size static buffers or `ImGui::InputTextWithHint` with a predetermined size.

- **Neglecting DPI scaling** makes UI elements appear tiny on Hi-DPI monitors. Always multiply sizes and positions by `io.DisplayFramebufferScale`.

## Production-Ready Integration Example

Below is a minimal, production-ready integration that applies the best practices above. It keeps UI state separate from game state, initializes the GLFW and OpenGL3 backends, and follows the exact frame order prescribed in [`docs/EXAMPLES.md`](https://github.com/ocornut/imgui/blob/main/docs/EXAMPLES.md).

```cpp
// ------------------------------------------------------------
// 1. Global structs (keep UI state separate from game state)
// ------------------------------------------------------------
struct UIState {
    bool showDemo = false;
    bool showConsole = true;
};
static UIState gUI;

// ------------------------------------------------------------
// 2. Initialization (once, at engine start)
// ------------------------------------------------------------
void InitializeImGui(GLFWwindow* window)
{
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;

    // ---- Font (optional) ----
    io.Fonts->AddFontFromFileTTF("misc/fonts/Roboto-Medium.ttf", 16.0f);
    io.FontGlobalScale = 1.0f / io.DisplayFramebufferScale.x;

    // ---- Backend init ----
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 330 core");
}

// ------------------------------------------------------------
// 3. Per-frame UI code (called after game update, before present)
// ------------------------------------------------------------
void RenderImGui()
{
    // 3.1 Backend new-frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // --------------------------------------------------------
    // 3.2 Build UI
    // --------------------------------------------------------
    if (gUI.showDemo)
        ImGui::ShowDemoWindow(&gUI.showDemo);

    // Example: simple console overlay
    if (gUI.showConsole) {
        ImGui::Begin("Console", &gUI.showConsole);
        static char cmdBuf[256] = "";
        ImGui::InputText("Command", cmdBuf, IM_ARRAYSIZE(cmdBuf));
        if (ImGui::Button("Run")) {
            // Process command after the frame to keep UI side-effect-free
            // (store it somewhere, execute later)
        }
        ImGui::End();
    }

    // --------------------------------------------------------
    // 3.3 Rendering
    // --------------------------------------------------------
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

// ------------------------------------------------------------
// 4. Shutdown (engine exit)
// ------------------------------------------------------------
void ShutdownImGui()
{
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
}

```

## Summary

- **Dear ImGui** divides work into the core API ([`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) / [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)), platform backends (`imgui_impl_XXX`), and official examples.

- **Frame order matters**: always call backend `NewFrame()` before `ImGui::NewFrame()`, and render after the scene but before post-processing.

- **Keep UI code side-effect-free** by reading game state into temporaries and applying changes after the frame ends.

- **Avoid per-frame allocations** with static buffers, and profile cost using `io.MetricsRenderVertices`.

- **Choose the backend that matches your renderer**, and manage backend-specific resources such as descriptor heaps or render passes carefully.

## Frequently Asked Questions

### What is the correct order for ImGui NewFrame calls?

You must call the backend-specific `NewFrame` functions before `ImGui::NewFrame()`. For example, in a GLFW and OpenGL3 integration, call `ImGui_ImplOpenGL3_NewFrame()` and `ImGui_ImplGlfw_NewFrame()` first, then `ImGui::NewFrame()`. Reversing this order causes input lag and widget offset, as noted in [`docs/EXAMPLES.md`](https://github.com/ocornut/imgui/blob/main/docs/EXAMPLES.md) and the reference example code.

### Should I remove ImGui::ShowDemoWindow from shipping game builds?

Yes. `ImGui::ShowDemoWindow` is defined in [`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) and is an excellent learning tool, but it adds significant binary bloat and unnecessary code paths. Wrap it in a preprocessor macro or development-only flag so it is excluded from retail builds.

### How do I handle high-DPI displays with Dear ImGui?

Query `io.DisplayFramebufferScale` after creating the context, then multiply font sizes and optionally set `io.FontGlobalScale` to match. Loading fonts at a base size and scaling globally prevents tiny UI elements on Hi-DPI monitors and ensures readable text across resolutions.

### Can I modify game state directly inside an ImGui button callback?

You should avoid direct mutation. Immediate-mode UI reconstructs every frame, so changing game state during UI construction can lead to frame inconsistencies and subtle bugs. Capture the intent in a temporary variable or queue the action, then apply it after `ImGui::Render()` or inside your game's fixed update step.