Environment:
- OS: Windows 11, build 26200.9457
- GPU and driver version: NVIDIA GeForce RTX 4080, driver 610.74.0.0
- Vulkan SDK: 1.4.357.0
- Reported physical-device API version: 1.4.341
- Options enabled:
VK_LAYER_KHRONOS_validation with synchronization validation enabled through VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT. No validation checks are explicitly disabled. Best practices and GPU-assisted validation are not explicitly enabled.
- Loaded validation layer DLL:
C:\VulkanSDK\1.4.357.0\Bin\VkLayer_khronos_validation.dll
- Reproduces in both Debug and Release builds.
Describe the Issue
Synchronization validation reports WRITE_AFTER_PRESENT from vkQueuePresentKHR, indicating a conflict between the current presentation and a previous presentation of the same swapchain image.
The attached standalone C reproducer uses only Vulkan, Win32 window creation, and the C runtime. It does not depend on a rendering framework, GLFW, shaders, render passes, or pipelines.
It creates three windows with separate swapchains and uses a single host thread and a single graphics/present queue.
Each iteration:
- Acquires one image from each swapchain using a binary semaphore.
- Submits one command buffer per window. Each command buffer transitions the acquired image to
TRANSFER_DST_OPTIMAL, clears it with vkCmdClearColorImage, and transitions it to PRESENT_SRC_KHR.
- Each submission waits on its acquire semaphore and the previous value of a shared timeline semaphore, then signals its per-image present semaphore and the next timeline value.
- Submits a final batch with no command buffers, waiting on the previous timeline value and signaling the next value.
- Waits for that final timeline value on the CPU using
vkWaitSemaphores.
- Presents each image, waiting on its corresponding binary present semaphore.
For the first iteration, with timeline semaphore T initially at 0:
Submit A: wait acquireA + T>=0; clear A; signal presentA + T=1
Submit B: wait acquireB + T>=1; clear B; signal presentB + T=2
Submit C: wait acquireC + T>=2; clear C; signal presentC + T=3
Tail: wait T>=3; no command buffers; signal T=4
Host: vkWaitSemaphores(T>=4)
Present A: wait presentA
Present B: wait presentB
Present C: wait presentC
Timeline values continue increasing in subsequent iterations. The window submission/presentation order alternates between forward and reverse each iteration.
Relevant synchronization details:
- Submissions use
vkQueueSubmit with VkTimelineSemaphoreSubmitInfo.
- All semaphore wait stage masks are
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT.
- Both image barriers use
ALL_COMMANDS for their source and destination stage masks.
- Acquire and present use binary semaphores; no timeline semaphore is passed to either WSI operation.
- Present semaphores are allocated per swapchain image.
- Each acquire semaphore is reused only after the previous iteration's host timeline wait.
- Command buffers are also reset only after that host wait.
On this machine, the first error appears during presentation in iteration 4 (round=3 in the log).
The most surprising observation is that inserting a completely empty VkSubmitInfo immediately before each original submission, within the same vkQueueSubmit call, makes the errors disappear:
VkSubmitInfo batches[2] = {0};
batches[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
/* batches[0] has no waits, command buffers, or signals. */
batches[1] = originalSubmit;
vkQueueSubmit(queue, 2, batches, VK_NULL_HANDLE);
Expected behavior
I expect this sequence to complete without synchronization validation errors. Inserting an empty predecessor batch should not be necessary to avoid a reported presentation hazard.
If the reproducer violates a synchronization requirement, clarification of the missing dependency would also be appreciated.
Valid Usage ID
SYNC-HAZARD-WRITE-AFTER-PRESENT
The reproducer's callback prints the message text rather than the message ID prefix:
[round=3 phase=present] vkQueuePresentKHR(): WRITE_AFTER_PRESENT hazard detected. vkQueuePresentKHR presents swapchain image 0 (VkImage 0xf000000000f from VkSwapchainKHR 0xe000000000e), which was previously written by another vkQueuePresentKHR command.
No sufficient synchronization is present to ensure that a swapchain present operation does not conflict with a prior swapchain present operation.
Additional context
The attached reproducer provides three independent control modes:
| Mode |
Change from baseline |
Debug: runs reporting errors |
Release: runs reporting errors |
| Baseline |
Three windows with GPU timeline chaining |
5/5 |
5/5 |
--empty-predecessor |
Inserts an empty batch before each original submission |
0/5 |
0/5 |
--no-timeline-chain |
Removes GPU timeline waits, retaining timeline signals, the host wait, and binary acquire/present synchronization |
0/5 |
0/5 |
--one-window |
Uses one window with the same submission pattern |
0/5 |
0/5 |
Each run uses 12 iterations. These results represent 40 separate process executions.
Each baseline run logs 10 WRITE_AFTER_PRESENT errors before the layer's duplicate-message limit suppresses further reports. No other validation errors were recorded. The errors occur during the rendering/presentation loop, before cleanup.
To build and run the complete matrix, extract the attachment and run from its directory:
pwsh -File .\reproduce.ps1 -Sdk C:\VulkanSDK\1.4.357.0
The script requires CMake and defaults to the Visual Studio 18 2026 generator. Its -Generator parameter can select another installed Visual Studio generator. It writes individual logs and results.csv to build_vk_present_repro.
To run individual cases after building:
cmake -E env VK_LAYER_PATH=C:/VulkanSDK/1.4.357.0/Bin ./build_vk_present_repro/Debug/vulkan_present_repro.exe
cmake -E env VK_LAYER_PATH=C:/VulkanSDK/1.4.357.0/Bin ./build_vk_present_repro/Debug/vulkan_present_repro.exe --empty-predecessor
cmake -E env VK_LAYER_PATH=C:/VulkanSDK/1.4.357.0/Bin ./build_vk_present_repro/Debug/vulkan_present_repro.exe --no-timeline-chain
cmake -E env VK_LAYER_PATH=C:/VulkanSDK/1.4.357.0/Bin ./build_vk_present_repro/Debug/vulkan_present_repro.exe --one-window
The executable returns 1 when validation errors are recorded and 0 otherwise. The matrix script treats the baseline failure and clean control runs as the expected observations for SDK 1.4.357.0.
Baseline output excerpt
windows=3 rounds=12 timeline_chain=1 empty_predecessor=0
Validation DLL: C:\VulkanSDK\1.4.357.0\Bin\VkLayer_khronos_validation.dll
GPU: NVIDIA GeForce RTX 4080 API=1.4.341
[round=3 phase=present] vkQueuePresentKHR(): WRITE_AFTER_PRESENT hazard detected. vkQueuePresentKHR presents swapchain image 0 (VkImage 0xf000000000f from VkSwapchainKHR 0xe000000000e), which was previously written by another vkQueuePresentKHR command.
No sufficient synchronization is present to ensure that a swapchain present operation does not conflict with a prior swapchain present operation.
...
LOOP errors=10 WRITE_AFTER_PRESENT=10
TOTAL errors=10 WRITE_AFTER_PRESENT=10
vulkan_present_repro.zip
Environment:
VK_LAYER_KHRONOS_validationwith synchronization validation enabled throughVK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT. No validation checks are explicitly disabled. Best practices and GPU-assisted validation are not explicitly enabled.C:\VulkanSDK\1.4.357.0\Bin\VkLayer_khronos_validation.dllDescribe the Issue
Synchronization validation reports
WRITE_AFTER_PRESENTfromvkQueuePresentKHR, indicating a conflict between the current presentation and a previous presentation of the same swapchain image.The attached standalone C reproducer uses only Vulkan, Win32 window creation, and the C runtime. It does not depend on a rendering framework, GLFW, shaders, render passes, or pipelines.
It creates three windows with separate swapchains and uses a single host thread and a single graphics/present queue.
Each iteration:
TRANSFER_DST_OPTIMAL, clears it withvkCmdClearColorImage, and transitions it toPRESENT_SRC_KHR.vkWaitSemaphores.For the first iteration, with timeline semaphore T initially at 0:
Timeline values continue increasing in subsequent iterations. The window submission/presentation order alternates between forward and reverse each iteration.
Relevant synchronization details:
vkQueueSubmitwithVkTimelineSemaphoreSubmitInfo.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT.ALL_COMMANDSfor their source and destination stage masks.On this machine, the first error appears during presentation in iteration 4 (
round=3in the log).The most surprising observation is that inserting a completely empty
VkSubmitInfoimmediately before each original submission, within the samevkQueueSubmitcall, makes the errors disappear:Expected behavior
I expect this sequence to complete without synchronization validation errors. Inserting an empty predecessor batch should not be necessary to avoid a reported presentation hazard.
If the reproducer violates a synchronization requirement, clarification of the missing dependency would also be appreciated.
Valid Usage ID
SYNC-HAZARD-WRITE-AFTER-PRESENTThe reproducer's callback prints the message text rather than the message ID prefix:
Additional context
The attached reproducer provides three independent control modes:
--empty-predecessor--no-timeline-chain--one-windowEach run uses 12 iterations. These results represent 40 separate process executions.
Each baseline run logs 10
WRITE_AFTER_PRESENTerrors before the layer's duplicate-message limit suppresses further reports. No other validation errors were recorded. The errors occur during the rendering/presentation loop, before cleanup.To build and run the complete matrix, extract the attachment and run from its directory:
The script requires CMake and defaults to the
Visual Studio 18 2026generator. Its-Generatorparameter can select another installed Visual Studio generator. It writes individual logs andresults.csvtobuild_vk_present_repro.To run individual cases after building:
The executable returns 1 when validation errors are recorded and 0 otherwise. The matrix script treats the baseline failure and clean control runs as the expected observations for SDK 1.4.357.0.
Baseline output excerpt
vulkan_present_repro.zip