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.
Image 1: Example of a descriptor that "describes" or "represents" a buffer object.
References:
- Direct3D 12 Descriptor Overview
- Vulkan Resource Descriptors
- Structure VkDescriptorBufferInfo
- Function vkBindBufferMemory
Pipelines and Descriptors:
Different stages of a pipeline access arbitrary resources like Buffers, Images or Samplers through descriptors.
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.
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.
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.
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.
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.
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
- Vulkan Docs | Descriptor Indexing
- DethRaid | How do you “descriptor set”?
- ChunkStories.xyz | A note on Descriptor Indexing
- Vulkan Docs | Descriptor and Buffer management
- Gasim Gasimzada | Managing bindless descriptors in Vulkan
- Wicked Engine | Bindless Descriptors
- The Khronos Group | Bringing Fortnite to Mobile with Vulkan and OpenGL ES - GDC 2019
- Adrian Courreges | GTA V Graphics Study
- Adrian Courreges | Doom 2016 Graphics Study
Credits
Written by Romualdo Villalobos