队列的验证层错误:QueueFamilyIndex 在 pCreateInfo->pQueueCreateInfos 数组中不是唯一的
Validation Layer Error with Queues: QueueFamilyIndex is not unique within pCreateInfo->pQueueCreateInfos array
我正在创建一个 Vulkan 渲染器并设置一个 Vulkan 设备及其相应的队列。以前我在创建队列时没有遇到任何问题,因为我只创建了一个,但现在我正在创建其中的几个(一个用于图形,一个用于计算,一个用于传输)验证层抛出此错误 after/during 设备创建:
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
而来自 vkCreateDevice 的 VkResult 是 VK_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 设置的)并导致错误,但我不知道我是否做错了什么。
在创建失败的设备后调用 vkGetDeviceQueue 时,vulkan-1.dll 也会崩溃。
在你的第二个块中,你将根据你用 _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 个队列。因此,如果您想要针对不同类型的工作使用多个队列,最好的方法是首先为给定类型的工作找到 best 队列系列索引。这将是 VkQueueFlagBits
数量最少的一个,而不是您请求的那个。例如,nVidia RTX 2080 有 3 个队列系列。一个专用于计算,一个专用于传输,一个支持所有 3。
因此,假设您编写了一个函数,该函数采用队列系列列表和 returns 给定队列的最佳系列索引:
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 个队列,但对于大多数硬件来说,这应该让你过去这个特定的失败。
我正在创建一个 Vulkan 渲染器并设置一个 Vulkan 设备及其相应的队列。以前我在创建队列时没有遇到任何问题,因为我只创建了一个,但现在我正在创建其中的几个(一个用于图形,一个用于计算,一个用于传输)验证层抛出此错误 after/during 设备创建:
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
而来自 vkCreateDevice 的 VkResult 是 VK_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 设置的)并导致错误,但我不知道我是否做错了什么。
在创建失败的设备后调用 vkGetDeviceQueue 时,vulkan-1.dll 也会崩溃。
在你的第二个块中,你将根据你用 _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 个队列。因此,如果您想要针对不同类型的工作使用多个队列,最好的方法是首先为给定类型的工作找到 best 队列系列索引。这将是 VkQueueFlagBits
数量最少的一个,而不是您请求的那个。例如,nVidia RTX 2080 有 3 个队列系列。一个专用于计算,一个专用于传输,一个支持所有 3。
因此,假设您编写了一个函数,该函数采用队列系列列表和 returns 给定队列的最佳系列索引:
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 个队列,但对于大多数硬件来说,这应该让你过去这个特定的失败。