Vulkan 1.3 のコア機能に格上げされた VK_EXT_inline_uniform_block 機能拡張ですが、これってどのようなもの?と個人的に気になったので、少し調べて&試してみました。結論として、これは便利に使えそうな場面あるな~という感想です。
記述内容に間違いが含まれている可能性もありますのでご注意下さい
VK_EXT_inline_uniform_block とは
自分がそうであったように、本家の説明ページを見てもあまりピンと来ないかもしれません。この拡張機能は、プッシュ定数とユニフォームバッファの間くらいに位置する機能を提供するものです。具体的には、プッシュ定数であればデータはコマンドバッファの中に値が格納されるのですが、この inline uniform block の機能を使うと、各データの値をディスクリプタ(ディスクリプタプール) のメモリ内に記録することが可能となります。つまりユニフォームバッファのようなバッファオブジェクトは不要ということです。データ更新の際には、ディスクリプタを更新するという作業が必要になります。プッシュ定数はコマンドバッファに記録される都合で、値を変更するときにはコマンドバッファを作り直す必要がありますが、この inline uniform block であればコマンドバッファは再利用が可能となります。
inline_uniform_block の準備
まずはディスクリプタプールに対して、準備が必要です。例えば以下のように一定量のサイズを割り当てておきます。
array<VkDescriptorPoolSize, 3> descPoolSize;
descPoolSize[0].descriptorCount = uint32_t(m_uniformBuffers.size());
descPoolSize[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descPoolSize[1].descriptorCount = uint32_t(m_uniformBuffers.size());
descPoolSize[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descPoolSize[2].descriptorCount = uint32_t(sizeof(ShaderParameters)*4);
descPoolSize[2].type = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
ここで注意事項として、 descriptorCount としてディスクリプタの個数ではなくてサイズを割り当てるという点が必要になるようです。さらにこのサイズは 4 の倍数となるサイズを要求されます。
続いてディスクリプタをアロケートするときには以下のコードとなります。
VkDescriptorSetLayoutBinding bindingUboInline{};
bindingUboInline.binding = 0;
bindingUboInline.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
bindingUboInline.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindingUboInline.descriptorCount = sizeof(ShaderParameters); // ここでも必要なバイトサイズを指定
....
vkCreateDescriptorSetLayout( ... )
データをディスクリプタへ書き込む (更新)
データを保持するディスクリプタを確保したら、今度はデータの書き込みです。
ここでは以下のようなコードとなります。VkWriteDescriptorSetInlineUniformBlockEXT 構造体で、メインメモリ側にあるシェーダーに渡したいデータ構造体などの先頭アドレスを pData に設定します。このときにもデータのサイズには先ほどと同じように 4バイトアライメント制約が発生します。
書き込みのためには、 VkWriteDescriptorSet 構造体を用いて設定しますが、このとき pNext メンバで、既に用意したインラインユニフォームブロックの情報を設定しておきます。その後 vkUpdateDescriptorSets 関数を呼び出してディスクリプタへデータの書き込みを行います。
VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{};
writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT;
writeDescriptorSetInlineUniformBlock.dataSize = sizeof(ShaderParameters);
writeDescriptorSetInlineUniformBlock.pData = &m_sceneParams; // メインメモリ側にあるデータ構造
VkWriteDescriptorSet uboInline{};
uboInline.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
uboInline.dstBinding = 0;
uboInline.descriptorCount = sizeof(ShaderParameters);
uboInline.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
uboInline.dstSet = m_descriptorSet[i];
uboInline.pNext = &writeDescriptorSetInlineUniformBlock;
......
vkUpdateDescriptorSets( ... )
描画
描画ではディスクリプタセットをバインドして描画コマンドを実行するだけです。この点は従来のコードと変更がありません。
描画に使う行列データを更新するということをそれぞれの方法で考えたとき、ユニフォームバッファを使う場合では、行列の更新でバッファオブジェクトの書き換えがあります。インラインユニフォームブロックを使う場合ではディスクリプタの更新が必要となります。バッファをマップして書き換えるという手間と、ディスクリプタを更新するという手間とどちらも似たような感じの手間にはなるでしょう。
1つ違いそうな点としては、ユニフォームバッファを用意せずにコマンドバッファを再利用出来る&一部データは動的更新が可能ということになるでしょう。VK_KHR_dynamic_rendering 機能拡張と併用すると、単純な描画までに必要な Vulkan のオブジェクト数を減らしてシンプルなプログラムコードを実現出来そうに思います。
コメント