Tech Notes

VulkanのPushConstantとDescriptorSetの使い分け

Vulkanにおいてはシェーダに何か共通の変数を渡すための方法として主に2つの方法がある。一つは「DescriptorSet」、もう一つは「PushConstant」だ。

この2つの使い分けに関する良さげな情報があったので読んでみた。

前提としてそもそもそれぞれどのように使うものなのかを説明しておく。

DescriptorSetの大雑把な使い方

まずデータ構造を表すDescriptorSetLayoutを作成し、PipelineLayoutに指定する。

またDescriptorPoolを作成し、それとDescriptorSetLayoutからDescriptorSetを作成する。VkUpdateDescriptorSetsで中身の情報を更新する。

ドローコール実行時にVkCmdBindDescriptorSetsでバインドする。

PushConstantの大雑把な使い方

データ構造を表すVkPushConstantRangeを作成し、PipelineLayoutに指定する。

ドローコール実行時にVkCmdPushConstantsでデータを指定する。

使い分け方

まずそもそも出来ることの違いとして、PushConstantは画像のような大きいデータを渡すようには作られていない。小さいデータ、例えばモデル変換行列のような短い数値列などを渡すためのものであって、テクスチャなどを渡そうと思ったらDescriptorSetを使うことになる。

またもう一つの違いとして、データの記録方法がある。PushConstantのデータはコマンドバッファそのものに貯められる。他方DescriptorSetの実体はメモリなりGPUメモリなりに置いてあって、VkCmdBindDescriptorSetsで行われるのは「このDescriptorSetを使う」というポインタ情報をコマンドバッファに記録するような操作である。これが何を意味するかというと、PushConstantの値を更新するにはコマンドバッファを記録しなおす必要があるのである。DescriptorSetの場合はVkUpdateDescriptorSetsを呼べば、コマンドバッファを記録しなおさずとも中身が更新できるのだ。

このように書くとPushConstantは機能的に劣っているように思われるかもしれない。しかし実際のところ、現代の一般的なVulkanの設計においてはコマンドバッファは実行するたび記録しなおすものであり、まず後者の「コマンドバッファを記録しなおす必要がある」という点は大した問題ではない。さらに言えばDescriptorSetを使用してuniform変数を操作する場合、VkBufferを管理して取り回したり前回のコマンド実行によってまだアクセスされている最中ではないかといった事情を考慮する必要もあり、結局のところPushConstantの方が処理上の扱いとしてはラクなのである。使えるならDescriptorSetよりもPushConstantを使った方がいい。

以上をまとめると、結論としては

  • 小さい数値データの類ならPushConstantを使った方がラク
  • テクスチャなどのようにでかいデータ、特殊なデータならDescriptorSet(を使うしかない)

ということである。

参考: https://stackoverflow.com/questions/67817141/vulkan-pushconstant-vs-uniform-buffer-update