From 1d8f6d8ee94cced96cd10033592bc429609f14f7 Mon Sep 17 00:00:00 2001 From: Francis De Brabandere Date: Wed, 17 Jun 2026 16:05:02 +0200 Subject: [PATCH] Vulkan: do not block acquiring window swap chain images (#3752) Acquiring waited forever for an image on every swap chain from the one submit thread, so a single swap chain that is no longer served (e.g. a fully occluded window on a Wayland compositor without fifo-v1, which stops releasing buffers) stalled rendering of all windows. Window swap chains now acquire without blocking and are skipped for the frame when no image is available; only the main back buffer may block, as that is the frame pacing. Image availability acts as natural back pressure, no compositor specific behavior is needed. --- src/renderer_vk.cpp | 22 +++++++++++++++++----- src/renderer_vk.h | 4 ++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/renderer_vk.cpp b/src/renderer_vk.cpp index ae58f8a76..29887ff37 100644 --- a/src/renderer_vk.cpp +++ b/src/renderer_vk.cpp @@ -3224,7 +3224,10 @@ VK_IMPORT_DEVICE else { int64_t start = bx::getHPCounter(); - newFrameBuffer.acquire(m_commandBuffer); + // Only block acquiring the main back buffer: window swap chains may not be + // served anymore (e.g. fully occluded window on Wayland compositors without + // fifo-v1) and must not stall the whole frame. + newFrameBuffer.acquire(m_commandBuffer, &newFrameBuffer == &m_backBuffer); m_presentElapsed += bx::getHPCounter() - start; } @@ -8297,7 +8300,7 @@ VK_DESTROY return idx; } - bool SwapChainVK::acquire(VkCommandBuffer _commandBuffer) + bool SwapChainVK::acquire(VkCommandBuffer _commandBuffer, bool _block) { BGFX_PROFILER_SCOPE("SwapChainVK::acquire", kColorFrame); @@ -8321,13 +8324,22 @@ VK_DESTROY result = vkAcquireNextImageKHR( device , m_swapChain - , UINT64_MAX + , _block ? UINT64_MAX : 0 , m_lastImageAcquiredSemaphore , VK_NULL_HANDLE , &m_backBufferColorIdx ); } + if (!_block + && (VK_NOT_READY == result || VK_TIMEOUT == result) ) + { + // No image available without blocking, e.g. a fully occluded window the + // compositor stopped serving; skip this swap chain for this frame instead + // of stalling all swap chains. The semaphore was not used, undo the rotation. + m_currentSemaphore = (m_currentSemaphore + kMaxBackBuffers - 1) % kMaxBackBuffers; + return false; + } if (result != VK_SUCCESS) { @@ -8686,7 +8698,7 @@ VK_DESTROY return denseIdx; } - bool FrameBufferVK::acquire(VkCommandBuffer _commandBuffer) + bool FrameBufferVK::acquire(VkCommandBuffer _commandBuffer, bool _block) { BX_ASSERT(NULL != m_nwh, "FrameBufferVK::acquire is only valid for swap-chain framebuffers."); BGFX_PROFILER_SCOPE("FrameBufferVK::acquire", kColorFrame); @@ -8704,7 +8716,7 @@ VK_DESTROY _commandBuffer = s_renderVK->m_commandBuffer; } - const bool acquired = m_swapChain.acquire(_commandBuffer); + const bool acquired = m_swapChain.acquire(_commandBuffer, _block); m_needPresent = m_swapChain.m_needPresent; m_currentFramebuffer = m_swapChain.m_backBufferFrameBuffer[m_swapChain.m_backBufferColorIdx]; diff --git a/src/renderer_vk.h b/src/renderer_vk.h index 1b7a1bf9f..646e75a65 100644 --- a/src/renderer_vk.h +++ b/src/renderer_vk.h @@ -796,7 +796,7 @@ VK_DESTROY_FUNC(DescriptorSet); uint32_t findPresentMode(bool _vsync); TextureFormat::Enum findSurfaceFormat(TextureFormat::Enum _format, VkColorSpaceKHR _colorSpace, bool _srgb); - bool acquire(VkCommandBuffer _commandBuffer); + bool acquire(VkCommandBuffer _commandBuffer, bool _block = true); void present(); void transitionImage(VkCommandBuffer _commandBuffer); @@ -875,7 +875,7 @@ VK_DESTROY_FUNC(DescriptorSet); void resolve(); - bool acquire(VkCommandBuffer _commandBuffer); + bool acquire(VkCommandBuffer _commandBuffer, bool _block = true); void present(); void markDirty() { m_needResolve = true; }