How to Set Up Dear ImGui with Metal: Complete Integration Guide for macOS and iOS
To set up Dear ImGui with Metal, include the backend headers from the backends/ folder, initialize with ImGui_ImplMetal_Init(device), and call ImGui_ImplMetal_NewFrame(renderPassDescriptor) and ImGui_ImplMetal_RenderDrawData(drawData, commandBuffer, commandEncoder) each frame.
Dear ImGui (ocornut/imgui) ships a first-party Metal renderer that enables high-performance immediate-mode GUI rendering on Apple platforms. The backend resides in backends/imgui_impl_metal.h and backends/imgui_impl_metal.mm, implementing a standard interface for device initialization, frame setup, and draw command submission. This guide walks through the complete integration process using the actual source implementation from the official repository.
Metal Backend Architecture
The Metal backend implements the standard ImGui renderer interface through four primary functions: ImGui_ImplMetal_Init, ImGui_ImplMetal_NewFrame, ImGui_ImplMetal_RenderDrawData, and ImGui_ImplMetal_Shutdown. The architecture centers around the ImGui_ImplMetal_Data structure, which maintains a per-context MetalContext containing device references, pipeline caches, and resource pools.
Context and Device Management
The ImGui_ImplMetal_Init() function allocates the backend data structure and stores the Metal device in the MetalContext. This context persists across frames, holding static objects like depth-stencil states and samplers created via ImGui_ImplMetal_CreateDeviceObjects() (lines 84-100 in imgui_impl_metal.mm).
Pipeline State Caching
To avoid costly shader recompilation, the backend caches MTLRenderPipelineState objects keyed by framebuffer descriptors. The MetalContext::renderPipelineStateForFramebufferDescriptor method (lines 87-100) maintains a dictionary mapping pixel formats and sample counts to compiled pipelines, reusing them across frames.
Buffer Reuse Strategy
The implementation minimizes allocation churn through MetalContext::dequeueReusableBufferOfLength: (lines 51-80). This method retrieves MTLBuffer objects from a per-context cache, purging entries older than one second to balance memory usage with performance.
Texture Handling
For font atlases and user textures, ImGui_ImplMetal_UpdateTexture() creates MTLTexture instances and stores them in ImTextureData::BackendUserData as MetalTexture wrappers. The backend handles binding via setFragmentTexture during the render pass (line 89).
Integration Workflow
Follow this sequence to integrate Dear ImGui into your Metal application:
- Create Metal Device: Obtain your
MTLDeviceusingMTLCreateSystemDefaultDevice()or equivalent. - Initialize ImGui Context: Call
ImGui::CreateContext()to establish the global state. - Initialize Metal Backend: Invoke
ImGui_ImplMetal_Init(device)to allocate backend data and create device objects. - Per-Frame Execution:
- Build a
MTLRenderPassDescriptorfor your drawable. - Call
ImGui_ImplMetal_NewFrame(renderPassDescriptor)to cache framebuffer settings. - Build UI with
ImGui::NewFrame(), widget calls, andImGui::Render(). - Retrieve draw data with
ImGui::GetDrawData(). - Create command buffer and encoder from your
MTLCommandQueue. - Submit with
ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, commandEncoder). - End encoding and present the drawable.
- Build a
- Shutdown: Call
ImGui_ImplMetal_Shutdown()before releasing the Metal device.
Implementation Examples
Objective-C Integration (macOS)
This minimal example demonstrates the standard setup using Objective-C APIs:
#import <Metal/Metal.h>
#import "imgui.h"
#import "imgui_impl_metal.h"
int main(int argc, char* argv[])
{
// Create Metal infrastructure
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
CAMetalLayer* metalLayer = ...; // Configure your layer
// Initialize Dear ImGui
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(1024, 768);
ImGui_ImplMetal_Init(device);
while (!shouldQuit) {
// Configure render pass
MTLRenderPassDescriptor* rpDesc = [MTLRenderPassDescriptor renderPassDescriptor];
rpDesc.colorAttachments[0].texture = metalLayer.nextDrawable.texture;
rpDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
rpDesc.colorAttachments[0].clearColor = MTLClearColorMake(0.1, 0.1, 0.1, 1.0);
// New frame
ImGui_ImplMetal_NewFrame(rpDesc);
ImGui::NewFrame();
// Build UI
ImGui::Begin("Hello Metal");
ImGui::Text("Hello world!");
ImGui::End();
ImGui::Render();
// Encode rendering
id<MTLCommandBuffer> cmdBuf = [commandQueue commandBuffer];
id<MTLRenderCommandEncoder> encoder = [cmdBuf renderCommandEncoderWithDescriptor:rpDesc];
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), cmdBuf, encoder);
[encoder endEncoding];
[cmdBuf presentDrawable:metalLayer.nextDrawable];
[cmdBuf commit];
}
ImGui_ImplMetal_Shutdown();
return 0;
}
Metal-C++ Integration
For modern C++ projects, enable the Metal-C++ bindings by defining IMGUI_IMPL_METAL_CPP before including the header:
#define IMGUI_IMPL_METAL_CPP
#include "imgui.h"
#include "imgui_impl_metal.h"
#include <Metal/Metal.hpp>
int main()
{
MTL::Device* device = MTL::CreateSystemDefaultDevice();
MTL::CommandQueue* cmdQueue = device->newCommandQueue();
ImGui::CreateContext();
ImGui_ImplMetal_Init(device); // C++ overload
while (!quit) {
MTL::RenderPassDescriptor* rpDesc = MTL::RenderPassDescriptor::alloc()->init();
rpDesc->colorAttachments()->object(0)->setTexture(currentDrawableTexture);
rpDesc->colorAttachments()->object(0)->setLoadAction(MTL::LoadActionClear);
rpDesc->colorAttachments()->object(0)->setClearColor(MTL::ClearColor::Make(0.1f, 0.1f, 0.1f, 1.0f));
ImGui_ImplMetal_NewFrame(rpDesc);
ImGui::NewFrame();
ImGui::Begin("Metal-C++");
ImGui::Text("Running on Metal-C++");
ImGui::End();
ImGui::Render();
MTL::CommandBuffer* cmdBuf = cmdQueue->commandBuffer();
MTL::RenderCommandEncoder* encoder = cmdBuf->renderCommandEncoder(rpDesc);
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), cmdBuf, encoder);
encoder->endEncoding();
cmdBuf->presentDrawable(currentDrawable);
cmdBuf->commit();
}
ImGui_ImplMetal_Shutdown();
return 0;
}
Loading Custom Textures
To display custom images, register MTLTexture objects with ImGui's texture system:
// Create your Metal texture
id<MTLTexture> myMTLTex = ...; // Loaded via MTKTextureLoader or manual creation
// Prepare ImGui texture data
ImTextureData texData;
texData.Width = myMTLTex.width;
texData.Height = myMTLTex.height;
texData.Format = ImTextureFormat_RGBA32;
texData.TexID = (ImTextureID)(intptr_t)myMTLTex;
texData.BackendUserData = (__bridge_retained void*)[[MetalTexture alloc] initWithTexture:myMTLTex];
texData.Status = ImTextureStatus_OK;
// Register
ImGui::GetPlatformIO().Textures.push_back(&texData);
// Use in UI
ImGui::Image((ImTextureID)(intptr_t)myMTLTex, ImVec2(256, 256));
The backend automatically binds these textures during ImGui_ImplMetal_RenderDrawData() via setFragmentTexture.
Key Source Files
The Metal backend implementation resides in the backends/ directory of the ocornut/imgui repository:
backends/imgui_impl_metal.h: Declares the public API includingImGui_ImplMetal_Init(),ImGui_ImplMetal_NewFrame(),ImGui_ImplMetal_RenderDrawData(), and texture helper functions.backends/imgui_impl_metal.mm: Contains the complete Objective-C++ implementation, includingImGui_ImplMetal_Datastructure, pipeline state caching, buffer reuse logic, and the full render loop.imgui.h: Defines core structures such asImDrawData,ImTextureData, andImTextureFormatused by the backend.
Summary
- Include the backend: Add
backends/imgui_impl_metal.handimgui_impl_metal.mmto your project. - Initialize once: Call
ImGui_ImplMetal_Init(device)after creating your Metal device. - Frame preparation: Pass your
MTLRenderPassDescriptortoImGui_ImplMetal_NewFrame()to enable pipeline caching. - Submit draws: Use
ImGui_ImplMetal_RenderDrawData()inside your command encoder to handle vertex buffers, index buffers, and scissor clipping. - Cleanup: Call
ImGui_ImplMetal_Shutdown()before device destruction. - C++ support: Define
IMGUI_IMPL_METAL_CPPfor Metal-C++ API compatibility.
Frequently Asked Questions
Can I use Dear ImGui with Metal on iOS?
Yes, the Metal backend supports both macOS and iOS. The initialization and rendering code remains identical across platforms. Ensure your CAMetalLayer is properly configured for iOS view hierarchies, and the backend handles the rest through the same ImGui_ImplMetal_* interface.
How do I enable Metal-C++ bindings instead of Objective-C?
Define IMGUI_IMPL_METAL_CPP in your imconfig.h or as a compiler flag before including imgui_impl_metal.h. This enables overloads accepting MTL::Device* and MTL::RenderPassDescriptor* instead of Objective-C id<MTLDevice> pointers. The implementation uses bridging casts internally to support both APIs from the same source file.
Does the Metal backend support custom fonts and textures?
Yes. The backend implements ImGui_ImplMetal_UpdateTexture() to handle font atlas generation and user-provided textures. Store your MTLTexture references in ImTextureData::BackendUserData as MetalTexture objects. The renderer automatically binds these textures during draw submission using the texture IDs generated by ImGui.
How does the backend optimize rendering performance?
The implementation employs several optimization strategies: pipeline state caching via renderPipelineStateForFramebufferDescriptor avoids shader recompilation, buffer reuse through dequeueReusableBufferOfLength: minimizes allocation overhead by caching MTLBuffer objects for one second, and texture management ensures GPU resources persist across frames unless explicitly updated.
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 →