From 9304c09edcf752e13eeee21f4b8a385720a7fe63 Mon Sep 17 00:00:00 2001 From: Thomas Clegg Date: Wed, 30 Sep 2026 12:04:53 -0500 Subject: [PATCH] Metal: Fixed blit encoder released without endEncoding on shutdown. (#4005) Resource updates (texture/buffer updates, texture zero-init) open a blit encoder that is ended in submit. When updates are pending at shutdown, RendererShutdownBegin clears m_rendererInitialized, submit is skipped, and renderFrame's autorelease pool frees the still-open encoder, which Metal API validation reports as 'Command encoder released without endEncoding'. Retain the blit encoder while it's open, and end encoding before the final command buffer kick in shutdown. --- src/renderer_mtl.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/renderer_mtl.cpp b/src/renderer_mtl.cpp index 8d196a1b9..223cd5d57 100644 --- a/src/renderer_mtl.cpp +++ b/src/renderer_mtl.cpp @@ -1256,6 +1256,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames { NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); + endEncoding(); m_gpuTimer.shutdown(); m_cmd.kick(false, true); @@ -1828,8 +1829,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames #if BX_PLATFORM_OSX m_blitCommandEncoder->synchronizeResource(swapChain->m_screenshotTarget); #endif // BX_PLATFORM_OSX - m_blitCommandEncoder->endEncoding(); - m_blitCommandEncoder = NULL; + endBlitEncoding(); m_cmd.kick(false, true); m_commandBuffer = 0; @@ -3531,7 +3531,10 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames m_commandBuffer = m_cmd.alloc(); } + // Retained, since it can stay open past the end of renderFrame's autorelease pool + // when submit is skipped (e.g. resource updates pending at shutdown). m_blitCommandEncoder = m_commandBuffer->blitCommandEncoder(); + retain(m_blitCommandEncoder); } return m_blitCommandEncoder; @@ -3618,10 +3621,15 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames m_computeCommandEncoder = NULL; } + endBlitEncoding(); + } + + void endBlitEncoding() + { if (NULL != m_blitCommandEncoder) { m_blitCommandEncoder->endEncoding(); - m_blitCommandEncoder = NULL; + MTL_RELEASE_I(m_blitCommandEncoder); } } @@ -5965,11 +5973,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames } } - if (0 != m_blitCommandEncoder) - { - m_blitCommandEncoder->endEncoding(); - m_blitCommandEncoder = 0; - } + endBlitEncoding(); } void RendererContextMtl::submitUniformCache(UniformCacheState& _ucs, uint16_t _view) @@ -6014,11 +6018,7 @@ static_assert(BX_COUNTOF(s_accessNames) == Access::Count, "Invalid s_accessNames int64_t timeBegin = bx::getHPCounter(); int64_t captureElapsed = 0; - if (m_blitCommandEncoder) - { - m_blitCommandEncoder->endEncoding(); - m_blitCommandEncoder = NULL; - } + endBlitEncoding(); updateResolution(_render->m_mainSwapChain, _render->m_reset);