Vulkanにおけるキューとは、GPUの実行するコマンドを保持する待ち行列です。有り体に言えばGPUの「やることリスト」です。GPUにコマンドを送るときは、このキューにコマンドを詰め込むことになります。

ところで、1つのGPUが持っているキューは1つだけとは限りません。いくつも持っている場合があります。また、それらキューによってサポートしている機能とサポートしていない機能があったりもします。キューにコマンドを送るときは、そのキューが何の機能をサポートしているかを事前に把握しておく必要があります。あるキューがサポートしていない機能を要するような仕事はそのキューには詰め込めません。また、ある物理デバイスの全てのキューがその機能をサポートしていないならば、その物理デバイスでその機能を要するコマンドは使えません。

ある物理デバイスの持っているキューの情報はgetQueueFamilyPropertiesメソッドで取得できます。メソッド名にある「キューファミリ」というのは、同じ能力を持っているキューをひとまとめにしたものです。1つの物理デバイスには1個以上のキューファミリがあり、1つのキューファミリには1個以上の同等の機能を持ったキューが所属しています。

1つのキューファミリの情報はvk::QueueFamilyProperties構造体で表されます。getQueueFamilyPropertiesを呼ぶと、vectorに入ったvk::QueueFamilyPropertiesの形で物理デバイスのキューファミリの情報を返してくれます。
std::vector<vk::QueueFamilyProperties> queueProps = physicalDevice.getQueueFamilyProperties();
試しに、ある物理デバイスの全てのキューファミリの情報を表示するプログラムを作ってみましょう。(1-4. プロジェクトの作成(Windows/VisualStudio2019)に従って空のプロジェクトから作った方はコンソール画面が出ないと思います。プロジェクトのプロパティ→リンカー→システム→サブシステム→コンソールを選んでコンソールアプリケーションにして下さい)
// 環境に合わせて #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 = physicalDevices[0]; std::vector<vk::QueueFamilyProperties> queueProps = physicalDevice.getQueueFamilyProperties(); std::cout << "queue family count: " << queueProps.size() << std::endl; std::cout << std::endl; for (size_t i = 0; i < queueProps.size(); i++) { std::cout << "queue family index: " << i << std::endl; std::cout << " queue count: " << queueProps[i].queueCount << std::endl; std::cout << " graphic support: " << (queueProps[i].queueFlags & vk::QueueFlagBits::eGraphics ? "True" : "False") << std::endl; std::cout << " compute support: " << (queueProps[i].queueFlags & vk::QueueFlagBits::eCompute ? "True" : "False") << std::endl; std::cout << " transfer support: " << (queueProps[i].queueFlags & vk::QueueFlagBits::eTransfer ? "True" : "False") << std::endl; std::cout << std::endl; } return 0; }
どのような結果が出力されたでしょうか。手元の環境(外付けグラボなし・CPU intel core i7)では以下のようになりました。
queue family count: 1 queue family index: 0 queue count: 1 graphic support: True compute support: True transfer support: True
どうやらintel core i7内臓のGPUはキューファミリが1つだけ、キューも1つだけ、唯一のキューファミリは描画と計算とデータ移動を全てサポートということのようです。面白いですね。
2-1. インスタンスと物理デバイスの項において「本来であれば各物理デバイスを吟味する」と書いたのを覚えていますでしょうか。今までの知識を元に「グラフィックスをサポートするキューファミリを最低でも1つ持っている」物理デバイスを選択するようなプログラムを書いてみましょう。どんなGPUだってそれくらいの機能はサポートしているかも知れませんが、1つもGPUが存在しない状況はあり得るかもしれません。
std::vector<vk::PhysicalDevice> physicalDevices = instance->enumeratePhysicalDevices(); vk::PhysicalDevice physicalDevice; bool existsSuitablePhysicalDevice = false; 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; break; } } if (existsGraphicsQueue) { physicalDevice = physicalDevices[i]; existsSuitablePhysicalDevice = true; break; } } if (!existsSuitablePhysicalDevice) { std::cerr << "使用可能な物理デバイスがありません。" << std::endl; return -1; }
さて、これで使用に適した物理デバイスを選ぶことが出来るようになりました。グラフィック機能をサポートしたキューを間違いなく持っています。
ではそのキューを取得しましょう。キューは論理デバイス(vk::Device)のgetQueueメソッドで取得できます。第1引数がキューファミリのインデックス、第2引数が取得したいキューのキューファミリ内でのインデックスです。
使用したいキューのキューファミリのインデックスを指定するということは、グラフィック機能をサポートしているキューファミリを見つけるだけでなく、そのキューファミリのインデックスも覚えておく必要がありますね。そのようなコードを追加しましょう。
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; } }
このgraphicsQueueFamilyIndexを使用してGetQueueを呼びます。取得するキューはキューファミリ内のどのキューでも変わりないので最初のキュー(0番)にします。
vk::Queue graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0);
これを実行してみましょう。すると実行時エラーが出たのではないでしょうか。一体なぜエラーが出たのでしょう。
// 環境に合わせて #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::UniqueDevice device = physicalDevice.createDeviceUnique(devCreateInfo); vk::Queue graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0); // エラー! return 0; }
実はVulkanでは論理デバイスからキューを取得するとき、論理デバイスにあらかじめ「このキューを使うよ」ということを伝えておく必要があります。論理デバイスの作成時、vk::DeviceCreateInfo構造体に「この論理デバイスを通じてこのキューファミリからいくつのキューを使う」という情報を指定する必要があるのです。その情報はvk::DeviceQueueCreateInfo構造体の配列で表されます。
vk::DeviceCreateInfo devCreateInfo; vk::DeviceQueueCreateInfo queueCreateInfo[1]; queueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex; queueCreateInfo[0].queueCount = 1; float queuePriorities[1] = { 1.0f }; queueCreateInfo[0].pQueuePriorities = queuePriorities; devCreateInfo.pQueueCreateInfos = queueCreateInfo; devCreateInfo.queueCreateInfoCount = 1;
今のところ欲しいキューは1つだけなので要素数1の配列にします。queueFamilyIndexはキューの欲しいキューファミリのインデックスを表し、queueCountはそのキューファミリからいくつのキューが欲しいかを表します。今欲しいのはグラフィック機能をサポートするキューを1つだけです。pQueuePrioritiesはキューのタスク実行の優先度を表すfloat値配列を指定します。1.0fを指定しておきます。優先度の値はキューごとに決められるため、この配列はそのキューファミリから欲しいキューの数だけの要素数を持ちます。vk::DeviceCreateInfoのpQueueCreateInfosに先ほどのvk::DeviceQueueCreateInfo構造体配列の先頭アドレス、queueCreateInfoCountに配列の要素数を指定します。上記のコードを追加した上で再度実行してみましょう。今度はエラーもなく通ったのではないでしょうか。
これで無事キューを取得することが出来ました。ここにコマンドを詰め込んでいくので、その方法を次回解説します。
なお、vk::Queueはvk::PhysicalDeviceと同じく、後始末の必要はありません。
この節では物理デバイスの選択とキューの取得をやりました。次回はコマンドバッファの作成とキューへの送信をやります。
// 環境に合わせて #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; devCreateInfo.pQueueCreateInfos = queueCreateInfo; devCreateInfo.queueCreateInfoCount = 1; vk::UniqueDevice device = physicalDevice.createDeviceUnique(devCreateInfo); vk::Queue graphicsQueue = device->getQueue(graphicsQueueFamilyIndex, 0); return 0; }