From 4b80d409e721b096b2b26e5edbf575178bdb48f0 Mon Sep 17 00:00:00 2001 From: Andy Grundman Date: Mon, 13 Apr 2026 19:16:15 -0400 Subject: [PATCH] Backends: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for synchronize(). (#9367) This also removes the dispatch onto main when adding the finished buffers back into the cache. This operation should be fine to run on any thread as long as it's inside the sync block. --- backends/imgui_impl_metal.mm | 19 ++++++++++--------- docs/CHANGELOG.txt | 4 +++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 037782bf6..7d604e6a5 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) // 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) // 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. @@ -78,6 +79,7 @@ @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) NSObject* bufferCacheLock; @property (nonatomic, assign) double lastBufferCachePurge; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @@ -328,13 +330,11 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* sharedMetalContext = bd->SharedMetalContext; [commandBuffer addCompletedHandler:^(id) { - dispatch_async(dispatch_get_main_queue(), ^{ - @synchronized(sharedMetalContext.bufferCache) - { - [sharedMetalContext.bufferCache addObject:vertexBuffer]; - [sharedMetalContext.bufferCache addObject:indexBuffer]; - } - }); + @synchronized(sharedMetalContext.bufferCacheLock) + { + [sharedMetalContext.bufferCache addObject:vertexBuffer]; + [sharedMetalContext.bufferCache addObject:indexBuffer]; + } }]; } @@ -514,6 +514,7 @@ void ImGui_ImplMetal_DestroyDeviceObjects() { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; self.bufferCache = [NSMutableArray array]; + self.bufferCacheLock = [[NSObject alloc] init]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; @@ -521,9 +522,9 @@ void ImGui_ImplMetal_DestroyDeviceObjects() - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { - uint64_t now = GetMachAbsoluteTimeInSeconds(); + double now = GetMachAbsoluteTimeInSeconds(); - @synchronized(self.bufferCache) + @synchronized(self.bufferCacheLock) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5a3051551..05dd294e1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -89,8 +89,10 @@ Other Changes: - Misc: - Minor optimization: reduce redudant label scanning in common widgets. - Backends: - - Metal: avoid redundant vertex buffer bind in SetupRenderState, which leads + - Metal: avoid redundant vertex buffer bind in `SetupRenderState()`, which leads to validation issue. (#9343) [@Hunam6] + - Metal: use a dedicated `bufferCacheLock` to avoid crashing when `bufferCache` is + replaced by a new object while being used for `@synchronize()`. (#9367) [@andygrundman] -----------------------------------------------------------------------