コマンドバッファとは、コマンドをため込んでおくバッファです。2-3. キューにおいて、VulkanでGPUに仕事をさせる際は「コマンドをキューに送る」として説明しましたが、より正確には「コマンドバッファの中にコマンドを記録し、そのコマンドバッファをキューに送る」という手続きをとります。なのでコマンドを送信する際には必ずコマンドバッファが必要になります。

一連の命令を書類にまとめて送りつける、みたいなイメージで考えると分かりやすいかもしれません。
コマンドバッファを作るには、その前段階として「コマンドプール」というまた別のオブジェクトを作る必要があります。コマンドバッファをコマンドの記録に使うオブジェクトとすれば、コマンドプールというのはコマンドを記録するためのメモリ実体のようなものです。コマンドバッファを作る際には必ず必要になります。
コマンドプールは論理デバイス(vk::Device)のcreateCommandPoolメソッド、コマンドバッファは論理デバイス(vk::Device)のallocateCommandBuffersメソッドで作成することができます。コマンドプールの作成が「create」なのに対し、コマンドバッファの作成は「allocate」であるあたりから「コマンドバッファの記録能力は既に用意したコマンドプールから割り当てる」という気持ちが読み取れますね。
まずはコマンドプールを作成します。
vk::CommandPoolCreateInfo cmdPoolCreateInfo; cmdPoolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex; vk::UniqueCommandPool cmdPool = device->createCommandPoolUnique(cmdPoolCreateInfo);
CommandPoolCreateInfoのqueueFamilyIndexには、後でそのコマンドバッファを送信するときに対象とするキューを指定します。
結局送信するときにも「このコマンドバッファをこのキューに送信する」というのは指定するのですが、そこはあまり深く突っ込まないでください。こんな二度手間が盛り沢山なのがVulkanです。
次はコマンドバッファを作成します。allocateCommandBufferではなくallocateCommandBuffersである名前から分かる通り、実は一度にいくつも作れる仕様になっています。逆に1個しか要らないよという場合でも要素数1の配列で返ってきます。どうしてこんな仕様なのでしょうか?どうせいくつも作るだろうみたいな想定なのでしょうか?
vk::CommandBufferAllocateInfo cmdBufAllocInfo; cmdBufAllocInfo.commandPool = cmdPool.get(); cmdBufAllocInfo.commandBufferCount = 1; cmdBufAllocInfo.level = vk::CommandBufferLevel::ePrimary; std::vector<vk::UniqueCommandBuffer> cmdBufs = device->allocateCommandBuffersUnique(cmdBufAllocInfo);
Vulkan-Hppではstd::vectorで返してくれるようになっています。
作るコマンドバッファの数はCommandBufferAllocateInfoのcommandBufferCountで指定します。commandPoolにはコマンドバッファの作成に使うコマンドプールを指定します。このコードではUniqueCommandPoolを使っているので.get()を呼び出して生CommandPoolを取得しています。
コマンドバッファが無事作成出来たら、今度はコマンドバッファにコマンドを記録してキューに送信してみましょう。といっても、今回やりたいのは「どうすればコマンドを記録・送信できるか」であり具体的にどんなコマンドが使えるのかはこの章の主題ではないので、とりあえず0個のコマンドが記録されたコマンドバッファを送信することにします。
コマンドの記録は次のようにして行います。
vk::CommandBufferBeginInfo cmdBeginInfo; cmdBufs[0]->begin(cmdBeginInfo); // コマンドを記録 cmdBufs[0]->end();
beginとendの間で描画だのなんだの色々なコマンドを記録します。分かりやすいですね。
こうしてコマンドの記録が終わったら今度はそれをキューに送信しましょう。
vk::CommandBuffer submitCmdBuf[1] = { cmdBufs[0].get() }; vk::SubmitInfo submitInfo; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = submitCmdBuf; graphicsQueue.submit({ submitInfo }, nullptr);
vk::SubmitInfo構造体にコマンドバッファの送信に関わる情報を指定します。commandBufferCountには送信するコマンドバッファの数、pCommandBuffersには送信するコマンドバッファの配列へのポインタを指定します。
送信はvk::Queueのsubmitメソッドで行います。第1引数には初期化子リストの形でvk::SubmitInfoを指定します。このコードでは一つしか指定していませんが、複数飛ばすことができます。
第2引数は現在nullptrにしていますが、本来はフェンスというものを指定することができます。詳細についてはまた今度にします。
こうしてsubmitでGPUに命令を送ることが出来るのですが、あくまで「送る」だけであることには留意が必要です。どういうことかというと、キューに仕事を詰め込むだけであるため、submitから処理が返ってきた段階で送った命令が完了されているとは限らないのです。

実用的なプログラムを作るためには、「依頼した処理が終わるまで待つ」方法を知る必要があります。これに必要なのが「セマフォ」や先ほど名前だけ出した「フェンス」という機構です。これらの詳細は別の章で書きたいと思います。とりあえずここでは、単純に「キューが空になるまで待つ」方法を書きます。
graphicsQueue.waitIdle();
キューのwaitIdleメソッドを呼ぶと、その段階でキューに入っているコマンドが全て実行完了してキューが空になるまで待つことができます。分かりやすいですね。セマフォなどを使うと送った個別のコマンドに関する待ち処理などが出来るのですが、それに関する解説は別の記事とします。(補講2. セマフォとフェンスによる同期処理に詳述しました。)
この節ではコマンドバッファの作成とコマンドの記録、送信についてやりました。
これでGPUにコマンドを送信する方法を覚えたことになります。
次章は三角形の描画に入ります。
// 環境に合わせて #define VK_USE_PLATFORM_WIN32_KHR #define VULKAN_HPP_TYPESAFE_CONVERSION #include <vulkan/vulkan.hpp> #include <iostream> #include <vector> int main() { vk::InstanceCreateInfo createInfo; vk::UniqueInstance instance; instance = vk::createInstanceUnique(createInfo); std::vector<vk::PhysicalDevice> physicalDevices = instance->enumeratePhysicalDevices(); vk::PhysicalDevice physicalDevice; bool existsSuitablePhysicalDevice = false; uint32_t graphicsQueueFamilyIndex; for (size_t i = 0; i < physicalDevices.size(); i++) { std::vector<vk::QueueFamilyProperties> queueProps = physicalDevices[i].getQueueFamilyProperties(); bool existsGraphicsQueue = false; for (size_t j = 0; j < queueProps.size(); j++) { if (queueProps[j].queueFlags & vk::QueueFlagBits::eGraphics) { existsGraphicsQueue = true; graphicsQueueFamilyIndex = j; break; } } if (existsGraphicsQueue) { physicalDevice = physicalDevices[i]; existsSuitablePhysicalDevice = true; break; } } if (!existsSuitablePhysicalDevice) { std::cerr << "使用可能な物理デバイスがありません。" << std::endl; return -1; } vk::DeviceCreateInfo devCreateInfo; vk::DeviceQueueCreateInfo queueCreateInfo[1]; queueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex; queueCreateInfo[0].queueCount = 1; float queuePriorities[1] = { 1.0 }; queueCreateInfo[0].pQueuePriorities = queuePriorities; devCreateInfo.pQueueCreateInfos = queueCreateInfo; devCreateInfo.queueCreateInfoCount = 1; vk::UniqueDevice device = physicalDevice.createDeviceUnique(devCreateInfo); vk::Queue graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0); vk::CommandPoolCreateInfo cmdPoolCreateInfo; cmdPoolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex; vk::UniqueCommandPool cmdPool = device->createCommandPoolUnique(cmdPoolCreateInfo); vk::CommandBufferAllocateInfo cmdBufAllocInfo; cmdBufAllocInfo.commandPool = cmdPool.get(); cmdBufAllocInfo.commandBufferCount = 1; cmdBufAllocInfo.level = vk::CommandBufferLevel::ePrimary; std::vector<vk::UniqueCommandBuffer> cmdBufs = device->allocateCommandBuffersUnique(cmdBufAllocInfo); vk::CommandBufferBeginInfo cmdBeginInfo; cmdBufs[0]->begin(cmdBeginInfo); // コマンドを記録 cmdBufs[0]->end(); vk::CommandBuffer submitCmdBuf[1] = { cmdBufs[0].get() }; vk::SubmitInfo submitInfo; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = submitCmdBuf; graphicsQueue.submit({ submitInfo }, nullptr); graphicsQueue.waitIdle(); return 0; }