0

I made a simple vulkan example that draws a quad with a texture. It worked fine. But after some time I came back to it and noticed a mistake, and I'm not sure how this is supposed to be working.

In the descriptorSetLayout I have two sets, with one descriptor each. One is of type UNIFORM_BUFFER and the other is COMBINED_IMAGE_SAMPLER.

{
    const VkDescriptorSetLayoutBinding bindings[] = {
        {
            .binding = 0,
            .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
            .descriptorCount = 1, // would be more than 1 for arrays of buffers
            .stageFlags = VK_SHADER_STAGE_ALL
        },
        {
            .binding = 1,
            .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        }
    };
    const VkDescriptorSetLayoutCreateInfo info = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = std::size(bindings),
        .pBindings = bindings,
    };
    vkRes = vkCreateDescriptorSetLayout(vk.device, &info, nullptr, &vk.descriptorSetLayout);
    assert(vkRes == VK_SUCCESS);
}

But I noticed that in my descriptor pool I forgot to specify COMBINED_IMAGE_SAMPLER descriptors.

{ // -- create descriptor pool
    const VkDescriptorPoolSize sizes[] = { {
            .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
            .descriptorCount = 2,
    } };
    const VkDescriptorPoolCreateInfo info = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
        .maxSets = 2,
        .poolSizeCount = std::size(sizes),
        .pPoolSizes = sizes
    };
    vkRes = vkCreateDescriptorPool(vk.device, &info, nullptr, &vk.descriptorPool);
    assert(vkRes == VK_SUCCESS);
}

And later I allocate descriptor sets using the layout that I showed earlier (it has a COMBINED_IMAGE_SAMPLER descriptor).

{ // allocate descriptor sets
    const VkDescriptorSetLayout layouts[] = {vk.descriptorSetLayout, vk.descriptorSetLayout};
    VkDescriptorSetAllocateInfo info = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = vk.descriptorPool,
        .descriptorSetCount = 2,
        .pSetLayouts = layouts,
    };
    vkRes = vkAllocateDescriptorSets(vk.device, &info, vk.descriptorSets);
    assert(vkRes == VK_SUCCESS);
}

How this is possible? I though vkAllocateDescriptorSets would return something other than VK_SUCCESS? According to the spec, it can return VK_ERROR_OUT_OF_*_MEMORY.

This is the whole code (couldn't paste it here because it's too long): https://gist.github.com/tuket/88c971a3dbe1ddf3b7151e92ebfd505c

tuket
  • 3,232
  • 1
  • 26
  • 41

1 Answers1

1

Vulkan is not a safe API. Most programming errors are not checked for by Vulkan implementations. In general, the only errors that Vulkan APIs test for are errors that a programmer cannot possibly fix.

For example, vkAllocateDescriptorSets thus errors out when you fragment the descriptor pool too much to fulfill the allocation. That's an implementation detail of the pool, and it therefore isn't something you can track and prevent. Therefore, the implementation catches it, and your code must handle the error.

But the mistake you made is a programming error; it's a misuse of the API. These are not tested for (in most circumstances). You are expected to make correct use of the API, and if not, then undefined behavior occurs. And UB may manifest as something which appears to work... today.

This is what Vulkan validation layers are for. They verify (some) programming errors, so that you can fix them in your application.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • The validation layers didn't spot this error – tuket Sep 14 '22 at 21:32
  • But the spec says: "the allocation may fail due to lack of space if the call to vkAllocateDescriptorSets would cause the number of any given descriptor type to exceed the sum of all the descriptorCount members of each element of VkDescriptorPoolCreateInfo::pPoolSizes with a type equal to that type." ... "If the allocation fails due to no more space in the descriptor pool, and not because of system or device memory exhaustion, then VK_ERROR_OUT_OF_POOL_MEMORY must be returned" – tuket Sep 14 '22 at 21:46
  • 2
    @tuket: The key phrase being "may fail". The allocation *didn't* fail, so no error need be returned. That is, the implementation does not *have to* check the limits you gave it. – Nicol Bolas Sep 14 '22 at 21:52
  • That's interesting. I guess the number of descriptors you provide is just a minimum, but the driver implementation could allocate space for more descriptors, and therefore not fail even if you exceed that number you provided. Probably it's a good thing as it gives drivers the freedom to potentially optimize. Sadly, I don't think the validation layers could do anything about my mistake; it's not really an invalid usage of the API (failing to allocate is not a misuse of the API, just an error that can/should be handled) – tuket Sep 15 '22 at 12:18