Every Linux GPU-adjacent API has its own buffer objects — V4L2 buffers,
VA-API surfaces, Vulkan VkDeviceMemory, GBM gbm_bo. Copying frames
between them wastes bandwidth and CPU. DMA-Buf is the kernel-side
mechanism that makes the same physical memory visible to all of them:
a buffer allocated by one driver is exported as a plain file
descriptor, and any other driver can mmap or import it.
The model
producer driver consumer driver
(V4L2 / VA-API / GPU) (Vulkan / OpenCL / other GPU)
│ │
▼ ▼
allocates buffer ──export──► dma-buf fd ──import──► same pages,
zero copiesA dma-buf fd is not the memory — it’s a reference to a kernel
struct dma_buf that owns the backing pages plus bookkeeping (the
exporting driver, an ops table for map/sync, and an fd is the
handle, so all the usual fd rules apply: dup(), SCM_RIGHTS passing
over unix sockets, FD_CLOEXEC on exec, etc.).
Why an fd and not a pointer
File descriptors are the kernel’s existing capability mechanism:
dup()shares the same dma-buf across processesSCM_RIGHTSoverunix(7)sockets hands it to an unrelated processpoll()on the fd signals buffer readiness (fence)- the kernel refcount pins the pages until the last fd closes
The exporter keeps a DMA reservation object (dma_resv) inside — fences
attached to it let consumers wait for producer writes to complete.
The modifier problem — the #1 gotcha
A bare VkDeviceMemory-sized blob isn’t enough: GPUs don’t store
images linearly. They tile and compress them, and each vendor’s
tiling is different. That’s why dma-buf pairs with DRM format
modifiers (drm_fourcc.h, DRM_FORMAT_MOD_*):
DRM_FORMAT_MOD_LINEAR = 0 — plain linear
I915_FORMAT_MOD_X_TILED = 0x100000000000001
I915_FORMAT_MOD_Y_TILED = 0x100000000000002 (Intel)
DRM_FORMAT_MOD_AMD_GFX9_GFX10 = vendor-tiled (AMD)The modifier is a 64-bit value saying how the bytes are laid out.
Both sides must agree on it — Vulkan queries support via
VK_EXT_image_drm_format_modifier, and the exporter must be told the
consumer’s modifier list.
The failure mode is silent. If the modifier doesn’t match, the
import may succeed — the driver creates a VkImage happily — and
the sampled pixels are garbage (tiled bytes read as linear). There is
no error, only wrong colors. Always intersect the exporter’s modifiers
with vkGetPhysicalDeviceImageFormatProperties2 +
VkPhysicalDeviceImageDrmFormatModifierInfoEXT results before
committing.
The Vulkan side
VK_EXT_external_memory_dma_buf imports the fd:
// 1. Query which modifiers the GPU accepts for this format:
vk::PhysicalDeviceImageFormatInfo2 fmtInfo{};
fmtInfo.setFormat(vk::Format::eR8Unorm)
.setType(vk::ImageType::e2D)
.setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
.setUsage(vk::ImageUsageFlagBits::eSampled);
// … DrmFormatModifier list in pNext → enumerate supported modifiers
// 2. Create the image with the negotiated modifier:
vk::ImageDrmFormatModifierExplicitCreateInfoEXT modInfo{};
modInfo.setDrmFormatModifier(chosenModifier)
.setPlaneLayouts(planeLayouts);
vk::ImageCreateInfo ici{};
ici.setPNext(&modInfo)
.setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
.setExternalMemoryHandleTypeFlags(
vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT);
// …
// 3. Import the fd:
vk::ImportMemoryFdInfoKHR import{};
import.setHandleType(vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT)
.setFd(dmaBufFd)
.setMemory(memory.get());
device.bindImageMemory2(bindInfos); // or vkBindImageMemory + ImportInfo
vkImportMemoryFdKHR consumes the fd on success — the caller must
not close() it afterwards (double-close recycles the number into a
later import → driver samples freed BOs → GPUVM fault).
Where it shows up
- V4L2 → Vulkan:
VIDIOC_EXPBUFexports capture buffers; import as textures — zero-copy camera ingest. - VA-API → Vulkan:
av_hwframe_map()produces anAVDRMFrameDescriptor(the dma-buf fds + fourcc + modifier) that imports per-plane asVkImage. - Vulkan → Vulkan across processes: export
VkDeviceMemoryviavkGetMemoryFdKHR— but the fd is a dma-buf, so the importer needseDmaBufEXT, noteOpaqueFd.
Working examples for each path:
Example: VulkanExamples/p48_v4l2
Example: VulkanExamples/p49_vaapi
Example: VulkanExamples/p70_ipc
Practical rules
- Always negotiate modifiers — never assume
DRM_FORMAT_MOD_LINEAR. - The fd is consumable — track ownership carefully (
-1after successful import). FD_CLOEXECbites when exec’ing a child that should import the fd — clear the flag beforefork.- The kernel serializes access via
dma_resvfences — useDMA_BUF_IOCTL_SYNCor the equivalent in-driver sync to respect producer fences.