前回の記事
ディスクリプタはシェーダー側にあるGPUリソースの搬入口と思って良い。
この搬入口がスロットである
ディスクリプタはディスクリプタセットレイアウトという構造体のインスタンスを作成し、
アプリ側でそのスロット番号、搬入されるリソースの型、ディスクリプタの数、送り込むシェーダーのステージを指定する
//Creates a descriptor for binding SSBO.
VkDescriptorSetLayoutBinding descriptorLayoutBinding{};
descriptorLayoutBinding.binding = 0;
descriptorLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorLayoutBinding.descriptorCount = 1;
descriptorLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
ちなみにC++ライクな疑似コードにするとこんな感じだろう
template VkDescriptorSetLayoutBinding<T>
std::vector<VkDesctiptorSetLayoutBinding<VK_SHADER_STAGE_COMPUTE_BIT> descsriptorSetBinding [];
次にディスクリプタセットレイアウトになる。
同じ要領でシェーダー側の搬入口、つまりbinding番号を指定、作成に使う構造体の型、先ほど作成したVkDescriptorSetLayoutBindingのインスタンスのポインタを指定する
VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &descriptorLayoutBinding;
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
throw std::runtime_error("ディスクリプタセットレイアウトの作成に失敗しました。failed to craete a descriptor set layout");
}
ディスクリプタプール
ディスクリプタを書類の束とするなら、
ディスクリプタプールはその束を入れる箱のようなものと考えて良い。
つまりコンテナの配列というべきか。
C++のように例えるなら以下の通りだろう。
// Cpp-like psuedo code.
std::vector<VK_DESCRIPTOR_TYPE_STORAGE_BUFFER > shaderbindingstoragebuffer[1];
これをコードにしよう。
まずディスクリプタプールの要素数を決める
VkDescriptorPoolSize poolSize{};
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
poolSize.descriptorCount = 1;
次にディスクリプタプールを作るための情報に要素数、容量、最大数を定義
VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = 1;
これを使って、最後にディスクリプタプールを作成する
if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
throw std::runtime_error("ディスクリプタプールの作成に失敗しました。failed to create a descriptor pool");
}
ディスクリプタセットの作成
いよいよディスクリプタセット本体を作成する時が来た。そのための設定と関数は以下の通り
ディスクリプタセットのメモリ領域を確保するための設定を行う。これまでに作成したディスクリプタレイアウト、プールのアドレスを格納していく。
VkDescriptorSetAllocateInfo descriptorAllocInfo{};
descriptorAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptorAllocInfo.descriptorPool = descriptorPool;
descriptorAllocInfo.descriptorSetCount = 1;
descriptorAllocInfo.pSetLayouts = &descriptorSetLayout;
最後にVK_NULL_HANDLEで綺麗にしたまっさらなディスクリプタセットのインスタンスを用意。
そこにvkAllocateDescriptorSetsを使い、ディスクリプタセットの実情報を作る
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
if (vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSet) != VK_SUCCESS) {
throw std::runtime_error("ディスクリプタセットの割り当てに失敗しました。 failed to allocate a descriptor set");
}
// Prepare to pack a descriptor into a buffer.
VkDescriptorBufferInfo bufferDescriptorInfo{};
bufferDescriptorInfo.buffer = buffer;
bufferDescriptorInfo.offset = 0;
bufferDescriptorInfo.range = bufferSize;
VkWriteDescriptorSet
そうして出来上がったディスクリプタセットを、VkWriteDescriptorSetを使いGPU側のメモリに書き込む命令を用意する。
ディスクリプタセットをカチッと取り付けて、
GPU側のシェーダーバインディング番号、つまりディスクリプタセットの搬入口、要素番号を、そしてディスクリプタの種類(送り込むGPUリソースやバッファの種類)も指定して、最後にVkWriteDescriptorSetのアドレスも指定しておく。
VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSet;
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferDescriptorInfo;
最後に以下の関数を呼び出して、GPUへの命令準備オッケー!
vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
この流れをC++のように例えてみよう
ディスクリプタの書き込みとその命令はこんな感じになる
//ディスクリプタの書き込み準備
unsigined int computeshaderbindingSlot =1;
std::vector<VkWriteDescriptorSet> writedescriptorset[1];
//GPUへの書き込み命令
computeshader[computeshaderbindingSlot]=&writedescriptorset;
フルコード