VK_EXT_descriptor_buffer の使い方——プレーンバッファメモリとしてのデスクリプタ

Post 54。VK_EXT_descriptor_buffer はデスクリプタ プール/セット/更新の踊りを完全に取り除く:デスクリプタは自分が 所有する VkBuffer 内の不透明バイトになる。デスクリプタ blob を memcpy でデバイス可視メモリに書き、2つのコマンドでバインド—— VkDescriptorPool なし、VkDescriptorSet オブジェクトなし、 vkUpdateDescriptorSets なし。

example.sh
$ ./app --headless out.ppm
descriptor blob: 64 bytes at set-offset 0
wrote out.ppm

ライフサイクルの差分

従来デスクリプタバッファ
vkCreateDescriptorPool + サイズ推測getDescriptorSetLayoutSizeEXT でサイズ決めた vkCreateBuffer
vkAllocateDescriptorSetsバッファへのオフセット
vkUpdateDescriptorSetsvkGetDescriptorEXT が base + offset にバイトを書く
vkCmdBindDescriptorSetsvkCmdBindDescriptorBuffersEXT + vkCmdSetDescriptorBufferOffsetsEXT

順序通りのセットアップ

main.cpp (device + layout)
vk::PhysicalDeviceDescriptorBufferFeaturesEXT feat{};
feat.setDescriptorBuffer(true);
// `feat` を createDevice にチェイン、有効化
// VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME

dslci.setFlags(eDescriptorBufferEXT)     // レイアウトに必須
     .setBindings(bindings);
auto dsl = device->createDescriptorSetLayoutUnique(dslci);
vk::DeviceSize setSize =
    device->getDescriptorSetLayoutSizeEXT(dsl.get(), dldi);
vk::DeviceSize b0ofs =
    device->getDescriptorSetLayoutBindingOffsetEXT(dsl.get(), 0, dldi);
main.cpp (buffer + write)
auto descBuf = vk.createBuffer(setSize,
    eResourceDescriptorBufferEXT | eSamplerDescriptorBufferEXT |
    eShaderDeviceAddress,                 // デバイスアドレスが必要
    eHostVisible | eHostCoherent, true);

vk::DescriptorGetInfoEXT gi{};
vk::DescriptorDataEXT dd{};
dd.setPCombinedImageSampler(&dii);        // sampler+view+layout
gi.setType(eCombinedImageSampler).setData(dd);
device->getDescriptorEXT(gi,
    props.combinedImageSamplerDescriptorSize,   // 実装固有のサイズ!
    (char*)descBuf.mapped + b0ofs, dldi);       // -> 生バイト
main.cpp (bind + draw)
vk::DescriptorBufferBindingInfoEXT bi{};
bi.setAddress(device->getBufferAddress(descBuf.buf.get()))
  .setUsage(eResourceDescriptorBufferEXT | eSamplerDescriptorBufferEXT);
cmd.bindDescriptorBuffersEXT(bi, dldi);
cmd.setDescriptorBufferOffsetsEXT(eGraphics, layout, /*set*/0,
                                  /*bufferIndex*/0, /*offset*/0, dldi);
cmd.draw(3, 1, 0, 0);

なぜ重要か

  • **テクスチャ交換 = 約64バイトの書き換え。**各タイルのソースが フレーム毎に新しい DMA-Buf インポートのビデオウォールで (post 49)、 フレームインフライト毎にデスクリプタセットスロットを燃やさない—— バイトをパッチしてメモリバリアを押すだけ。
  • プールサイズ決め不要:VkDescriptorPoolSize の簿記( 「combined-image-sampler はいくつ必要?」)が消える;バッファは 作った大きさ。
  • 複数セット = オフセット:バインディングスロット毎の VkDescriptorSetLayout は依然として存在(形状を定義)するが、 セットは単なるオフセット——フレーム毎にデスクリプタ領域を リングバッファ化できる。

注意点

  • デスクリプタサイズは不透明で実装毎:ハードコード禁止—— 常に VkPhysicalDeviceDescriptorBufferPropertiesEXT から combinedImageSamplerDescriptorSize などを問い合わせる。
  • eDescriptorBufferEXT はレイアウトとパイプラインの両方に 設定必須(VkPipelineCreateFlagBits::eDescriptorBufferEXT)—— パイプラインフラグを忘れるとバインド時にバリデーションが 文句を言う。
  • ホスト書き込みにはバリアが必要:GPU が読みうる間に CPU で 書くデスクリプタには HOST_WRITE → SHADER_READ の順序付けが 必要(あるいはサブミット前に書くだけ——ただしリングバッファは そうはいかない)。
  • この拡張はデスクトップではどこにでもある(RADV、NVIDIA、ANV) ただし features の descriptorBuffer を確認——組み込みポート は欠くかもしれない。

完全プログラム

Example: VulkanExamples/p54_descbuf

— main.cpp:デスクリプタバッファ経由でバインドした1つのテクスチャ → チェッカーボード PPM、check.py が両色をサンプル。


同じカテゴリの類似記事をチェック: C++ Vulkan