2-4. コマンドバッファ

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

コマンドバッファを作るには、その前段階として「コマンドプール」というまた別のオブジェクトを作る必要があります。コマンドバッファをコマンドの記録に使うオブジェクトとすれば、コマンドプールというのはコマンドを記録するためのメモリ実体のようなもので、コマンドバッファを作る際には必ず必要になります。

コマンドプールは論理デバイス(vk::Device)のcreateCommandPoolメソッド、コマンドバッファは論理デバイス(vk::Device)のallocateCommandBuffersメソッドで作成することができます。コマンドプールが「create」なのに対し、コマンドバッファは「allocate」であるあたりから「コマンドバッファの力は既にあるコマンドプールから割り当てる」という気持ちが読み取れますね。

まずはコマンドプールを作成します。

vk::CommandPoolCreateInfo cmdPoolCreateInfo;
cmdPoolCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;

vk::UniqueCommandPool cmdPool = device->createCommandPoolUnique(cmdPoolCreateInfo);

CommandPoolCreateInfoqueueFamilyIndexには、そのコマンドバッファを送信する場合のキューを指定します。

結局送信するときにも「このコマンドバッファをこのキューに送信する」というのは指定するのですが、そこはあまり深く突っ込まないでください。こんなのが盛り沢山なのが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で返してくれるようになっています。

作るコマンドバッファの数はCommandBufferAllocateInfocommandBufferCountで指定します。commandPoolにはコマンドバッファの作成に使うコマンドプールを指定します。このコードではUniqueCommandPoolを使っているので.get()を呼び出して生CommandPoolを取得しています。

コマンドバッファが無事作成出来たら、今度はコマンドバッファにコマンドを記録してキューに送信してみましょう。といっても今回やりたいのは「どうすればコマンドを記録・送信できるか」であり具体的にどんなコマンドが使えるのかはこの章の主題ではないので、とりあえず0個のコマンドが記録されたコマンドバッファを送信することにします。

コマンドの記録は次のようにして行います。

vk::CommandBufferBeginInfo cmdBeginInfo;
cmdBufs[0]->begin(cmdBeginInfo);

// コマンドを記録

cmdBufs[0]->end();

beginendの間で描画だのなんだの色々なコマンドを記録します。分かりやすいですね。

こうしてコマンドの記録が終わったら今度はそれをキューに送信しましょう。

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::Queuesubmitメソッドで行います。第1引数には初期化子リストの形でvk::SubmitInfoを指定します。このコードでは一つしか指定していませんが、複数飛ばすことができます。

第2引数は現在nullptrにしていますが、本来はフェンスというものを指定することができます。詳細についてはまた今度にします。

 

こうしてsubmitでGPUに命令を送ることが出来るのですが、あくまで「送る」だけであることには留意が必要です。どういうことかというと、キューに仕事を詰め込むだけであるため、submitから処理が返ってきた段階で送った命令が完了されているとは限らないのです。

実用的なプログラムを作るためには、「依頼した処理が終わるまで待つ」方法を知る必要があります。これに必要なのが「セマフォ」や先ほど名前だけ出した「フェンス」という機構です。これらの詳細は別の章で書きたいと思います。とりあえずここでは、単純に「キューが空になるまで待つ」方法を書きます。

graphicsQueue.waitIdle();

このようにすると、その段階でキューに入っているコマンドが全て実行完了してキューが空になるまで待つことができます。分かりやすいですね。セマフォなどを使うと送った個別のコマンドごとに関する待ち処理などが出来るのですが、それはまた今度にします。


この節ではコマンドバッファの作成とコマンドの記録、送信についてやりました。

これで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(); i++)
        {
            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;
}

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です