The previous post covered how the pixels are laid out (DRM
modifiers). This is the mechanism that gets them into Vulkan: a
DMA-Buf is a kernel-owned fd that references GPU memory allocated by
another driver (VAAPI, V4L2, GBM, a camera). VK_EXT_external_memory_dma_buf lets you claim that fd as VkDeviceMemory and bind it to a
VkImage — the decode result is a texture, no copy.
The pipeline
flow.txt
decoder (VAAPI) ──► DMA-Buf fd ──► vkGetMemoryFdPropertiesKHR
(validate: is it a dma-buf, which
memory types accept it)
──► vkAllocateMemory(ImportMemoryFdInfoKHR)
──► vkBindImageMemory → VkImage
──► sampled in shaderOne fd → one VkDeviceMemory → one VkImage. The kernel keeps the
backing pages; Vulkan just maps the same physical memory.
The code
import.cpp
// 1. validate the fd is importable + get allowed memory types:
vk::MemoryFdPropertiesKHR fdProps =
dev.getMemoryFdPropertiesKHR(
vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT, fd);
// fdProps.memoryTypeBits — which memory types can host it
// 2. allocate with the fd chained in:
vk::ImportMemoryFdInfoKHR imp{};
imp.handleType = vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT;
imp.fd = dup(fd); // Vulkan takes ownership of its own fd
vk::MemoryAllocateInfo mai{};
mai.pNext = &imp;
mai.allocationSize = size; // from the image's mem requirements
mai.memoryTypeIndex = pickType(fdProps.memoryTypeBits,
eDeviceLocal);
auto mem = dev.allocateMemory(mai);
dev.bindImageMemory(img, mem, 0);The dup(fd) matters — vkAllocateMemory consumes the fd; you keep
your original for the DMA-Buf’s other users.
Multi-plane images
planes.txt
NV12 DMA-Buf = one fd, two planes (Y, UV):
→ VkImage per plane OR VkImage with multiple planes
(eG8B8R82Plane420Unorm + VK_IMAGE_CREATE_DISJOINT +
vkBindImageMemory2 per plane — the disjoint path)
→ OR: two separate VkImages (R8 + R8G8), one fd per plane —
VAAPI often splits it this way per-layerHandle both layouts: one multi-plane layer or a
per-plane fd — the AVDRMFrameDescriptor tells you which.
Ownership and lifetime
- The fd is the lifetime — when the decoder reuses the buffer the
fd may be recycled; keep the
VkImagealive until the decoder signals the buffer is free. VK_EXT_queue_family_foreignis the sync half — the DMA-Buf’s data is written by a foreign queue (the decoder’s); a queue-family transfer makes the writes visible.- Modifiers decide the tiling — the DMA-Buf import doesn’t set the
layout;
eDrmFormatModifierEXT(previous post) does.
Practical rules
- Always
dup()the fd —vkAllocateMemorytakes ownership of the passed fd; a shared original lets the exporter keep the buffer. - Check
memoryTypeBitsfromgetMemoryFdPropertiesKHR— an imported DMA-Buf may only land inDEVICE_LOCALtypes on some drivers. VkImagecreation must declareexternalMemoryusage +eDrmFormatModifierEXTtiling — the combination signals “foreign memory, modifier layout”.- The fd’s
offset/strideinAVDRMFrameDescriptor≠ the image’s internal offsets —VkImageDrmFormatModifierExplicitCreateInfoEXTpins them if needed; the simplepDrmFormatModifierslist lets the driver pick. - On a failure path, fall back to
av_hwframe_transfer_data+ CPU staging — the import is the fast path, not the only path.
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow