Almost nothing in Vulkan executes when you call it.
vkCmdDraw, vkCmdCopyBuffer, vkCmdPipelineBarrier — all of them
just append to a command buffer, a recording that the GPU
consumes later at vkQueueSubmit. The command buffer is the unit
of GPU work; the command pool is its allocator.
The lifecycle
example.txt
pool ──allocate──> cmdbuf ──begin──> record commands ──end──> submit
^ |
└──────────── reset (after fence) <──────────────────┘VkCommandPool— allocates buffers for one queue family. Cheap to allocate from, andeResetCommandBufferon the pool lets you reset buffers individually.vkBeginCommandBuffer— starts recording. WitheOneTimeSubmityou promise “recorded once, submitted once, then done” — drivers optimize for that.vkQueueSubmit— hands it to the queue asynchronously. The function returns immediately; the fence signals later.
How to use it
- One pool per thread. Pool operations are not externally synchronized — the standard pattern is pool-per-thread + per-frame buffers.
- Per-frame buffers, not per-frame pools. Allocate 2–3 buffers
(frames in flight),
reset+ re-record each frame — much cheaper than freeing pools. - Reset only after the fence. A buffer may be re-recorded only when the submit that used it has retired — that’s what the frames-in-flight fence is for.
eOneTimeSubmitfor helpers: upload/transition helpers (likevkmini::oneTime) record → submit →waitIdle→ free.
Primary vs secondary
Primary buffers are submitted to a queue; secondary buffers are
called by a primary (vkCmdExecuteCommands) — that’s the
mechanism for multi-threaded recording: N threads fill secondaries,
one primary replays them in a render pass.
How NOT to use it
- Don’t re-record a pending buffer. Classic corruption: frame 3 overwrites the cmd buffer still executing for frame 1 because no fence gated the slot.
- Don’t submit the same command buffer twice while it’s in flight — same problem, different symptom.
- Don’t create a pool per buffer — pools amortize allocation; 1000 pools ≈ 1000 mallocs of bookkeeping.
eSimultaneousUseisn’t a default — it exists for buffers submitted while a previous submission may still run; it has a cost. Prefer fence-gated reset.- Don’t forget
begin/end— commands outside a begun buffer are silently dropped into nowhere (validation catches it, if you run validation layers).
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow