Backends: OSX, Metal: Store backend data in a per-context struct, allowing to use these backends with multiple contexts. (#5203, #5221, #4141)

# Conflicts:
#	docs/CHANGELOG.txt
diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm
index 3d5772e..744f590 100644
--- a/backends/imgui_impl_metal.mm
+++ b/backends/imgui_impl_metal.mm
@@ -12,6 +12,7 @@
 
 // CHANGELOG
 // (minor and older changes stripped away, please see git history for details)
+//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
 //  2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code).
 //  2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file.
 //  2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464)
@@ -54,6 +55,7 @@
 // renderer backend. Stores the render pipeline state cache and the default
 // font texture, and manages the reusable buffer cache.
 @interface MetalContext : NSObject
+@property (nonatomic, strong) id<MTLDevice> device;
 @property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
 @property (nonatomic, strong) FramebufferDescriptor *framebufferDescriptor; // framebuffer descriptor for current frame; transient
 @property (nonatomic, strong) NSMutableDictionary *renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
@@ -77,7 +79,16 @@
         commandEncoder:(id<MTLRenderCommandEncoder>)commandEncoder;
 @end
 
-static MetalContext *g_sharedMetalContext = nil;
+struct ImGui_ImplMetal_Data
+{
+    MetalContext*            SharedMetalContext;
+
+    ImGui_ImplMetal_Data()  { memset(this, 0, sizeof(*this)); }
+};
+
+static ImGui_ImplMetal_Data*     ImGui_ImplMetal_CreateBackendData()  { return IM_NEW(ImGui_ImplMetal_Data)(); }
+static ImGui_ImplMetal_Data*     ImGui_ImplMetal_GetBackendData()     { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : NULL; }
+static void                      ImGui_ImplMetal_DestroyBackendData() { IM_DELETE(ImGui_ImplMetal_GetBackendData()); }
 
 #ifdef IMGUI_IMPL_METAL_CPP
 
@@ -119,16 +130,14 @@
 
 bool ImGui_ImplMetal_Init(id<MTLDevice> device)
 {
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_CreateBackendData();
     ImGuiIO& io = ImGui::GetIO();
+    io.BackendRendererUserData = (void*)bd;
     io.BackendRendererName = "imgui_impl_metal";
     io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
 
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        g_sharedMetalContext = [[MetalContext alloc] init];
-    });
-
-    ImGui_ImplMetal_CreateDeviceObjects(device);
+    bd->SharedMetalContext = [[MetalContext alloc] init];
+    bd->SharedMetalContext.device = device;
 
     return true;
 }
@@ -136,42 +145,48 @@
 void ImGui_ImplMetal_Shutdown()
 {
     ImGui_ImplMetal_DestroyDeviceObjects();
+    ImGui_ImplMetal_DestroyBackendData();
 }
 
 void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor *renderPassDescriptor)
 {
-    IM_ASSERT(g_sharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?");
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    IM_ASSERT(bd->SharedMetalContext != nil && "No Metal context. Did you call ImGui_ImplMetal_Init() ?");
+    bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
 
-    g_sharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor];
+    if (bd->SharedMetalContext.depthStencilState == nil)
+        ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device);
 }
 
 // Metal Render function.
 void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
 {
-    [g_sharedMetalContext renderDrawData:draw_data commandBuffer:commandBuffer commandEncoder:commandEncoder];
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    [bd->SharedMetalContext renderDrawData:draw_data commandBuffer:commandBuffer commandEncoder:commandEncoder];
 }
 
 bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device)
 {
-    [g_sharedMetalContext makeFontTextureWithDevice:device];
-
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
     ImGuiIO& io = ImGui::GetIO();
-    io.Fonts->SetTexID((__bridge void *)g_sharedMetalContext.fontTexture); // ImTextureID == void*
+    [bd->SharedMetalContext makeFontTextureWithDevice:device];
+    io.Fonts->SetTexID((__bridge void *)bd->SharedMetalContext.fontTexture); // ImTextureID == void*
 
-    return (g_sharedMetalContext.fontTexture != nil);
+    return (bd->SharedMetalContext.fontTexture != nil);
 }
 
 void ImGui_ImplMetal_DestroyFontsTexture()
 {
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
     ImGuiIO& io = ImGui::GetIO();
-    g_sharedMetalContext.fontTexture = nil;
+    bd->SharedMetalContext.fontTexture = nil;
     io.Fonts->SetTexID(nullptr);
 }
 
 bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
 {
-    [g_sharedMetalContext makeDeviceObjectsWithDevice:device];
-
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
+    [bd->SharedMetalContext makeDeviceObjectsWithDevice:device];
     ImGui_ImplMetal_CreateFontsTexture(device);
 
     return true;
@@ -179,8 +194,9 @@
 
 void ImGui_ImplMetal_DestroyDeviceObjects()
 {
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
     ImGui_ImplMetal_DestroyFontsTexture();
-    [g_sharedMetalContext emptyRenderPipelineStateCache];
+    [bd->SharedMetalContext emptyRenderPipelineStateCache];
 }
 
 #pragma mark - MetalBuffer implementation
@@ -458,8 +474,9 @@
             vertexBuffer:(MetalBuffer *)vertexBuffer
       vertexBufferOffset:(size_t)vertexBufferOffset
 {
+    ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
     [commandEncoder setCullMode:MTLCullModeNone];
-    [commandEncoder setDepthStencilState:g_sharedMetalContext.depthStencilState];
+    [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState];
 
     // Setup viewport, orthographic projection matrix
     // Our visible imgui space lies from draw_data->DisplayPos (top left) to
diff --git a/backends/imgui_impl_osx.mm b/backends/imgui_impl_osx.mm
index 3c6635c..e733541 100644
--- a/backends/imgui_impl_osx.mm
+++ b/backends/imgui_impl_osx.mm
@@ -23,6 +23,7 @@
 
 // CHANGELOG
 // (minor and older changes stripped away, please see git history for details)
+//  2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts.
 //  2022-03-22: Inputs: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key
 //  2022-02-07: Inputs: Forward keyDown/keyUp events to OS when unused by dear imgui.
 //  2022-01-31: Fix building with old Xcode versions that are missing gamepad features.
@@ -57,13 +58,22 @@
 @class KeyEventResponder;
 
 // Data
-static double               g_HostClockPeriod = 0.0;
-static double               g_Time = 0.0;
-static NSCursor*            g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
-static bool                 g_MouseCursorHidden = false;
-static ImFocusObserver*     g_FocusObserver = nil;
-static KeyEventResponder*   g_KeyEventResponder = nil;
-static NSTextInputContext*  g_InputContext = nil;
+struct ImGui_ImplOSX_Data
+{
+    double                  HostClockPeriod;
+    double                  Time;
+    NSCursor*               MouseCursors[ImGuiMouseCursor_COUNT];
+    bool                    MouseCursorHidden;
+    ImFocusObserver*        FocusObserver;
+    KeyEventResponder*      KeyEventResponder;
+    NSTextInputContext*     InputContext;
+
+    ImGui_ImplOSX_Data()    { memset(this, 0, sizeof(*this)); }
+};
+
+static ImGui_ImplOSX_Data*  ImGui_ImplOSX_CreateBackendData()  { return IM_NEW(ImGui_ImplOSX_Data)(); }
+static ImGui_ImplOSX_Data*  ImGui_ImplOSX_GetBackendData()     { return (ImGui_ImplOSX_Data*)ImGui::GetIO().BackendPlatformUserData; }
+static void                 ImGui_ImplOSX_DestroyBackendData() { IM_DELETE(ImGui_ImplOSX_GetBackendData()); }
 
 // Undocumented methods for creating cursors.
 @interface NSCursor()
@@ -75,14 +85,16 @@
 
 static void InitHostClockPeriod()
 {
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
     struct mach_timebase_info info;
     mach_timebase_info(&info);
-    g_HostClockPeriod = 1e-9 * ((double)info.denom / (double)info.numer); // Period is the reciprocal of frequency.
+    bd->HostClockPeriod = 1e-9 * ((double)info.denom / (double)info.numer); // Period is the reciprocal of frequency.
 }
 
 static double GetMachAbsoluteTimeInSeconds()
 {
-    return (double)mach_absolute_time() * g_HostClockPeriod;
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    return (double)mach_absolute_time() * bd->HostClockPeriod;
 }
 
 /**
@@ -363,6 +375,8 @@
 bool ImGui_ImplOSX_Init(NSView* view)
 {
     ImGuiIO& io = ImGui::GetIO();
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_CreateBackendData();
+    io.BackendPlatformUserData = (void*)bd;
 
     // Setup backend capabilities flags
     io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;           // We can honor GetMouseCursor() values (optional)
@@ -372,16 +386,16 @@
     io.BackendPlatformName = "imgui_impl_osx";
 
     // Load cursors. Some of them are undocumented.
-    g_MouseCursorHidden = false;
-    g_MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor];
-    g_MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor];
-    g_MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor];
-    g_MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor];
-    g_MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor];
-    g_MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
-    g_MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
-    g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
-    g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
+    bd->MouseCursorHidden = false;
+    bd->MouseCursors[ImGuiMouseCursor_Arrow] = [NSCursor arrowCursor];
+    bd->MouseCursors[ImGuiMouseCursor_TextInput] = [NSCursor IBeamCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = [NSCursor closedHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_Hand] = [NSCursor pointingHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = [NSCursor operationNotAllowedCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor];
+    bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor];
 
     // Note that imgui.cpp also include default OSX clipboard handlers which can be enabled
     // by adding '#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS' in imconfig.h and adding '-framework ApplicationServices' to your linker command-line.
@@ -412,44 +426,45 @@
         return s_clipboard.Data;
     };
 
-    g_FocusObserver = [[ImFocusObserver alloc] init];
-    [[NSNotificationCenter defaultCenter] addObserver:g_FocusObserver
+    bd->FocusObserver = [[ImFocusObserver alloc] init];
+    [[NSNotificationCenter defaultCenter] addObserver:bd->FocusObserver
                                              selector:@selector(onApplicationBecomeActive:)
                                                  name:NSApplicationDidBecomeActiveNotification
                                                object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:g_FocusObserver
+    [[NSNotificationCenter defaultCenter] addObserver:bd->FocusObserver
                                              selector:@selector(onApplicationBecomeInactive:)
                                                  name:NSApplicationDidResignActiveNotification
                                                object:nil];
 
     // Add the NSTextInputClient to the view hierarchy,
     // to receive keyboard events and translate them to input text.
-    g_KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
-    g_InputContext = [[NSTextInputContext alloc] initWithClient:g_KeyEventResponder];
-    [view addSubview:g_KeyEventResponder];
+    bd->KeyEventResponder = [[KeyEventResponder alloc] initWithFrame:NSZeroRect];
+    bd->InputContext = [[NSTextInputContext alloc] initWithClient:bd->KeyEventResponder];
+    [view addSubview:bd->KeyEventResponder];
 
     // Some events do not raise callbacks of AppView in some circumstances (for example when CMD key is held down).
     // This monitor taps into global event stream and captures these events.
     NSEventMask eventMask = NSEventMaskFromType(NSKeyUp) | NSEventMaskFlagsChanged;
     [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^NSEvent * _Nullable(NSEvent *event)
     {
-        ImGui_ImplOSX_HandleEvent(event, g_KeyEventResponder);
+        ImGui_ImplOSX_HandleEvent(event, bd->KeyEventResponder);
         return event;
     }];
 
     io.SetPlatformImeDataFn = [](ImGuiViewport* viewport, ImGuiPlatformImeData* data) -> void
     {
+        ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
         if (data->WantVisible)
         {
-            [g_InputContext activate];
+            [bd->InputContext activate];
         }
         else
         {
-            [g_InputContext discardMarkedText];
-            [g_InputContext invalidateCharacterCoordinates];
-            [g_InputContext deactivate];
+            [bd->InputContext discardMarkedText];
+            [bd->InputContext invalidateCharacterCoordinates];
+            [bd->InputContext deactivate];
         }
-        [g_KeyEventResponder setImePosX:data->InputPos.x imePosY:data->InputPos.y + data->InputLineHeight];
+        [bd->KeyEventResponder setImePosX:data->InputPos.x imePosY:data->InputPos.y + data->InputLineHeight];
     };
 
     return true;
@@ -457,11 +472,14 @@
 
 void ImGui_ImplOSX_Shutdown()
 {
-    g_FocusObserver = NULL;
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
+    bd->FocusObserver = NULL;
+    ImGui_ImplOSX_DestroyBackendData();
 }
 
 static void ImGui_ImplOSX_UpdateMouseCursor()
 {
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
     ImGuiIO& io = ImGui::GetIO();
     if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
         return;
@@ -470,23 +488,23 @@
     if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
     {
         // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
-        if (!g_MouseCursorHidden)
+        if (!bd->MouseCursorHidden)
         {
-            g_MouseCursorHidden = true;
+            bd->MouseCursorHidden = true;
             [NSCursor hide];
         }
     }
     else
     {
-        NSCursor* desired = g_MouseCursors[imgui_cursor] ?: g_MouseCursors[ImGuiMouseCursor_Arrow];
+        NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow];
         // -[NSCursor set] generates measureable overhead if called unconditionally.
         if (desired != NSCursor.currentCursor)
         {
             [desired set];
         }
-        if (g_MouseCursorHidden)
+        if (bd->MouseCursorHidden)
         {
-            g_MouseCursorHidden = false;
+            bd->MouseCursorHidden = false;
             [NSCursor unhide];
         }
     }
@@ -553,15 +571,18 @@
 
 static void ImGui_ImplOSX_UpdateImePosWithView(NSView* view)
 {
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
     ImGuiIO& io = ImGui::GetIO();
     if (io.WantTextInput)
-        [g_KeyEventResponder updateImePosWithView:view];
+        [bd->KeyEventResponder updateImePosWithView:view];
 }
 
 void ImGui_ImplOSX_NewFrame(NSView* view)
 {
-    // Setup display size
+    ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
     ImGuiIO& io = ImGui::GetIO();
+
+    // Setup display size
     if (view)
     {
         const float dpi = (float)[view.window backingScaleFactor];
@@ -570,14 +591,14 @@
     }
 
     // Setup time step
-    if (g_Time == 0.0)
+    if (bd->Time == 0.0)
     {
         InitHostClockPeriod();
-        g_Time = GetMachAbsoluteTimeInSeconds();
+        bd->Time = GetMachAbsoluteTimeInSeconds();
     }
     double current_time = GetMachAbsoluteTimeInSeconds();
-    io.DeltaTime = (float)(current_time - g_Time);
-    g_Time = current_time;
+    io.DeltaTime = (float)(current_time - bd->Time);
+    bd->Time = current_time;
 
     ImGui_ImplOSX_UpdateMouseCursor();
     ImGui_ImplOSX_UpdateGamepads();
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index f0c4db9..5ebec7d 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -99,6 +99,8 @@
 - Backends: SDL: Added support for extra mouse buttons (SDL_BUTTON_X1/SDL_BUTTON_X2). (#5125) [@sgiurgiu]
 - Backends: SDL, OpenGL3: Fixes to facilitate building on AmigaOS4. (#5190) [@afxgroup]
 - Backends: OSX: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key (#5128) [@thedmd]
+- Backends: OSX, Metal: Store backend data in a per-context struct, allowing to use these backends with
+  multiple contexts. (#5203, #5221, #4141) [@noisewuwei]
 - Examples: Emscripten+WebGPU: Fix building for latest WebGPU specs. (#3632)