From e54db4ee708b99e24348f31847e8032921913989 Mon Sep 17 00:00:00 2001 From: noisewuwei <1215058854@qq.com> Date: Wed, 20 Apr 2022 21:31:44 +0800 Subject: [PATCH] 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 --- backends/imgui_impl_metal.mm | 55 +++++++++++------- backends/imgui_impl_osx.mm | 107 +++++++++++++++++++++-------------- docs/CHANGELOG.txt | 2 + 3 files changed, 102 insertions(+), 62 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 3d5772e5f..744f59011 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 device; @property (nonatomic, strong) id 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)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_CreateDeviceObjects(MTL::Device* device) bool ImGui_ImplMetal_Init(id 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 @@ bool ImGui_ImplMetal_Init(id device) 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 commandBuffer, id 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 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 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 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) 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 @@ void ImGui_ImplMetal_DestroyDeviceObjects() 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 3c6635c1a..e733541fd 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 NSTextInputContext* g_InputContext = nil; 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 @@ static ImGuiKey ImGui_ImplOSX_KeyCodeToImGuiKey(int key_code) 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 @@ bool ImGui_ImplOSX_Init(NSView* view) 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 @@ bool ImGui_ImplOSX_Init(NSView* view) 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 @@ bool ImGui_ImplOSX_Init(NSView* view) 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 @@ static void ImGui_ImplOSX_UpdateMouseCursor() 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_UpdateGamepads() 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 @@ void ImGui_ImplOSX_NewFrame(NSView* view) } // 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 f0c4db902..5ebec7da1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -99,6 +99,8 @@ Other Changes: - 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)