# ImGui Thread Safety and Concurrency: Single-Threaded UI with Parallel Rendering

> Learn about ImGui thread safety and concurrency. Discover how to achieve single-threaded UI with parallel rendering by cloning draw data for safe background thread use.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: internals
- Published: 2026-07-19

---

**Dear ImGui strictly requires all UI generation and state mutation to run on a single thread, but supports concurrent rendering by cloning immutable draw data via `ImDrawData::CloneOutput()` for safe background thread consumption.**

Dear ImGui (`ocornut/imgui`) implements a deliberately single-threaded architecture for all UI construction calls to eliminate internal locking and minimize latency. Understanding **imgui thread safety and concurrency** constraints is critical for preventing crashes in multi-threaded applications, as the library assumes every widget-building function executes on the thread that owns the active `ImGuiContext`. While you cannot safely build UI from worker threads, the library provides explicit mechanisms to offload rendering work through data cloning.

## Thread-Local Context Ownership

Dear ImGui stores the active context in a thread-local pointer accessed via `ImGui::GetCurrentContext()` in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) (line 396). This design means each thread maintains its own distinct `ImGuiContext*` value, making concurrent UI construction impossible without explicit context swapping.

- **Context lifecycle**: Create and destroy contexts using `ImGui::CreateContext()` and `ImGui::DestroyContext()`, which manage the thread-local storage allocation.
- **Ownership rule**: All functions that build widgets, modify state, or query `ImGuiIO` must execute on the context-owning thread. Calling `ImGui::Begin()` or `ImGui::GetIO()` from a worker thread results in immediate crashes or silent data corruption because the thread-local pointer resolves to null or a different context instance.

## Safe Concurrency via Render Isolation

ImGui separates the *generation* of draw lists from the *rendering* of those lists. After `ImGui::Render()` completes, the resulting `ImDrawData` structure becomes immutable, enabling safe hand-off to background threads for GPU submission.

### The CloneOutput Pattern

The library provides `CloneOutput()` (declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) at lines 3435-3436 and implemented in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)) to create deep copies of draw command buffers. This allows worker threads to execute GPU commands without touching core ImGui state.

```cpp
// Main thread: Build UI and clone for background rendering
ImGui::NewFrame();
ImGui::Begin("Thread Demo");
ImGui::Text("UI construction stays on main thread");
ImGui::End();
ImGui::Render();

// Deep copy safe for background thread consumption
ImDrawData* cloned = ImGui::GetDrawData()->CloneOutput();
EnqueueRender(cloned);

```

### The imgui_threaded_rendering Extension

For production implementations, the official **imgui_threaded_rendering** example in the `ocornut/imgui_club` repository demonstrates a lock-free queue pattern. This extension builds on `CloneOutput()` by managing the lifecycle of cloned draw data across thread boundaries, ensuring the main thread never blocks on GPU submission.

## Operations That Are Never Thread-Safe

Any function that mutates ImGui state must execute exclusively on the main thread:

- **Widget construction**: `ImGui::Begin()`, `ImGui::Button()`, `ImGui::Text()`, and all interaction APIs.
- **Frame management**: `ImGui::NewFrame()`, `ImGui::EndFrame()`, and context resets.
- **State mutation**: Modifying `ImGuiIO` fields, changing styles, or manipulating `ImDrawList` vertices directly (e.g., `ImDrawList::AddRect()`).
- **Context queries**: `ImGui::GetCurrentContext()` returns the thread-local value, which differs per thread.

## Implementing Multi-Threaded Rendering

Follow this workflow to keep UI generation single-threaded while parallelizing GPU work:

1. **Main thread** builds the frame and clones the output:

```cpp
void MainThreadUpdate()
{
    ImGui::NewFrame();
    // ... all UI widget code here ...
    ImGui::Render();
    
    ImDrawData* draw_data = ImGui::GetDrawData();
    if (draw_data)
    {
        ImDrawData* cloned = draw_data->CloneOutput();
        render_queue.enqueue(cloned);
    }
}

```

2. **Render thread** consumes the immutable clone:

```cpp
void RenderThreadFunc()
{
    while (running)
    {
        ImDrawData* data = render_queue.dequeue();
        if (data)
        {
            // Backend implementation using cloned data only
            MyBackend_RenderDrawData(data);
            delete data; // Release clone when GPU work completes
        }
    }
}

```

3. **Synchronization**: The main thread continues immediately after enqueuing, while the render thread submits to the GPU asynchronously. The cloned data remains valid until the render thread calls `delete`.

## Performance and Memory Considerations

Using `CloneOutput()` involves specific trade-offs managed in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) and [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h):

- **Memory overhead**: Cloning performs a deep copy of all vertices and indices proportional to frame complexity. This allocation cost is only worthwhile when rendering involves heavy GPU pipelines, remote rendering, or complex shader compilation.
- **Texture lifetime**: The cloned `ImDrawData` contains raw pointers to `ImTextureID` values. These texture identifiers must remain valid for the clone's entire lifetime, typically requiring reference-counted texture tables in your backend.
- **Lock-free benefits**: Because the main thread never acquires locks during cloning, you maintain the library’s zero-latency guarantee for UI construction.

## Summary

- **Single-threaded construction**: All `ImGui::` functions that build widgets or mutate context state must run on one designated thread owning the `ImGuiContext`.
- **Parallel rendering**: Offload GPU work to background threads by cloning `ImDrawData` using `CloneOutput()` after `ImGui::Render()` completes.
- **Texture safety**: Ensure `ImTextureID` pointers in cloned data remain valid until the render thread finishes GPU submission.
- **Reference implementation**: Study the `imgui_threaded_rendering` repository for production-ready lock-free queue patterns.

## Frequently Asked Questions

### Can I call ImGui::Begin() from a background thread?

No. Calling `ImGui::Begin()` or any widget construction function from a worker thread violates the single-threaded contract and will crash or corrupt state. The active context lives in thread-local storage (`ImGui::GetCurrentContext()` in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)), so worker threads see different memory or null pointers.

### Is ImGui::Render() thread-safe?

Calling `ImGui::Render()` itself is only safe from the main thread that owns the context. However, the `ImDrawData` it produces becomes immutable after the call returns, allowing you to safely read and clone that data from the main thread for consumption by background render threads.

### How do I safely render ImGui from a worker thread?

Call `ImGui::Render()` on the main thread to finalize draw lists, then use `ImGui::GetDrawData()->CloneOutput()` to create a deep copy. Pass this copy to your worker thread for GPU submission. The render thread must delete the clone when finished. Never pass the original `ImDrawData` pointer to another thread.

### What is the performance cost of CloneOutput()?

`CloneOutput()` allocates memory and copies all vertex buffers, index buffers, and command lists for the entire frame. This cost scales linearly with UI complexity. Use it only when the GPU rendering work itself is expensive enough to justify the copy overhead, such as when targeting multiple GPUs or performing remote rendering.