すべての Linux GPU 関連 API は独自のバッファオブジェクトを
持つ——V4L2 バッファ、VA-API サーフェス、Vulkan
VkDeviceMemory、GBM gbm_bo。それらの間でフレームを
コピーすると帯域幅と CPU を浪費する。DMA-Buf は同じ物理
メモリをすべてに見せるカーネル側メカニズム:1つのドライバが
割り当てたバッファがプレーンなファイルディスクリプタとして
エクスポートされ、他のドライバが mmap またはインポート
できる。
モデル
プロデューサドライバ コンシューマドライバ
(V4L2 / VA-API / GPU) (Vulkan / OpenCL / 他の GPU)
│ │
▼ ▼
バッファを割り当て ──export──► dma-buf fd ──import──► 同じページ、
ゼロコピーdma-buf fd はメモリではない——バッキングページと簿記
(エクスポートしたドライバ、map/sync の ops テーブル)を
所有するカーネル struct dma_buf への参照で、fd が
ハンドル、よって通常の fd ルールがすべて適用:dup()、
unix ソケット越しの SCM_RIGHTS 受け渡し、exec での
FD_CLOEXEC など。
なぜポインタではなく fd か
ファイルディスクリプタはカーネルの既存ケイパビリティ メカニズム:
dup()が同じ dma-buf をプロセス間で共有unix(7)ソケット越しのSCM_RIGHTSが無関係なプロセスに 渡す- fd への
poll()がバッファ準備完了をシグナル(フェンス) - カーネル refcount が最後の fd が閉じるまでページをピン
エクスポーターは内部に DMA 予約オブジェクト(dma_resv)を
保持——それにアタッチされたフェンスでコンシューマが
プロデューサの書き込み完了を待てる。
モディファイア問題——最大の落とし穴
裸の VkDeviceMemory サイズのブロブでは不十分:GPU は
イメージを線形に格納しない。タイル化と圧縮を行い、
ベンダーごとにタイル化が異なる。だから dma-buf は DRM
フォーマットモディファイア(drm_fourcc.h、
DRM_FORMAT_MOD_*)と対になる:
DRM_FORMAT_MOD_LINEAR = 0 — プレーン線形
I915_FORMAT_MOD_X_TILED = 0x100000000000001
I915_FORMAT_MOD_Y_TILED = 0x100000000000002 (Intel)
DRM_FORMAT_MOD_AMD_GFX9_GFX10 = ベンダータイル (AMD)モディファイアはバイトがどのように配置されるかを言う 64
ビット値。両側が一致しなければならない——Vulkan は
VK_EXT_image_drm_format_modifier でサポートをクエリし、
エクスポーターにはコンシューマのモディファイアリストを
伝える必要。
失敗モードは静か。 モディファイアが一致しなければ、
インポートは成功するかも——ドライバは喜んで VkImage を
作成——そしてサンプルされたピクセルはゴミ(線形として
読まれたタイルバイト)。エラーなし、間違った色のみ。
コミット前に常にエクスポーターのモディファイアと
vkGetPhysicalDeviceImageFormatProperties2 +
VkPhysicalDeviceImageDrmFormatModifierInfoEXT の結果を
交差させる。
Vulkan 側
VK_EXT_external_memory_dma_buf が fd をインポート:
// 1. GPU がこのフォーマットで受け入れるモディファイアをクエリ:
vk::PhysicalDeviceImageFormatInfo2 fmtInfo{};
fmtInfo.setFormat(vk::Format::eR8Unorm)
.setType(vk::ImageType::e2D)
.setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
.setUsage(vk::ImageUsageFlagBits::eSampled);
// … pNext の DrmFormatModifier リスト → 対応モディファイアを列挙
// 2. ネゴシエートしたモディファイアでイメージを作成:
vk::ImageDrmFormatModifierExplicitCreateInfoEXT modInfo{};
modInfo.setDrmFormatModifier(chosenModifier)
.setPlaneLayouts(planeLayouts);
vk::ImageCreateInfo ici{};
ici.setPNext(&modInfo)
.setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
.setExternalMemoryHandleTypeFlags(
vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT);
// …
// 3. fd をインポート:
vk::ImportMemoryFdInfoKHR import{};
import.setHandleType(vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT)
.setFd(dmaBufFd)
.setMemory(memory.get());
device.bindImageMemory2(bindInfos); // または vkBindImageMemory + ImportInfo
vkImportMemoryFdKHR は成功時に fd を消費——呼び出し側は
その後 close() してはならない(二重クローズが番号を後の
インポートに再利用 → ドライバが解放済み BO をサンプル →
GPUVM フォルト)。
出現場所
- V4L2 → Vulkan:
VIDIOC_EXPBUFがキャプチャバッファを エクスポート;テクスチャとしてインポート——ゼロコピー カメラ取り込み。 - VA-API → Vulkan:
av_hwframe_map()がAVDRMFrameDescriptor(dma-buf fd + fourcc + モディファイア) を生成し、プレーンごとにVkImageとしてインポート。 - プロセス間 Vulkan → Vulkan:
vkGetMemoryFdKHRでVkDeviceMemoryをエクスポート——しかし fd は dma-buf なのでインポーターはeOpaqueFdではなくeDmaBufEXTが必要。
各パスの動作例:
Example: VulkanExamples/p48_v4l2
Example: VulkanExamples/p49_vaapi
Example: VulkanExamples/p70_ipc
実践的なルール
- 常にモディファイアをネゴシエート——
DRM_FORMAT_MOD_LINEARを仮定しない。 - fd は消費可能——所有権を慎重に追跡(成功したインポート後に
-1)。 - fd をインポートすべき子を exec する時
FD_CLOEXECが 刺す——fork前にフラグをクリア。 - カーネルは
dma_resvフェンスでアクセスを直列化——DMA_BUF_IOCTL_SYNCまたは同等のドライバ内同期で プロデューサフェンスを尊重。