アプリケーションは通常、VkCommandBuffer
オブジェクトまたはスパースバインディング(sparse bindings) の形で、VkQueue
に仕事をサブミットします。
VkQueue
にサブミットされたコマンドバッファは順番に開始されますが、その後は独立して進行することができ、順番通りに完了するとは限りません。
異なるキューにサブミットされたコマンドバッファは、VkSemaphore
で明示的に同期させない限り、互いに順序付けられません。
一度にひとつのスレッドからのみ VkQueue
に仕事をサブミットすることができますが、異なるスレッドは同時に異なる VkQueue
に仕事をサブミットすることができます。
VkQueue
が基礎となるハードウェアにどのようにマッピングされるかは、実装で定義されます。いくつかの実装では、複数のハードウェアキューを持ち、複数の VkQueue
への仕事のサブミットは独立して同時に行われます。一部の実装では、ハードウェアに作業をサブミットする前にカーネルドライバレベルでスケジューリングを行います。現在の Vulkan では、各 VkQueue
がどのようにマッピングされているか、正確な詳細を公開する方法はありません。
Note
|
すべてのアプリケーションが複数のキューを必要としたり、その恩恵を受けたりするわけではありません。すべての仕事を GPU にサブミットするために、アプリケーションが単一の「ユニバーサル」グラフィックス対応キューを持つことは合理的です。 |
VkQueue
がサポートできる操作にはさまざまな種類があります。「キューファミリ」とは、VkQueueFamilyProperties
で示されているように、共通のプロパティを持ち、同じ機能をサポートする VkQueue
のセットを表しています。
VkQueueFlagBits に記載されているキューの操作を以下に示します。
-
VK_QUEUE_GRAPHICS_BIT
はvkCmdDraw*
とグラフィックパイプラインのコマンドに使用されます。 -
VK_QUEUE_COMPUTE_BIT
はvkCmdDispatch*
やvkCmdTraceRays*
といったコンピュートパイプライン関連のコマンドに使用されます。 -
VK_QUEUE_TRANSFER_BIT
は全ての転送コマンドに使用されます。-
仕様書の VK_PIPELINE_STAGE_TRANSFER_BIT には「転送コマンド」が記載されています。
-
VK_QUEUE_TRANSFER_BIT
のみを持つキューファミリは、通常、DMA を使用して、ホストとディスクリート GPU 上のデバイスメモリ間で非同期にデータを転送するためのもので、独立したグラフィックス/コンピュート処理と同時に転送を行うことができます。 -
VK_QUEUE_GRAPHICS_BIT
およびVK_QUEUE_COMPUTE_BIT
は、常にVK_QUEUE_TRANSFER_BIT
コマンドを暗黙的に受け入れることができます。
-
-
VK_QUEUE_SPARSE_BINDING_BIT
は、スパースリソース(sparse resources) をvkQueueBindSparse
でメモリにバインドする際に使用されます。 -
VK_QUEUE_PROTECTED_BIT
は、保護されたメモリに使用されます。 -
VK_QUEUE_VIDEO_DECODE_BIT_KHR
とVK_QUEUE_VIDEO_ENCODE_BIT_KHR
は、Vulkan Video に使用されます。
Vulkan Spec の各操作の箇所には、vk.xml ファイルから生成された「対応するキューの種類」のセクションがあります。以下は、仕様書に記載されている3種類の例です。
アプリケーションが単一のグラフィックス VkQueue
のみを必要とする場合に必要な最も単純なロジックは以下の通りです。
uint32_t count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, nullptr);
std::vector<VkQueueFamilyProperties> properties(count);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &count, properties.data());
// Vulkan では、少なくともひとつのグラフィックスキューファミリを公開する実装が必要
uint32_t graphicsQueueFamilyIndex;
for (uint32_t i = 0; i < count; i++) {
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
// このキューファミリはグラフィックをサポートする
graphicsQueueFamilyIndex = i;
break;
}
}
VkDevice
、VkBuffer
、VkDeviceMemory
などの他のハンドルとは異なり、vkCreateQueue
や vkAllocateQueue
はありません。代わりに、ドライバが vkCreateDevice
/ vkDestroyDevice
の際に VkQueue
ハンドルの作成と破棄を行います。
以下の例では、2つのキューファミリから3つの VkQueue
をサポートする仮想的な実装を使用します。
以下に論理デバイスを用いて3つの VkQueue
を作成する例を示します。
VkDeviceQueueCreateInfo queueCreateInfo[2];
queueCreateInfo[0].queueFamilyIndex = 0; // 転送
queueCreateInfo[0].queueCount = 1;
queueCreateInfo[1].queueFamilyIndex = 1; // グラフィックス
queueCreateInfo[1].queueCount = 2;
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.pQueueCreateInfos = queueCreateInfo;
deviceCreateInfo.queueCreateInfoCount = 2;
vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device);
VkDevice
を作成した後、アプリケーションは vkGetDeviceQueue
を使って VkQueue
のハンドルを取得することができます。
VkQueue graphicsQueue0 = VK_NULL_HANDLE;
VkQueue graphicsQueue1 = VK_NULL_HANDLE;
VkQueue transferQueue0 = VK_NULL_HANDLE;
// どのような順番でも入手可能
vkGetDeviceQueue(device, 0, 0, &transferQueue0); // ファミリ 0 - キュー 0
vkGetDeviceQueue(device, 1, 1, &graphicsQueue1); // ファミリ 1 - キュー 1
vkGetDeviceQueue(device, 1, 0, &graphicsQueue0); // ファミリ 1 - キュー 0