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
- Skip rendering/presenting while the window is minimized or has zero-size (check before Present).
- Clamp back-buffer size to at least 1x1 and to current surface extent so recreation converges.
- Update Vulkan drivers / compositor environment if recreation never converges.
- 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
- Skip Present while the window is minimized or has zero extent
- Clamp back-buffer sizes to at least 1x1 and to the current surface capabilities
- Handle resize events by pausing rendering, not by forcing immediate presents
- Read the VkResult in the message to distinguish out-of-date (transient) from device-lost (fatal)
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
- Required Vulkan extension
- DeviceWindowHandle cannot be null
- Cannot create a swapchain: Vulkan surface extensions are…
- Only SDL is supported for the time being on Linux
- Can't resize VirtualFileStream if endPosition is not -1.
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)