Search code examples
vulkan

I didn't specify COMBINED_IMAGE_SAMPLER in my descriptorSet and yet was able to allocate?


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


Solution

  • 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.