# Best Practices for Using ImGui with Vulkan: Complete Integration Guide

> Learn best practices for integrating ImGui with Vulkan. This guide offers a zero-modification API for seamless embedding into Vulkan engines.

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

---

**The Dear ImGui Vulkan backend provides a zero-modification API—centered on `ImGui_ImplVulkan_InitInfo` and `ImGui_ImplVulkan_RenderDrawData`—that lets you embed ImGui into any Vulkan engine without touching renderer source code.**

Dear ImGui ships with a dedicated Vulkan renderer located in [`backends/imgui_impl_vulkan.h`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.h) and [`backends/imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.cpp). This backend is intentionally designed as a drop-in solution that handles pipeline creation, descriptor management, and draw command generation while respecting your existing Vulkan context.

## Understanding the Zero-Modification Contract

The `imgui_impl_vulkan` backend follows a strict zero-modification contract. All public symbols prefixed with `ImGui_ImplVulkan_*` constitute the only API surface you need to touch. The implementation deliberately separates engine-specific helpers (prefixed with `ImGui_ImplVulkanH_*`) into example code only—these window and swap-chain utilities in [`examples/example_glfw_vulkan/main.cpp`](https://github.com/ocornut/imgui/blob/main/examples/example_glfw_vulkan/main.cpp) are **not** required for your own integration.

When you adhere to the documented API, you can update ImGui versions without merge conflicts. If you encounter limitations, file an issue upstream rather than modifying the backend, as the implementation is kept stable to ensure cross-project compatibility.

## Initialization and Configuration

### Setting Up ImGui_ImplVulkan_InitInfo

All Vulkan backend configuration flows through the **`ImGui_ImplVulkan_InitInfo`** struct defined in [`backends/imgui_impl_vulkan.h`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_vulkan.h) (lines 84-104). This container holds your Vulkan instance, device, queues, and optional dynamic-rendering flags.

You must populate every field before calling `ImGui_ImplVulkan_Init()`:

```cpp
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance               = vkInstance;
init_info.PhysicalDevice         = vkPhysicalDevice;
init_info.Device                 = vkDevice;
init_info.QueueFamily            = graphicsQueueFamily;
init_info.Queue                  = graphicsQueue;
init_info.DescriptorPoolSize     = 0;                 // Let backend allocate
init_info.MinImageCount          = 2;                 // Must be >= swap-chain images
init_info.ImageCount             = swapChainImages;   // In-flight frames
init_info.Allocator              = nullptr;           // Default Vulkan allocator
init_info.CheckVkResultFn        = [](VkResult err){ /* validation */ };
init_info.UseDynamicRendering    = true;              // Optional: Vulkan 1.3 path

ImGui_ImplVulkan_Init(&init_info);

```

The `CheckVkResultFn` callback is mandatory for debug builds—it catches validation errors during pipeline creation and buffer allocation.

### Descriptor Pool Management

The backend requires a **descriptor pool** with at least `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE` sampled-image descriptors and `IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE` sampler descriptors. You have two options:

1. **Backend-managed pool**: Set `DescriptorPoolSize` > 0 in `InitInfo`. The backend automatically creates a pool inside `ImGui_ImplVulkan_CreateDeviceObjects()` (lines 1153-1190 in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp)).
2. **User-provided pool**: Create your own pool satisfying the minimum counts and pass it via `DescriptorPool` in `InitInfo`.

If you manage the pool yourself, ensure it remains valid for the lifetime of the ImGui context and supports freeing individual descriptor sets.

## Texture Registration and ImTextureID

Unlike OpenGL backends that use integer texture IDs, the Vulkan backend uses **`VkDescriptorSet`** as `ImTextureID`. This design eliminates glue code by allowing ImGui to bind sampled images directly.

To register a custom texture, call **`ImGui_ImplVulkan_AddTexture()`** (implementation in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) lines 1329-1336):

```cpp
VkImageView myTextureView = /* your image view */;
VkDescriptorSet texDescSet = ImGui_ImplVulkan_AddTexture(
    myTextureView, 
    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

ImTextureID myImTextureID = (ImTextureID)texDescSet;
ImGui::Image(myImTextureID, ImVec2(256, 256));

```

The backend binds this descriptor set before each draw call. Store the returned `VkDescriptorSet` for reuse across frames—repeatedly calling `AddTexture` for the same image view creates redundant descriptor sets.

## Rendering and Pipeline Management

### Frame Submission Workflow

Each frame follows a strict sequence:

```cpp
// 1. Start ImGui frame
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();

// 2. Build UI...
ImGui::Render();

// 3. Record Vulkan commands
ImDrawData* draw_data = ImGui::GetDrawData();
ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffer, VK_NULL_HANDLE);

```

The **`ImGui_ImplVulkan_RenderDrawData()`** function (lines 5530-5560 in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp)) handles vertex/index buffer allocation via `CreateOrResizeBuffer` (lines 665-698), respecting `MinAllocationSize` to keep validation layers satisfied.

### Swap-Chain Recreation

When your swap chain changes, you must update the backend's pipeline. Call **`ImGui_ImplVulkan_CreateMainPipeline()`** with updated `PipelineInfoMain` before the next render:

```cpp
// After swap-chain recreation
init_info.ImageCount = newSwapChainImageCount;
ImGui_ImplVulkan_SetMinImageCount(newSwapChainImageCount);
ImGui_ImplVulkan_CreateMainPipeline(&init_info.PipelineInfoMain);

```

This recreates the graphics pipeline at lines 1174-1215 in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp), ensuring compatibility with your new render pass or dynamic rendering configuration.

## Dynamic Rendering Support (Vulkan 1.3+)

If you target Vulkan 1.3 or have the `VK_KHR_dynamic_rendering` extension, enable **`UseDynamicRendering`** in `InitInfo` and populate `PipelineInfoMain.PipelineRenderingCreateInfo`. This bypasses the traditional `VkRenderPass` requirement, simplifying swap-chain recreation:

```cpp
init_info.UseDynamicRendering = true;
init_info.PipelineInfoMain.PipelineRenderingCreateInfo = {
    VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
    nullptr,
    0,
    1,
    &colorAttachmentFormat
};

```

The backend automatically selects the dynamic rendering path when `IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING` is defined, eliminating render-pass boilerplate while maintaining full compatibility with the standard path.

## State Management and Validation

### Scissor and Viewport Reset

The backend always pushes a full-viewport scissor at the end of `ImGui_ImplVulkan_RenderDrawData` (lines 6969-6975) to prevent state leakage. However, if your pipelines use `VK_DYNAMIC_STATE_VIEWPORT` or `VK_DYNAMIC_STATE_SCISSOR`, you must reset these states after calling `RenderDrawData`:

```cpp
ImGui_ImplVulkan_RenderDrawData(draw_data, cmd, VK_NULL_HANDLE);

// Reset your dynamic state
vkCmdSetViewport(cmd, 0, 1, &myViewport);
vkCmdSetScissor(cmd, 0, 1, &myScissor);

```

### Render State Access

For custom draw callbacks, access the current Vulkan state via **`ImGui_ImplVulkan_RenderState`** exposed through `ImGui::GetPlatformIO().Renderer_RenderState` (defined in [`imgui_impl_vulkan.h`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.h) lines 68-73). This struct provides the active `VkCommandBuffer`, `VkPipeline`, and `VkPipelineLayout`:

```cpp
void MyCustomCallback(const ImDrawList*, const ImDrawCmd* cmd)
{
    ImGui_ImplVulkan_RenderState* rs = 
        (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
    // Access rs->CommandBuffer, rs->PipelineLayout, etc.
}

```

## Multi-Context and Threading Considerations

The backend stores per-context data in `io.BackendRendererUserData` as **`ImGui_ImplVulkan_Data`**. While multiple viewports share this data within a single context, the implementation is **single-context oriented**. For multi-context setups (e.g., multiple ImGui instances across threads), you must duplicate the renderer state manually, ensuring each context has its own `ImGui_ImplVulkan_InitInfo` and descriptor pools.

## Summary

- **Use the zero-modification API**: Stick to `ImGui_ImplVulkan_*` functions and avoid editing [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) to ensure clean updates.
- **Initialize completely**: Fill every field of `ImGui_ImplVulkan_InitInfo`, including `CheckVkResultFn` and proper descriptor pool sizing.
- **Manage descriptor sets**: Treat `ImTextureID` as `VkDescriptorSet` via `ImGui_ImplVulkan_AddTexture()`, caching results for reuse.
- **Handle swap-chain changes**: Recreate the main pipeline using `ImGui_ImplVulkan_CreateMainPipeline()` after surface format or size changes.
- **Consider dynamic rendering**: Enable `UseDynamicRendering` for Vulkan 1.3 to eliminate render-pass dependencies.
- **Reset dynamic state**: Re-establish viewport and scissor after `ImGui_ImplVulkan_RenderDrawData()` if your engine uses dynamic pipeline states.

## Frequently Asked Questions

### How do I initialize the Vulkan backend for ImGui?

Initialize by filling **`ImGui_ImplVulkan_InitInfo`** with your Vulkan instance, physical device, logical device, queue family, and queue. Set `DescriptorPoolSize` or provide your own `DescriptorPool`, then call `ImGui_ImplVulkan_Init(&initInfo)`. Ensure `MinImageCount` matches your swap-chain configuration to prevent validation errors.

### What is the minimum descriptor pool size required for ImGui Vulkan?

The backend requires at least **`IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE`** sampled-image descriptors and **`IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE`** sampler descriptors. You can let the backend allocate this by setting `DescriptorPoolSize` > 0 in `InitInfo`, or create your own pool satisfying these minimums.

### How do I display custom textures in ImGui with Vulkan?

Call **`ImGui_ImplVulkan_AddTexture(VkImageView, VkImageLayout)`** to obtain a `VkDescriptorSet`, cast it to `ImTextureID`, and pass it to `ImGui::Image()`. The function is located in [`imgui_impl_vulkan.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_vulkan.cpp) lines 1329-1336. Store the returned descriptor set for reuse rather than calling `AddTexture` every frame.

### How do I handle swap chain recreation with ImGui Vulkan?

After recreating your swap chain, update `init_info.ImageCount` to match the new image count, call **`ImGui_ImplVulkan_SetMinImageCount()`** if needed, and invoke **`ImGui_ImplVulkan_CreateMainPipeline()`** with your updated `PipelineInfoMain` before the next `ImGui_ImplVulkan_RenderDrawData` call. This ensures the internal pipeline matches your new surface format and dimensions.