队列的验证层错误: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

而来自 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 设置的)并导致错误,但我不知道我是否做错了什么。

在创建失败的设备后调用 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 个队列,但对于大多数硬件来说,这应该让你过去这个特定的失败。