队列验证层错误:队列族索引在 pCreateInfo->pQueueCreateInfos 数组中不是唯一的

Validation Layer Error with Queues: QueueFamilyIndex is not unique within pCreateInfo->pQueueCreateInfos array

本文关键字:队列 数组 pQueueCreateInfos 唯一 gt 错误 验证 索引 pCreateInfo-      更新时间:2023-10-16

我正在创建一个 Vulkan 渲染器并设置一个 Vulkan 设备及其相应的队列。以前,由于我只创建了一个队列,因此创建队列没有任何问题,但是现在我正在创建其中的几个(一个用于图形,一个用于计算,一个用于传输),验证层在设备创建之后/期间抛出此错误:

VUID-VkDeviceCreateInfo-queueFamilyIndex-00372(ERROR / SPEC): msgNum: 0 - vkCreateDevice: pCreateInfo->pQueueCreateInfos[1].queueFamilyIndex (=0) is not unique within pCreateInfo->pQueueCreateInfos array. The Vulkan spec states:  (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkDeviceCreateInfo-queueFamilyIndex-00372)
Objects: 1
[0] 0x16933778a10, type: 2, name: NULL
VUID-VkDeviceCreateInfo-queueFamilyIndex-00372(ERROR / SPEC): msgNum: 0 - vkCreateDevice: pCreateInfo->pQueueCreateInfos[2].queueFamilyIndex (=0) is not unique within pCreateInfo->pQueueCreateInfos array. The Vulkan spec states:  (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkDeviceCreateInfo-queueFamilyIndex-00372)
Objects: 1
[0] 0x16933778a10, type: 2, name: NULL
VUID-VkDeviceCreateInfo-queueFamilyIndex-00372(ERROR / SPEC): msgNum: 0 - CreateDevice(): pCreateInfo->pQueueCreateInfos[1].queueFamilyIndex (=0) is not unique within pQueueCreateInfos. The Vulkan spec states:  (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkDeviceCreateInfo-queueFamilyIndex-00372)
Objects: 1
[0] 0x16933778a10, type: 3, name: NULL
VUID-VkDeviceCreateInfo-queueFamilyIndex-00372(ERROR / SPEC): msgNum: 0 - CreateDevice(): pCreateInfo->pQueueCreateInfos[2].queueFamilyIndex (=0) is not unique within pQueueCreateInfos. The Vulkan spec states:  (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-VkDeviceCreateInfo-queueFamilyIndex-00372)
Objects: 1
[0] 0x16933778a10, type: 3, name: NULL

来自vkCreateDeviceVkResultVK_ERROR_INITIALIZATION_FAILED

我的三个队列是这样创建的(这是非标准代码,它包装在我自己的结构中):

GraphicsQueueInfo.QueueFlag = VK_QUEUE_GRAPHICS_BIT;
GraphicsQueueInfo.QueuePriority = 1.0f;
ComputeQueueInfo.QueueFlag = VK_QUEUE_COMPUTE_BIT;
ComputeQueueInfo.QueuePriority = 1.0f;
TransferQueueInfo.QueueFlag = VK_QUEUE_TRANSFER_BIT;
TransferQueueInfo.QueuePriority = 1.0f;

QueueFlag 成员用于确定我们要从中建立的队列类型。这稍后在队列选择函数中使用(这是一个片段)

uint8 i = 0;
while (true)
{
if ((queueFamilies[i].queueCount > 0) && (queueFamilies[i].queueFlags & _QI.QueueFlag))
{
break;
}
i++;
}
QueueCreateInfo.queueFamilyIndex = i;

似乎所有队列最终都具有相同的 queueFamilyIndex(从 i 设置)并导致错误,但我不知道我是否做错了什么。

vulkan-1.dll也会在设备创建失败后调用vkGetDeviceQueue时崩溃。

在第二个块中,您将根据您找到的第一个队列_QI.QueueFlag,使用 i 填充QueueCreateInfo.queueFamilyIndex(我假设类型为 VkDeviceQueueCreateInfo)。

我还假设您在某种循环中调用此块,以便获取图形、计算和传输的队列。 因此,让我们假设您的块位于一个名为findQueueFamilyIndex(...)的函数中,并且您将其称为类似的东西......

std::vector<VkDeviceQueueCreateInfo> deviceQueueInfos;
deviceQueueInfos.push_back({});
findQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT, deviceQueueInfos.back());
deviceQueueInfos.push_back({});
findQueueFamilyIndex(VK_QUEUE_COMPUTE_BIT, deviceQueueInfos.back());
deviceQueueInfos.push_back({});
findQueueFamilyIndex(VK_QUEUE_TRANSFER_BIT, deviceQueueInfos.back());

这里的问题是,您几乎肯定会在这里获得所有三个队列的相同队列系列索引,这是非法的。 每个图形队列都必须支持计算和传输操作,因此您的循环

if ((queueFamilies[i].queueCount > 0) && (queueFamilies[i].queueFlags & _QI.QueueFlag))
{
break;
}

是选取队列系列索引的不良方法。您想要的是具有给定标志的队列的队列系列索引,以及尽可能少的其他标志。 像这样:

uint32_t targetIndex = UINT32_MAX;
uint32_t targetFlags = 0xFFFFFFFF;
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
// doesn't have the flag?  ignore this
if (0 == (queueFamilies[i].queueFlags & _QI.QueueFlag)) {
continue;
}
// first matching queue?  use it and continue
if (targetIndex == UINT32_MAX) {
targetIndex = i;
targetFlags = queueFamilies[i].queueFlags;
continue;
}
// Matching queue, but with fewer flags than the current best?  Use it.
if (countBits(queueFamilies[i].queueFlags) < countBits(targetFlags)) {
targetIndex = i;
targetFlags = queueFamilies[i].queueFlags;
continue;
}
}

如果要从给定队列系列中生成 N 个队列,则必须指定该系列一次,并说需要 N 个队列。 因此,如果您希望为不同类型的工作使用多个队列,最好的方法是首先找到给定工作类型的最佳队列系列索引。 这将是除您请求的VkQueueFlagBits数最少的那个。 例如,nVidia RTX 2080有3个队列系列。 一个专用于计算,一个专用于传输,一个支持所有 3 个。

因此,让我们假设您编写一个函数,该函数获取队列系列列表并返回给定队列的最佳系列索引:

uint32_t findBestQeueue(
VkQueueFlags desiredFlags, 
const std::vector<VkQueueFamilyProperties>& queueFamilies) 
{ ... }

然后你可以做的是这样的:

std::vector<VkQueueFamilyProperties> qfps;
... populate qfps using vkGetPhysicalDeviceQueueFamilyProperties ...
std::map<uint32_t, uint32_t> queueFamilyToQueueCount;
auto qfi = findBestQeueue(VK_QUEUE_TRANSFER_BIT, qfps);
queueFamilyToQueueCount[qfi] += 1;
qfi = findBestQeueue(VK_QUEUE_COMPUTE_BIT, qfps);
queueFamilyToQueueCount[qfi] += 1;
qfi = findBestQeueue(VK_QUEUE_TRANSFER_BIT, qfps);
queueFamilyToQueueCount[qfi] += 1;

现在,您有一个队列系列索引的映射,该映射显示了它们所需的队列计数。 然后,您可以将其转换为一个std::vector<VkDeviceQueueCreateInfo>然后可用于填充VkDeviceCreateInfo的相应成员。

请注意,这不是抓取队列的完美方法。 例如,可能只有一个队列系列和一个队列... 在这种情况下,此代码将失败,因为它从只有一个可用队列的家庭请求 3 个队列,但对于大多数硬件来说,这应该可以让您克服此特定故障。