Skip to content

Possible sync validation false positive: WRITE_AFTER_PRESENT with multiple swapchains and same-queue timeline semaphore chaining #13117

Description

@ksgfk

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:

  1. Acquires one image from each swapchain using a binary semaphore.
  2. 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.
  3. 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.
  4. Submits a final batch with no command buffers, waiting on the previous timeline value and signaling the next value.
  5. Waits for that final timeline value on the CPU using vkWaitSemaphores.
  6. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

SynchronizationSynchronization Validation Object Issue

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions