VK_EXT_multi_draw 解説:vkCmdDrawMultiEXT によるバッチ化されたドローコール

500個の単純なオブジェクトを持つシーンは500回の vkCmdDraw 呼び出しを要求する。それぞれがドライバーがバリデートして記録する わずかな API オーバーヘッドだ。VK_EXT_multi_draw はそれらを 潰す。vkCmdDrawMultiEXT は {firstVertex, vertexCount} ペアの 配列を取り、すべてを1回の呼び出しで発行する。パラメータは ホストメモリ から来る。間接バッファは不要だ。

API

multi.cpp
// 機能を有効化:VkPhysicalDeviceMultiDrawFeaturesEXT::multiDraw

vk::MultiDrawInfoEXT draws[] = {
    {0,    36},    // obj0:頂点 0..35
    {36,   24},    // obj1:頂点 36..59
    {60,  120},    // obj2:…
};

cmd.drawMultiEXT(uint32_t(std::size(draws)), draws,
                 /*instanceCount*/1, /*firstInstance*/0,
                 /*stride*/sizeof(vk::MultiDrawInfoEXT));

// インデックス版 — 同じアイデア + pVertexOffset:
cmd.drawMultiIndexedEXT(n, idxInfos, 1, 0,
                        sizeof(vk::MultiDrawIndexedInfoEXT), &vtxOff);

vkCmdDraw*Indirect との主な違い:

drawMultidrawIndirect
パラメータの出所呼び出し内のホスト配列GPU 上の VkBuffer
全 draw が共有instanceCount、firstInstanceなし — draw ごとに完全なパラメータ
drawCount 制限maxMultiDrawCount間接 draw カウントは無制限
シェーダーが見るdraw ごとの gl_DrawIDdraw ごとの gl_DrawID

何に向いているか

  • バッチ化された単純 draw——それぞれが異なる頂点スライスだが 同じインスタンス数を必要とする数百のクワッド/ビルボード/タイル。 1回の API 呼び出し、1回のバリデーションパス。
  • gl_DrawID による draw ごとのデータ——シェーダー内で gl_DrawID が SSBO/UBO にインデックスし、バッチ内の各 draw が 自分のトランスフォーム/マテリアルを取得できる。これが「1回の 呼び出し、N オブジェクト」の得方だ。
  • N draw より少ない CPU、間接より単純——埋める GPU バッファが なく、カウント用の vkCmdFillBuffer もない。パラメータは インラインだ。
drawid.vert
layout(set=0, binding=0) readonly buffer Xforms { mat4 m[]; } xf;
void main() {
    // マルチバッチのどの draw か?
    mat4 m = xf.m[gl_DrawID];
    gl_Position = m * vec4(pos, 1);
}

制限

  • VkPhysicalDeviceMultiDrawPropertiesEXT::maxMultiDrawCount が配列を 制限する。一般に2048以上。巨大バッチに依存する前に確認する。
  • すべての draw が instanceCount/firstInstance を共有する。 オブジェクトが異なるインスタンス数を必要とするなら別々の 呼び出しが依然必要。
  • draw ごとの first-instance 変化はない。多様性には gl_DrawID によるデータバッファへのオフセットを組み合わせる。

実践的なルール

  • 「同じマテリアル、異なるメッシュスライス」——drawMulti が スイートスポット。パラメータがとにかく CPU にあるなら間接より 安い。
  • 「GPU が draw を決める」(カリングがリストを書く)——代わりに vkCmdDrawIndirect* が欲しい。パラメータは GPU 生成でなければ ならない。
  • gl_DrawID には VK_KHR_shader_draw_parameters が必要(1.1 で コア)。バッファを draw ごとにインデックスする標準的な方法だ。
  • バッチごとに依然1パイプライン。マルチドローは異なるパイプラインを 融合しない。パイプライン 内 でバッチし、間で切り替える。

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