Vulkan:ディスクリプタの解説——セットレイアウト、プール、更新パターン

シェーダーはポインタを取れない。ディスクリプタは「この シェーダーのバインディング 0 = このバッファ / この イメージ+サンプラ」と言うハンドル。4つのオブジェクトが 機構を形成する:

オブジェクト何か
VkDescriptorSetLayoutインターフェース:どのバインディング、どのタイプ、どのステージ
VkDescriptorPoolセットのアロケータ(タイプごとのカウント!)
VkDescriptorSetレイアウトの割り当て済みインスタンス——実際のデータポインタ
vkUpdateDescriptorSetsバッファ/イメージハンドルをセット内に書き込む

パイプラインレイアウトは期待するセットレイアウトをリストする (set = 0 → レイアウト A …)、GLSL の layout(set=N, binding=M) に対応。

フロー

example.cpp
// 1. インターフェース:binding 0 = combined image sampler、フラグメントステージ
VkDescriptorSetLayoutBinding b{.binding=0,
    .descriptorType=eCombinedImageSampler, .descriptorCount=1,
    .stageFlags=eFragment};
// 2. プールはセットではなくディスクリプタタイプでサイズ決定
VkDescriptorPoolSize ps{eCombinedImageSampler, 8};
// 3. セットを割り当て、具体的な image+sampler を書き込む
VkWriteDescriptorSet w{.dstSet=set, .dstBinding=0,
    .descriptorType=eCombinedImageSampler, .pImageInfo=&info};
// 4. パイプラインと一緒にバインド
c.bindDescriptorSets(eGraphics, layout, 0 /*set*/, ds, {});

完全版は post 5 (テクスチャ)と post 10 (ユニフォームバッファ)を参照。

更新パターン——意図的に選ぶ

  • 静的セット:一度割り当て、一度更新(変わらない テクスチャ)。最も安く、ほとんどのチュートリアルがやるもの。
  • フレームごとのセット:それぞれ自分のUBO を指す N 個のフライト中セット——GPU がまだデータを読んでいる 可能性があるなら必須。
  • eFreeDescriptorSet プールフラグは本当にセットを 個別に解放する場合のみ;ほとんどのエンジンはフレームごとに プール全体をリセットする。
  • プッシュディスクリプタ(VK_KHR_push_descriptor): コマンドバッファに記録されるディスクリプタ——セット オブジェクトがまったくなく、ドローごとのテクスチャに最適。

使ってはいけない方法

  • GPU がまだ読んでいるセットを更新しない。 コマンド バッファと同じルール:バインドされたディスクリプタは フェンスまで生きなければならない。フライト中のバインド済み UBO の変異は、スクリーンショットが時々見える形でフレームを 破壊する。
  • プールサイズはディスクリプタタイプごと。 maxSets=8 でも poolSize{eUniformBuffer, 2} だけ → 3番目のセットで 割り当て失敗。誰もが一度は噛まれる。
  • バインド-レイアウトの不一致をしない。 GLSL の layout(set=1) vs bindDescriptorSets の set=0 —— 検証は捕まえる;レイヤなしではゴミをサンプルする。
  • オフセットはバイト、そして動的 UBO は存在する。 eUniformBufferDynamic は pDynamicOffsets 経由で1つの バインディングが大きなバッファ上を滑ることを許す—— 数千のオブジェクトごとブロックに適したツールで、N 個の ディスクリプタセットではない。
  • エイリアス禁止:同じセットを1つのディスパッチで サンプルドとストレージイメージの両方としてバインドできない。

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