How to bind multiple textures using vkCmdBindDescriptorSets?

Hi, the real problem is at the bottom of this post because it’s close to the problem, and there are explanation of my problems in the comments in the codes.

Goal
I want to bind multiple textures using vkCmdBindDescriptorSets, because I want multiple textures (Fonts Texture and Icons Texture), and those codes are referenced from professional developers.

A screenshot to prove that I have a good hope asking for real help

Vulkan is the hardest backend
There is a missing feature in the original code from professional developers because it cannot render texture for Vulkan, but only for OpenGL, DirectX or something. So I want to make that texture possible with Vulkan, but it’s difficult for me, so please help, I spent a lot of time with this. Thank you very much.

// A function that works perfectly
void ImGui_ImplVulkan_UpdateFontsTexture(VkImageView fontView)
{
    // Referenced from ImGui_ImplVulkan_CreateFontsTexture
    ImGui_ImplVulkan_Data *bd = ImGui_ImplVulkan_GetBackendData();
    ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;

  //bd->FontSampler = ;
    bd->FontMemory = nullptr;
    bd->FontImage = nullptr;
    bd->FontView = fontView;
    bd->UploadBufferMemory = nullptr;
    bd->UploadBuffer = nullptr;

    // Update the Descriptor Set:
    {
        VkDescriptorImageInfo desc_image[1] = {};
        desc_image[0].sampler = bd->FontSampler;
        desc_image[0].imageView = bd->FontView;
        desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        VkWriteDescriptorSet write_desc[1] = {};
        write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        write_desc[0].dstSet = bd->DescriptorSet;
        write_desc[0].descriptorCount = 1;
        write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        write_desc[0].pImageInfo = desc_image;
        vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL);
    }

    // Store our identifier
    ImGuiIO& io = ImGui::GetIO();
    io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontImage);
}

// A copy function of the above with modification but it doesn't work
void ImGui_ImplVulkan_UpdateIconsTexture(VkImageView iconsView)
{
    ImGui_ImplVulkan_Data *bd = ImGui_ImplVulkan_GetBackendData();
    ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;

    // Update the Descriptor Set:
    {
        VkDescriptorImageInfo desc_image[1] = {};
        desc_image[0].sampler = bd->FontSampler;
        //_/--------------------------------------------------\_
        desc_image[0].imageView = iconsView; // <-- This is a modification.
        // \--------------------------------------------------/
        desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        VkWriteDescriptorSet write_desc[1] = {};
        write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        //_/--------------------------------------------------\_
        write_desc[0].dstSet = bd->DescriptorSet_icons; // <-- This is a modification.
        // \--------------------------------------------------/
        write_desc[0].descriptorCount = 1;
        write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        write_desc[0].pImageInfo = desc_image;
        vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL);
    }
}

How I call the 2 functions above

ImGui_ImplVulkan_UpdateFontsTexture(_fontAtlasTexture->_vk._imageView);

...

_copyCmd->begin();
TextureCreateInfo CI = {};
CI.type = TextureCreateInfoType_UVGrid;
CI.extent = { 128, 128, 1 };
CI.color = 0xff0000ff;
_iconsTexture = new Texture(this, _copyCmd, CI);
_copyCmd->end();
ImGui_ImplVulkan_UpdateIconsTexture(_iconsTexture->_vk._imageView);
// ^ ^ ^ ^ I'm sure that my Texture class works because I tried it with
// the other working function ImGui_ImplVulkan_UpdateFontsTexture
// just for testing and there is no problem.
// But I want 2 different textures in my GUI, FontsTexture and IconsTexture.

How the device objects are created
Note that most of the codes below are correct, and referenced from professional developers, but the only modification I made is indicated in a comment.

bool ImGui_ImplVulkan_CreateDeviceObjects()
{
    ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
    ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
    VkResult err;

    if (!bd->FontSampler)
    {
        VkSamplerCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
        info.magFilter = VK_FILTER_LINEAR;
        info.minFilter = VK_FILTER_LINEAR;
        info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
        info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        info.minLod = -1000;
        info.maxLod = 1000;
        info.maxAnisotropy = 1.0f;
        err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->FontSampler);
        check_vk_result(err);
    }

    if (!bd->DescriptorSetLayout)
    {
        VkSampler sampler[1] = {bd->FontSampler};
        VkDescriptorSetLayoutBinding binding[1] = {};
        binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        binding[0].descriptorCount = 1;
        binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
        binding[0].pImmutableSamplers = sampler;
        VkDescriptorSetLayoutCreateInfo info = {};
        info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
        info.bindingCount = 1;
        info.pBindings = binding;
        err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &bd->DescriptorSetLayout);
        check_vk_result(err);
    }

    // Create Descriptor Set:
    {
        VkDescriptorSetAllocateInfo alloc_info = {};
        alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
        alloc_info.descriptorPool = v->DescriptorPool;
        alloc_info.descriptorSetCount = 1;
        alloc_info.pSetLayouts = &bd->DescriptorSetLayout;
        err = vkAllocateDescriptorSets(v->Device, &alloc_info, &bd->DescriptorSet);
        check_vk_result(err);
    }

    //_/--------------------------------------------------\_
    // --> Create Descriptor Set (icons)  <-- This is the modification I made,
    // and it doesn't work.
    {
        VkDescriptorSetAllocateInfo alloc_info = {};
        alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
        alloc_info.descriptorPool = v->DescriptorPool;
        alloc_info.descriptorSetCount = 1;
        alloc_info.pSetLayouts = &bd->DescriptorSetLayout;
        err = vkAllocateDescriptorSets(v->Device, &alloc_info, &bd->DescriptorSet_icons); // <-- Modification
        check_vk_result(err);
    }
	// \--------------------------------------------------/

    if (!bd->PipelineLayout)
    {
        // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix
        VkPushConstantRange push_constants[1] = {};
        push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
        push_constants[0].offset = sizeof(float) * 0;
        push_constants[0].size = sizeof(float) * 4;
        VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout };
        VkPipelineLayoutCreateInfo layout_info = {};
        layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
        layout_info.setLayoutCount = 1;
        layout_info.pSetLayouts = set_layout;
        layout_info.pushConstantRangeCount = 1;
        layout_info.pPushConstantRanges = push_constants;
        err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &bd->PipelineLayout);
        check_vk_result(err);
    }

    ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, bd->RenderPass, v->MSAASamples, &bd->Pipeline, bd->Subpass);

    return true;
}

How I bind the descriptor set (for icons) and the draw

// Bind descriptor set (This is a code I wrote, maybe not perfect?)
{
    // Referenced from "gltfloading.cpp" > drawNode in "Vulkan C++ examples and demos" https://github.com/SaschaWillems/Vulkan
    ImGui_ImplVulkan_Data *bd = ImGui_ImplVulkan_GetBackendData();
    vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->DescriptorSet_icons, 0, nullptr);
}

// Draw
vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0);

This is the error I get:

[vulkan] Debug report from ObjectType: VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT
Validation Error: [ VUID-vkCmdBindDescriptorSets-firstSet-00360 ] Object 0: handle = 0x1ca5a560270, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x1893ae29 | vkCmdBindDescriptorSets(): Sum of firstSet (1) and descriptorSetCount (1) is greater than VkPipelineLayoutCreateInfo::setLayoutCount (1) when pipeline layout was created The Vulkan spec states: The sum of firstSet and descriptorSetCount must be less than or equal to VkPipelineLayoutCreateInfo::setLayoutCount provided when layout was created (Vulkan® 1.2.182 - A Specification (with all registered extensions))


I don’t understand the error message, I want multiple Descriptor Sets for all textures, because there can be unlimited many textures, and I try to bind all the descriptor sets per Render Frame. The error message may doesn’t telling the real problem because maybe I’m in the wrong direction, maybe the solution has nothing to do with LayoutCount. Please help.

You seem to be having some difficulty understanding the difference between actual descriptor set objects (which reference specific resources) and descriptor set indices referenced by a bound pipeline.

A pipeline layout references one or more descriptor set layouts as an array of layouts. The indices of a layout within that array is important, as it defines how the pipeline finds its descriptor data.

You bind descriptor sets to a particular index; this defined by the firstSet and descriptorSetCount parameters that you pass to vkCmdBindDescriptorSets. The pipeline layout’s set indices match the set indices you bind to the command buffer. So if a shader looks at set index 0, descriptor 3, then this means it looks at the third descriptor in whatever descriptor set is bound to set index 0.

Your pipeline has only 1 descriptor set (as defined by VkPipelineLayoutCreateInfo::setLayoutCount). Therefore, all of its data is expected to come from set 0. You bound a descriptor set to set 1, but the pipeline doesn’t look there.

Hence the error.

You cannot bind a bunch of descriptor sets and make a pipeline look for which one to use. You bind the descriptor set containing the resources you want the pipeline to use for a particular set of rendering calls. That’s the way it’s supposed to work.

If you want to give the pipeline a bunch of textures and have it use shader logic to pick which one to use without having to bind different descriptor sets, that’s possible but it wouldn’t work through having a bunch of descriptor sets. The two ways to handle this would be to use a single, large array texture or to use arrays of samplers.

1 Like

Thank you so much, but I still need to think.
EDIT: I deleted my wrong explanation because I’m thinking, I’m still learning.

Method 1 - a bunch of vkCmdBindDescriptorSets
Maybe, I will come back after a few days because Vulkan is extremely difficult for me. Sascha Willems (the author), he binds a bunch of descriptor sets in an example I indicated (“gltfloading.cpp” > drawNode in “Vulkan C++ examples and demos” GitHub - SaschaWillems/Vulkan: Examples and demos for the new Vulkan API).

Method 2 - one single vkCmdBindDescriptorSets (suggestion by the same author)
A quote from Sascha Willems in the link you gave to me (the author of the Vulkan Examples in the links in my post).

… This is only one way of doing it. You don’t necessarily have to create one descriptor set per mesh or per texture. If your mesh e.g. uses 4 different textures, you could bind all of them at once to different binding points and select them in the shader.

And this is a code he showed:

VkDescriptorSet sets[3] = { sceneDescriptor, textureDescriptors[0], m_transform_descriptor_set };
vkCmdBindDescriptorSets(m_draw_command[inCommandIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 3, sets, 0, NULL);

Sascha is saying that you would have a bunch of texture descriptors inside of the set called textureDescriptors[0]. You’re too focused on how many sets get bound and not focused on what is in those sets.

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.