Two different queues — a compute queue and a graphics queue — can’t freely share a resource. A buffer written by queue family A read by queue family B isn’t just a matter of “the semaphore says it’s done”: the driver may have cached the memory as seen by family A, and family B literally can’t see the contents without an explicit ownership transfer.
The concept
exclusive (default):
graphics queue writes buf → buf is OWNED by the graphics family
compute queue reads buf → UNDEFINED unless ownership was released
by graphics and acquired by compute
concurrent (sharingMode = CONCURRENT):
all listed families share the resource simultaneously — no transfer
needed, but the driver may pick a slower/coherent memory pathThe transfer ceremony
A transfer is two barriers, one on each queue:
// Graphics command buffer — RELEASE the buffer to compute family:
vk::BufferMemoryBarrier release{};
release.setSrcQueueFamilyIndex(graphicsFamily)
.setDstQueueFamilyIndex(computeFamily)
.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite)
.setDstAccessMask({}) // release doesn't transfer reads
.setBuffer(buf)
.setOffset(0).setSize(VK_WHOLE_SIZE);
gfxCmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eBottomOfPipe,
{}, {}, release, {});
// Compute command buffer — ACQUIRE the buffer:
vk::BufferMemoryBarrier acquire{};
acquire.setSrcQueueFamilyIndex(graphicsFamily) // SAME queue pair
.setDstQueueFamilyIndex(computeFamily)
.setSrcAccessMask({}) // acquire doesn't re-declare writes
.setDstAccessMask(vk::AccessFlagBits::eShaderRead)
.setBuffer(buf)
.setOffset(0).setSize(VK_WHOLE_SIZE);
cmpCmd.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eComputeShader,
{}, {}, acquire, {});Rule of thumb: release on the old family, acquire on the new family, both with the same src/dst queue pair. Only the first barrier is a release, the second is an acquire — the semaphore between them just makes sure the release happened before the acquire on the GPU.
gfxQueue ──[release]──► semaphore signal ──► cmpQueue ──[acquire]──► useWhy CONCURRENT is tempting and when it’s fine
vk::BufferCreateInfo bci{};
bci.setSharingMode(vk::SharingMode::eConcurrent)
.setQueueFamilyIndices({graphicsFamily, computeFamily});CONCURRENT skips the transfer entirely. It costs nothing extra on desktop GPUs where all queues share a cache hierarchy. On tile-based mobile GPUs it can force a slower, coherent memory pool — still fine for most cases. Start with CONCURRENT; add transfers only when profiling shows the concurrent path is the bottleneck.
Images need the layout transfer too
For a VkImage, ownership transfer isn’t just the queue pair — it
must carry a layout transition (even eGeneral → eGeneral counts).
The release barrier transitions out of the old queue’s view, the
acquire transitions in — and both list the same old/new layout pair.
graphics: barrier img, old=TransferSrc, new=ShaderRead,
srcQF=gfx, dstQF=compute // release
compute: barrier img, old=TransferSrc, new=ShaderRead,
srcQF=gfx, dstQF=compute // acquireThe two oldLayout/newLayout fields match on both sides — the
“transfer” is encoded in the queue-family indices, not the layouts.
The p63_asynccompute connection
The async-compute example uses exactly this pattern — compute writes a
histogram on queue family 1, graphics displays it on family 0. Since
both families are from the same physical device and share memory
coherency on discrete GPUs, CONCURRENT is a no-op cost — but the
semaphore still orders the contents, not the ownership. The transfer
becomes load-bearing only if you pick eExclusive (the default).
Example: VulkanExamples/p63_asynccompute
Practical rules
eExclusive(default) + manual transfer = most correct, most code.eConcurrent= simpler, usually free on desktop, may be slower on mobile tilers.- Never forget the transfer for
eExclusive— the debug layer won’t always catch it, and you’ll see silent garbage on the consumer side. - Timeline semaphores pair naturally: signal value N on queue A, wait on queue B — but the ownership transfer is separate and still required for exclusive resources.