Raw Source Code

Vulkan Descriptor Sets cheat sheet

An illustrated glossary and mental model of Vulkan's descriptor sets, their setup and relation with other entities like pipelines, layouts, buffers, etc...

Descriptor:

Describes a resource to the GPU such as a buffer, image or sampler.

Image1: Example of a descriptor that describes or represents a buffer object.

Image 1: Example of a descriptor that "describes" or "represents" a buffer object.

References:


Pipelines and Descriptors:

Different stages of a pipeline access arbitrary resources like Buffers, Images or Samplers through descriptors.

Image2: Graphic Pipeline (1) reads buffer A at Vertex and Fragment stage and reads buffer B at Vertex stage; Compute Pipeline (2) reads buffer B at Compute stage; Compute Pipeline (3) reads image C, sampler D and uniform buffer E at Compute stage.

Image 2: Graphic Pipeline (1) reads buffer A at Vertex and Fragment stage and reads buffer B at Vertex stage; Compute Pipeline (2) reads buffer B at Compute stage; Compute Pipeline (3) reads image C, sampler D and uniform buffer E at Compute stage.

References:


Descriptor Sets:

A pipeline cannot access a descriptor individually, we need to group them in sets.

Image3: Some shader stages at Graphic Pipeline (1) read from set A while also read and write from set B; shader at Compute Pipeline (2) reads and writes from set B; shader at Compute Pipeline (3) reads and writes from set B.

Image 3: Some shader stages at Graphic Pipeline (1) read from set A while also read and write from set B; shader at Compute Pipeline (2) reads and writes from set B; shader at Compute Pipeline (3) reads and writes from set B.

You define how many and what type of descriptors a set will have (layout of the set) using a VkDescriptorSetLayout object.

Image4: Two descriptor sets describing different resources.

Image 4: Two descriptor sets describing different resources.

A descriptor set is structured as a sequence of slots known as bindings, each binding represents a single type of descriptor, for instance buffer, image, sampler, etc… A binding also defines how many descriptors of the given type will be available at a given slot and on what pipeline stages the described resources will be accessible, see Structure VkDescriptorSetLayoutCreateInfo.

References:


Allocating a Descriptor Set:

You allocate descriptor sets from a pool of descriptors.

Image5: A descriptor pool with 12 descriptors, 3 of type image, 7 of type buffer and 2 of type sampler.

Image 5: A descriptor pool with 12 descriptors, 3 of type image, 7 of type buffer and 2 of type sampler.

When creating a descriptor pool you indicate how many descriptors the pool will have and the max amount of descriptor sets that can take descriptors from the pool.

References:


Pipeline Layout:

The pipeline layout defines what descriptors are accessible from a given pipeline.

Image6: A pipeline layout defining what descriptor sets are accessible from the pipeline.

Image 6: A pipeline layout defining what descriptor sets are accessible from the pipeline.

The pipeline layout represents a sequence of descriptor sets with each having a specific descriptor set layout.

References:


Making Descriptors point to Resources:

Descriptors can be thought as some kind of pointers to resources. To update a descriptor so it points and represents an actual resource you use the function vkUpdateDescriptorSets.

Image7: A set with a single descriptor that is being updated to identify an actual buffer resource.

Image 7: A set with a single descriptor that is being updated to identify an actual buffer resource.

References:


Using pipelines and descriptor sets:

Once you have a pipeline and a descriptor set you can bind them together for actual usage in the pipeline with the function vkCmdBindDescriptorSets.

References:


Code Example | Creating descriptors:

Creating a descriptor set layout:

// Binding 0 will describes 1 uniform buffer
const VkDescriptorSetLayoutBinding BindingDescription
{
  .binding = 0,
  .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
  .descriptorCount = 1,
  .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
  .pImmutableSamplers = nullptr
};

// The layout of a DescriptorSet that contains a single Descriptor  
const VkDescriptorSetLayoutCreateInfo DescriptorSetLayoutCreateInfo
{
  .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .bindingCount = 1,
  .pBindings = &BindingDescription
};

// Handle to a descriptor set layout object
VkDescriptorSetLayout DescriptorSetLayout;

// Create the descriptor set layout
if (vkCreateDescriptorSetLayout(
  DeviceHandle, // VkDevice                                    
  &DescriptorSetLayoutCreateInfo, // VkDescriptorSetLayoutCreateInfo
  nullptr, // VkAllocationCallbacks
  &DescriptorSetLayout) != VK_SUCCESS)
{
  assert(false);
}

Creating a descriptor pool:

// The amount of `Descriptors` representing `Uniform Buffers` in the descriptor pool
const VkDescriptorPoolSize DescriptorPoolSize
{
  .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
  .descriptorCount = 1
};

// Create info for the descriptor pool
const VkDescriptorPoolCreateInfo DescriptorPoolCreateInfo
{
  .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,
  .maxSets = 1, // Max DescriptorSets allocatable from this pool
  .poolSizeCount = 1,
  .pPoolSizes = &DescriptorPoolSize
};

// Handle to the descriptor pool object
VkDescriptorPool DescriptorPool;

// Create the descriptor pool
if (vkCreateDescriptorPool(
  DeviceHandle, // VkDevice
  &DescriptorPoolCreateInfo, // VkDescriptorPoolCreateInfo
  nullptr, // VkAllocationCallbacks
  &DescriptorPool) != VK_SUCCESS)
{
  assert(false);
}

Allocating a descriptor set

// Create info for the DescriptorSet
const VkDescriptorSetAllocateInfo DescriptorSetAllocateInfo
{
    .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
    .pNext = nullptr,
    .descriptorPool = DescriptorPool,
    .descriptorSetCount = 1,
    .pSetLayouts = &DescriptorSetLayout
};

// Handle to the DescriptorSet object
VkDescriptorSet DescriptorSet;

if (vkAllocateDescriptorSets(
  DeviceHandle, // VkDevice
  &DescriptorSetAllocateInfo, // VkDescriptorSetAllocateInfo
  &DescriptorSet) != VK_SUCCESS)
{
    assert(false);
}

Making descriptors point to resources:

// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorBufferInfo.html
VkDescriptorBufferInfo DescriptorBufferInfo
{
  .buffer = MyUniformBufferHandle, // VkBuffer
  .offset = 0, // offset
  .range = VK_WHOLE_SIZE // from offset to end of buffer
};

// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html
const VkWriteDescriptorSet WriteDescriptorSet
{
  .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
  .pNext = nullptr,
  .dstSet = DescriptorSet,
  .dstBinding = 0,
  .dstArrayElement = 0,
  .descriptorCount = 1,
  .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
  .pImageInfo = nullptr,
  .pBufferInfo = &DescriptorBufferInfo,
  .pTexelBufferView = nullptr
};

// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkUpdateDescriptorSets.html
vkUpdateDescriptorSets(
  DeviceHandle, // VkDevice
  1, // descriptorWriteCount
  &WriteDescriptorSet, // VkWriteDescriptorSet
  0, // descriptorCopyCount
  nullptr); // VkCopyDescriptorSet

Code Example | Making descriptors accesible in a pipeline:

Creating a pipeline layout:

// A pipeline layout representing
// a pipeline that has access
// to a single descriptor set
const VkPipelineLayoutCreateInfo PipelineLayoutCreateInfo
{
  .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
  .pNext = nullptr,
  .flags = 0,

  .setLayoutCount = 1,
  .pSetLayouts = &DescriptorSetLayout,

  .pushConstantRangeCount = 0,
  .pPushConstantRanges = nullptr
};

// Handle to the new pipeline layout object
VkPipelineLayout PipelineLayout;

if (vkCreatePipelineLayout(
  DeviceHandle, // VkDevice
  &PipelineLayoutCreateInfo, // VkPipelineLayoutCreateInfo
  nullptr, // VkAllocationCallbacks
  &PipelineLayout) != VK_SUCCESS)
{
  assert(false);
}

Creating a pipeline:

// Create info for the pipeline
const VkGraphicsPipelineCreateInfo PipelineCreateInfo
{
  .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,

  // more code...

  .layout = PipelineLayout

  // more code...
}

// Handle to the graphics pipeline object
VkPipeline GraphicsPipeline;

if (vkCreateGraphicsPipelines(
  /* more code... */
  &PipelineCreateInfo,
  /* more code... */
  GraphicsPipeline) != VK_SUCCESS)
{
  assert(false);
}

Code Example | Using descriptors:

Shader code

// This shader requires a pipeline that has a uniform buffer at set 0 -> binding 0
layout(set = 0, binding = 0) uniform MyUniformBufferData
{
    mat4 Foo;
    mat4 Var;
    mat4 Zas;
    mat4 Boo;
} uMyUniformBufferData;

Binding descriptors with an actual pipeline

// Bind DescriptorSet to any GRAPHIC pipeline
vkCmdBindDescriptorSets(
  CommandBufferHandle,

  VK_PIPELINE_BIND_POINT_GRAPHICS, // Type of the pipeline that will use the descriptors
  PipelineLayout, // The pipeline layout

  /* more code... */

  1, // How many descriptor sets to bind?
  &DescriptorSet,

  /* more code... */);

// In this example subsequent commands
// that interact with a pipeline of GRAPHICS type 
// will have access to the bound descriptors


Further lectures

Credits

Written by Romualdo Villalobos

All rights reserved.