stride3d/stride · error · InvalidOperationException

Could not acquire swapchain image

Error message

Could not acquire swapchain image: {result}

What it means

AcquireNextImage calls vkAcquireNextImageKHR and, when it can't get an image (e.g. VK_ERROR_OUT_OF_DATE_KHR), attempts up to 3 bounded recoveries by recreating the swapchain (OnRecreated). If the surface keeps changing or remains unusable after 3 attempts, it throws InvalidOperationException with the failing VkResult embedded, since continuing without a back-buffer image is impossible.

Solutions

  1. Skip rendering/presenting while the window is minimized or has zero-size (check before Present).
  2. Clamp back-buffer size to at least 1x1 and to current surface extent so recreation converges.
  3. Update Vulkan drivers / compositor environment if recreation never converges.
  4. Inspect the VkResult in the message (e.g. ERROR_OUT_OF_DATE_KHR vs ERROR_DEVICE_LOST) — device-lost requires device re-creation, not just swapchain recreation.

Example fix

// before
swapChain.Present();
// after
if (!gameWindow.Visible || gameWindow.ClientSize.Width == 0 || gameWindow.ClientSize.Height == 0) return;
swapChain.Present();
Defensive patterns

Strategy: retry

Validate before calling

bool CanPresent(GameWindow w) => w.Visible && w.ClientSize.Width > 0 && w.ClientSize.Height > 0;

Type guard

bool SurfaceUsable(IntPtr hwnd) => hwnd != IntPtr.Zero && IsWindowVisible(hwnd) && !IsIconic(hwnd);

Try / catch

try { swapChain.Present(); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Could not acquire swapchain image")) {
    // surface never settled (minimized/zero-size): pause rendering until next frame
    skipFramesUntilResizeEnds = true;
}

Prevention

When it happens

Trigger: Present() or CreateBackBuffers() calling AcquireNextImage while the window surface is continuously resized, minimized, or destroyed, so that vkAcquireNextImageKHR keeps returning out-of-date/error results through 3 recreation attempts.

Common situations: User dragging/resizing the window rapidly on Wayland/Windows; window minimized during Present; GPU reset or swapchain invalidation during heavy window management; running under compositors that force swapchain recreation per frame.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/5009ec10f7d76e7f. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs:209

            // Wait for frame fence to be available
            GraphicsDevice.CheckResult(GraphicsDevice.NativeDeviceApi.vkWaitForFences(GraphicsDevice.NativeDevice, frameFences[currentFrameIndex], VkBool32.True, ulong.MaxValue));
            GraphicsDevice.NativeDeviceApi.vkResetFences(GraphicsDevice.NativeDevice, frameFences[currentFrameIndex]);

            AcquireNextImage();
        }

        private unsafe void AcquireNextImage()
        {
            // Get next image
            var result = GraphicsDevice.NativeDeviceApi.vkAcquireNextImageKHR(GraphicsDevice.NativeDevice, swapChain, ulong.MaxValue, acquireSemaphores[currentFrameIndex], VkFence.Null, out currentBufferIndex);
            if (result == VkResult.ErrorOutOfDateKHR || result == VkResult.ErrorSurfaceLostKHR)
            {
                // No image acquired: the surface changed while the swapchain was being (re)created
                // (continuous interactive resize) or was lost. Recreate against the current surface
                // state; bounded, as each attempt re-queries the surface so it converges once the
                // surface settles.
                if (acquireRecreateDepth >= 3)
                    throw new InvalidOperationException($"Could not acquire swapchain image: {result}");
                acquireRecreateDepth++;
                try { OnRecreated(); }
                finally { acquireRecreateDepth--; }
                return;
            }

            // SuboptimalKHR is a success code: an image was acquired and the semaphore will be
            // signaled; the next Present reports it again and recreates the swapchain.
            if (result != VkResult.SuboptimalKHR)
            {
                GraphicsDevice.CheckResult(result, "vkAcquireNextImageKHR");
            }

            // Flip render targets
            backBuffer.SetNativeHandles(swapchainImages[currentBufferIndex].NativeImage, swapchainImages[currentBufferIndex].NativeColorAttachmentView);

            lock (GraphicsDevice.QueueLock)
            {

View on GitHub (pinned to 96fad776d2)