Descriptor Sets

Descriptor Sets

[TOC]

Creating a sampler
Creating a sampled image
Creating a combined image sampler
Creating a storage image
Creating a uniform texel buffer
Creating a storage texel buffer
Creating a uniform buffer
Creating a storage buffer
Creating an input attachment
Creating a descriptor set layout
Creating a descriptor pool
Allocating descriptor sets
Updating descriptor sets
Binding descriptor sets
Creating descriptors with a texture and a uniform buffer
Freeing descriptor sets
Resetting a descriptor pool
Destroying a descriptor pool
Destroying a descriptor set layout
Destroying a sampler

在现代计算机图形学领域,多数image数据(vertices,pixels,fragments,voxel)的渲染和执行都是通过可编程pipeline和shaders实现.相关的:textures,samplers,buffers,uniform variables.在vulkan里这些通过descriptor sets提供.

Descriptors表示shader resources的不透明的structures.有descriptor set layouts指明内容,组织成groups或sets.为了给shaders提供资源,给Pipelines绑定descriptor sets.我们能一次绑定多个sets.为了从shaders内部访问resources,我们需要指定从哪个set以及从set中的哪个位置(称为binding)获取给定资源.

sampler

create a sampler

Samplers定义了image data加载到shader里的一组参数.包括address caculations(wrapping or repeating)、filtering(linear or nearest)、use mipmaps.

VkSamplerCreateInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
VkSamplerCreateInfo sampler_create_info = {
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
nullptr,
0,
mag_filter,
min_filter,
mipmap_mode,
u_address_mode,
v_address_mode,
w_address_mode,
lod_bias,
anisotropy_enable,
max_anisotropy,
compare_enable,
compare_operator,
min_lod,
max_lod,
border_color,
unnormalized_coords
};
1
2
3
4
5
6
7
VkResult result = vkCreateSampler( logical_device, &sampler_create_info,
nullptr, &sampler );
if( VK_SUCCESS != result ) {
std::cout << "Could not create sampler." << std::endl;
return false;
}
return true;

为了在shader里指明samplr,需要创建一个sampler uniform 变量

形如

1
layout (set=m, binding=n) uniform sampler <variable name>;

create a sampled image

sampled images用于在shaders里从images(textures)读取数据.通常是一起创建,VK_IMAGE_USAGE_SAMPLED_BIT usage.

在shaders里,我们能用多个samplers按照不同方式读取同一个image.也能一个sampler对应多个images.但有些平台,二者是何为一个obj的.

不是所有image foramt都支持sampled iamges.这依赖于app执行平台.下列是总能用于sampled images和linearly filtered sampled images的formats.不限于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VK_FORMAT_B4G4R4A4_UNORM_PACK16
VK_FORMAT_R5G6B5_UNORM_PACK16
VK_FORMAT_A1R5G5B5_UNORM_PACK16
VK_FORMAT_R8_UNORM and VK_FORMAT_R8_SNORM
VK_FORMAT_R8G8_UNORM and VK_FORMAT_R8G8_SNORM
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, and
VK_FORMAT_R8G8B8A8_SRGB
VK_FORMAT_B8G8R8A8_UNORM and VK_FORMAT_B8G8R8A8_SRGB
VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32,
and VK_FORMAT_A8B8G8R8_SRGB_PACK32
VK_FORMAT_A2B10G10R10_UNORM_PACK32
VK_FORMAT_R16_SFLOAT
VK_FORMAT_R16G16_SFLOAT
VK_FORMAT_R16G16B16A16_SFLOAT
VK_FORMAT_B10G11R11_UFLOAT_PACK32
VK_FORMAT_E5B9G9R9_UFLOAT_PACK32

其他格式性需要自己检查一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties( physical_device, format,
&format_properties );
if( !(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) ) {
std::cout << "Provided format is not supported for a sampled image." <<
std::endl;
return false;
}
if( linear_filtering &&
!(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) ) {
std::cout << "Provided format is not supported for a linear image
filtering." << std::endl;
return false;
}

如果满足需求,可以创建一个image,一个memory object,一个image view(vulkan中images are represented with iamge views most of the time).指明usage为VK_IMAGE_USAGE_SAMPLED_BIT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if( !CreateImage( logical_device, type, format, size, num_mipmaps,
num_layers, VK_SAMPLE_COUNT_1_BIT, usage | VK_IMAGE_USAGE_SAMPLED_BIT,
false, sampled_image ) ) {
return false;
}
if( !AllocateAndBindMemoryObjectToImage( physical_device, logical_device,
sampled_image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memory_object ) ) {
return false;
}
if( !CreateImageView( logical_device, sampled_image, view_type, format,
aspect, sampled_image_view ) ) {
return false;
}
return true;

当想用一个image作为sampled image,在加载数据到shaders前,我们需要变换image的layout为VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.

shader里

1
layout (set=m, binding=n) uniform texture2D <variable name>;

create a combined image sampler

创建和分开时是一样的,只是shaders里不一样.descriptor为VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if( !CreateSampler( logical_device, mag_filter, min_filter, mipmap_mode,
u_address_mode, v_address_mode, w_address_mode, lod_bias,
anisotropy_enable, max_anisotropy, compare_enable, compare_operator,
min_lod, max_lod, border_color, unnormalized_coords, sampler ) ) {
return false;
}
bool linear_filtering = (mag_filter == VK_FILTER_LINEAR) || (min_filter ==
VK_FILTER_LINEAR) || (mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR);
if( !CreateSampledImage( physical_device, logical_device, type, format,
size, num_mipmaps, num_layers, usage, view_type, aspect, linear_filtering,
sampled_image, sampled_image_view ) ) {
return false;
}
return true;

使用sampler关键词

1
layout (set=m, binding=n) uniform sampler2D <variable name>;

有些平台性能更好.

storage

create a storage image

storage image允许我们从images里加载数据到Pipelines,也可以从shader存储数据到images.这类images需要指明usage为VK_IMAGE_USAGE_STORAGE_BIT

虽然可以从这类images里load数据,单数据是unfiltered(所以不能sampler)

descriptors type为VK_DESCRIPTOR_TYPE_STORAGE_IMAGE

需要指明合适的格式,不是所有格式都支持storage images.与平台相关,单下列是都支持的(not limited).

1
2
3
4
5
6
7
8
9
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_UINT, and VK_FORMAT_R8G8B8A8_SINT
VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT and
VK_FORMAT_R16G16B16A16_SFLOAT
VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT and VK_FORMAT_R32_SFLOAT
VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT and
VK_FORMAT_R32G32_SFLOAT
VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT and
VK_FORMAT_R32G32B32A32_SFLOAT

如果想要原子操作,只能用如下格式

1
2
VK_FORMAT_R32_UINT
VK_FORMAT_R32_SINT

如果想用其他的,需要检查是否支持或是否支持原子操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties( physical_device, format,
&format_properties );
if( !(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) ) {
std::cout << "Provided format is not supported for a storage image." <<
std::endl;
return false;
}
if( atomic_operations &&
!(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT) ) {
std::cout << "Provided format is not supported for atomic operations on
storage images." << std::endl;
return false;
}

如果支持,如常创建images.指明usage为VK_IMAGE_USAGE_STORAGE_BIT,然后创建memory object,绑定都image,然后是image view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if( !CreateImage( logical_device, type, format, size, num_mipmaps,
num_layers, VK_SAMPLE_COUNT_1_BIT, usage | VK_IMAGE_USAGE_STORAGE_BIT,
false, storage_image ) ) {
return false;
}
if( !AllocateAndBindMemoryObjectToImage( physical_device, logical_device,
storage_image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memory_object ) ) {
return false;
}
if( !CreateImageView( logical_device, storage_image, view_type, format,
aspect, storage_image_view ) ) {
return false;
}
return true;

在load/store数据前需要设置layout为VK_IMAGE_LAYOUT_GENERAL.这是这些操作为唯一支持的layout.

GLSL的storage images定义的例子

1
layout (set=m, binding=n, r32f) uniform image2D <variable name>;

create uniform texel buffer

uniform texel buffer允许我们想从images里读取数据一样,他们的内容不是一个单值(scalar)的数组,而是格式化整个为pixels(texel)(1,2,3,4种分项).能比images访问更多的数据.

创建一个uniform texel buffer的buffer时usage为VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT

下列是可用于uniform texel buffers(not limited)的formats:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_UINT, and
VK_FORMAT_R8_SINT
VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_UINT,
and VK_FORMAT_R8G8_SINT
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_UINT, and VK_FORMAT_R8G8B8A8_SINT
VK_FORMAT_B8G8R8A8_UNORM
VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32,
VK_FORMAT_A8B8G8R8_UINT_PACK32, and
VK_FORMAT_A8B8G8R8_SINT_PACK32
VK_FORMAT_A2B10G10R10_UNORM_PACK32 and
VK_FORMAT_A2B10G10R10_UINT_PACK32
VK_FORMAT_R16_UINT, VK_FORMAT_R16_SINT and VK_FORMAT_R16_SFLOAT
VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SINT and
VK_FORMAT_R16G16_SFLOAT
VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT and
VK_FORMAT_R16G16B16A16_SFLOAT
VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT and VK_FORMAT_R32_SFLOAT
VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT and
VK_FORMAT_R32G32_SFLOAT
VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT and
VK_FORMAT_R32G32B32A32_SFLOAT
VK_FORMAT_B10G11R11_UFLOAT_PACK32

检查是否支持

1
2
3
4
5
6
7
8
9
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties( physical_device, format,
&format_properties );
if( !(format_properties.bufferFeatures &
VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) ) {
std::cout << "Provided format is not supported for a uniform texel
buffer." << std::endl;
return false;
}

然后create a buffer,memory object and bind it to the buffer,create a buffer view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if( !CreateBuffer( logical_device, size, usage |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, uniform_texel_buffer ) ) {
return false;
}
if( !AllocateAndBindMemoryObjectToBuffer( physical_device, logical_device,
uniform_texel_buffer, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memory_object )
) {
return false;
}
if( !CreateBufferView( logical_device, uniform_texel_buffer, format, 0,
VK_WHOLE_SIZE, uniform_texel_buffer_view ) ) {
return false;
}
return true;

uniform texel buffers,我们需要指明data format,以便shaders按照合适的方式访问buffer的内容,这就是buffer view的作用.

GLSL

1
layout (set=m, binding=n) uniform samplerBuffer <variable name>;

create a torage texel buffer

如果想在shader中存数据到buffer里,需要使用storage buffers,usage为VK_BUFFER_USAGE_STORAGE_BUFFER_BIT

descriptor types:VK_DESCRIPTOR_TYPE_STORAGE_BUFFER 或者VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC

storage buffers需要注意对齐问题,遵循GLSL的std430是最简单的方式.基本的对齐方式与Uniform buffer的数组和结构体差不多.它们的偏移量不需要四舍五入到16的倍数.规则如下:

  • A scalar variable of size N must be placed at offsets that are a multiple of N

  • A vector with two components, where each component has a size of N, must be

    placed at offsets that are a multiple of 2N

  • A vector with three or four components, where each component has a size of N,

    must be placed at offsets that are a multiple of 4N

  • An array with elements of size N must be placed at offsets that are a multiple of N

  • A structure must be placed at offsets that are a multiple of the biggest offset of

  • any of its members (a member with the biggest offset requirement)

  • A row-major matrix must be placed at an offset equal to the offset of a vector

    with the number of components equal to the number of columns in the matrix

  • A column-major matrix must be placed at the same offsets as its columns

dynamic storage buffers不同之处为它们的base memory offset被定义了.在描述符集更新期间为普通存储缓冲区指定的偏移量和范围在下一次更新之前保持不变.在动态变化的情况下,指定的偏移量将变为基址,随后由描述符集绑定到命令缓冲区时指定的动态偏移量修改.

GLSL中使用关键词buffer

1
2
3
4
5
6
layout (set=m, binding=n) buffer <variable name>
{
vec4 <member 1 name>;
mat4 <member 2 name>;
// ...
};

这里有个非常关键的信息没有说,关于对齐的计算

1
2
3
4
5
6
7
8
9
size_t minUboAlignment = device->properties.limits.minUniformBufferOffsetAlignment;
dynamicAlignment = sizeof (customstruct);
if (minUboAlignment > 0) {
dynamicAlignment = (dynamicAlignment + static_cast<uint32_t>(minUboAlignment - 1)) & ~(static_cast<uint32_t>(minUboAlignment - 1));
}
size_t bufferSize = count * dynamicAlignment;
ptr = (customstruct*)tl::alignedAlloc (bufferSize, dynamicAlignment);
std::cout << "minUniformBufferOffsetAlignment = " << minUboAlignment << std::endl;
std::cout << "dynamicAlignment = " << dynamicAlignment << std::endl;

create an input attachment

attachment是render passes中dc绘制的RT.

对于input attachments,通常为color或depth/stencil attachments,也可能是其他images.

usage:VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT

descriptors type:VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT

vulkan里render passes有一个及以上的subpass,在一个subpass写了attachment,后面的subpass可以读.也是唯一在shaders里读取attachments的方式.

当从input attachments读取数据时,我们仅限于与processed fragment的location对应的location.但这种方法可能比渲染到attachments中、结束render pass、将image绑定到作为sampled image(texture)的descriptor set以及启动另一个不将给定image用作其任何attachments的render pass更为理想.

对于Input attachments,也能用其他images(不必作为color or depth/stencil attachments).只需要使用usage VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT和合适的格式创建.下列格式是强制支持的input attachment(color).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
VK_FORMAT_R5G6B5_UNORM_PACK16
VK_FORMAT_A1R5G5B5_UNORM_PACK16
VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UINT and VK_FORMAT_R8_SINT
VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_UINT, and VK_FORMAT_R8G8_SINT
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UINT,
VK_FORMAT_R8G8B8A8_SINT, and VK_FORMAT_R8G8B8A8_SRGB
VK_FORMAT_B8G8R8A8_UNORM and VK_FORMAT_B8G8R8A8_SRGB
VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_A8B8G8R8_UINT_PACK32,
VK_FORMAT_A8B8G8R8_SINT_PACK32, and
VK_FORMAT_A8B8G8R8_SRGB_PACK32
VK_FORMAT_A2B10G10R10_UNORM_PACK32 and
VK_FORMAT_A2B10G10R10_UINT_PACK32
VK_FORMAT_R16_UINT, VK_FORMAT_R16_SINT and VK_FORMAT_R16_SFLOAT
VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SINT and
VK_FORMAT_R16G16_SFLOAT
VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT, and
VK_FORMAT_R16G16B16A16_SFLOAT
VK_FORMAT_R32_UINT, VK_FORMAT_R32_SINT, and VK_FORMAT_R32_SFLOAT
VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT, and
VK_FORMAT_R32G32_SFLOAT
VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SINT, and
VK_FORMAT_R32G32B32A32_SFLOAT

depth/stencil 强制支持的

1
2
3
4
5
VK_FORMAT_D16_UNORM
VK_FORMAT_X8_D24_UNORM_PACK32 or VK_FORMAT_D32_SFLOAT (at least one of
these two formats must be supported)
VK_FORMAT_D24_UNORM_S8_UINT or VK_FORMAT_D32_SFLOAT_S8_UINT (at
least one of these two formats must be supported)

其他格式需要检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties( physical_device, format,
&format_properties );
if( (aspect & VK_IMAGE_ASPECT_COLOR_BIT) &&
!(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) ) {
std::cout << "Provided format is not supported for an input attachment."
<< std::endl;
return false;
}
if( (aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_DEPTH_BIT)) &&
!(format_properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) ) {
std::cout << "Provided format is not supported for an input attachment."
<< std::endl;
return false;
}

然后创建image,allocate a memory object(或使用已有的),bind it to the image,create an image view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if( !CreateImage( logical_device, type, format, size, 1, 1,
VK_SAMPLE_COUNT_1_BIT, usage | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, false,
input_attachment ) ) {
return false;
}
if( !AllocateAndBindMemoryObjectToImage( physical_device, logical_device,
input_attachment, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memory_object ) ) {
return false;
}
if( !CreateImageView( logical_device, input_attachment, view_type, format,
aspect, input_attachment_image_view ) ) {
return false;
}
return true;

需要准备合适的render pass的descrition,包括framebuffers的image views.

GLSL

1
2
layout (input_attachment_index=i, set=m, binding=n) uniform subpassInput
<variable name>;

descriptor

create a descriptor set layout

descritor sets将很多resources(descriptors)收集到一个object里.之后再pipeline中建立了app和shaders的接口.但是硬件要知道什么资源组织在set里,每种有多少,什么顺序,我们需要创建descriptor set layout.

descriptor set layout指明了descriptor set的核心结构,同时,严格定义了什么资源能被它bound.

当创建layout需要知道什么资源(descriptor types)会被用以及他们的顺序.顺序是通过bindings指明的.shader里的index.

1
layout (set=m, binding=n) // variable definition

西安志明所有资源的列表

1
2
3
4
5
6
7
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
nullptr,
0,
static_cast<uint32_t>(bindings.size()),
bindings.data()
};

create layout

1
2
3
4
5
6
7
8
VkResult result = vkCreateDescriptorSetLayout( logical_device,
&descriptor_set_layout_create_info, nullptr, &descriptor_set_layout );
if( VK_SUCCESS != result ) {
std::cout << "Could not create a layout for descriptor sets." <<
std::endl;
return false;
}
return true;

descriptor set layouts也form了一个个pipeline layout,定义了已给pipeline能访问的resources type.created layouts是创建pipeline layout的一部分也是descriptor set allocation要求的.

createa a descriptor pool

descriptors由descriptor pools分配.创建descriptor pools时需要指明什么descriptors,多少,能从该pool创建.

VkDescriptorPoolCreateInfo

1
2
3
4
5
6
7
8
9
10
VkDescriptorPoolCreateInfo descriptor_pool_create_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
nullptr,
free_individual_sets ?

VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT : 0,
max_sets_count,
static_cast<uint32_t>(descriptor_types.size()),
descriptor_types.data()
};

注意多线程问题.

allocating descriptor sets

Descriptor sets汇集shader resources(descriptors)到一个object容器里.它的内容,types,资源数量由descritpor set layout定义.storage 从池里取,

descriptor sets提供了shaders的resources.他们形成了app和programmable pipeline stages的interface.这个interface的结构由descriptor set layouts定义.当使用image或buffer resources更新descriptor sets时提供了真实数据,然后在recording 操作绑定descriptor sets到cb.

1
2
3
4
5
6
7
VkDescriptorSetAllocateInfo descriptor_set_allocate_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
nullptr,
descriptor_pool,
static_cast<uint32_t>(descriptor_set_layouts.size()),
descriptor_set_layouts.data()
};

然后,allocate descriptor sets

1
2
3
4
5
6
7
8
descriptor_sets.resize( descriptor_set_layouts.size() );
VkResult result = vkAllocateDescriptorSets( logical_device,
&descriptor_set_allocate_info, descriptor_sets.data() );
if( VK_SUCCESS != result ) {
std::cout << "Could not allocate descriptor sets." << std::endl;
return false;
}
return true;

不幸的是,当我们分配和释放separate descriptor sets时,池的内存可能会变得fragmented(支离破碎).此时即使还没到上限也不能申请新的sets了.如下

第一次allocate descriptors sets,fragmentation问题不会发生

另外,如果每个descritor sets使用相同类型相同数量的资源也不会产生这个问题.

为了避免这个问题,需要释放一次释放所有descriptor sets,否则,只能创建一个新的pool.

updating descriptor sets

现在想提供特定的资源(samplers,image views,buffers,buffer views)(之后通过descriptor sets绑定到pipeline).定义应该使用的资源是通过update descriptor sets的过程来完成的.

一些自定义结构

samplers

1
2
3
4
5
6
7
struct ImageDescriptorInfo {
VkDescriptorSet TargetDescriptorSet;
uint32_t TargetDescriptorBinding;
uint32_t TargetArrayElement;
VkDescriptorType TargetDescriptorType;
std::vector<VkDescriptorImageInfo> ImageInfos;
};

uniform 和 storage buffers

1
2
3
4
5
6
7
struct BufferDescriptorInfo {
VkDescriptorSet TargetDescriptorSet;
uint32_t TargetDescriptorBinding;
uint32_t TargetArrayElement;
VkDescriptorType TargetDescriptorType;
std::vector<VkDescriptorBufferInfo> BufferInfos;
};

uniform and storage texel buffer

1
2
3
4
5
6
7
struct TexelBufferDescriptorInfo {
VkDescriptorSet TargetDescriptorSet;
uint32_t TargetDescriptorBinding;
uint32_t TargetArrayElement;
VkDescriptorType TargetDescriptorType;
std::vector<VkBufferView> TexelBufferViews;
};

可以从另一个descriptor拷贝.

1
2
3
4
5
6
7
8
9
struct CopyDescriptorInfo {
VkDescriptorSet TargetDescriptorSet;
uint32_t TargetDescriptorBinding;
uint32_t TargetArrayElement;
VkDescriptorSet SourceDescriptorSet;
uint32_t SourceDescriptorBinding;
uint32_t SourceArrayElement;
uint32_t DescriptorCount;
};

前面所有的结构都定义了应该更新的descriptor set的句柄、给定集内描述符的索引以及数组中的索引如果要更新的话.通过数组访问的描述符.其余参数是特定于类型的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
std::vector<VkWriteDescriptorSet> write_descriptors;
for( auto & image_descriptor : image_descriptor_infos ) {
write_descriptors.push_back( {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
image_descriptor.TargetDescriptorSet,
image_descriptor.TargetDescriptorBinding,
image_descriptor.TargetArrayElement,
static_cast<uint32_t>(image_descriptor.ImageInfos.size()),
image_descriptor.TargetDescriptorType,
image_descriptor.ImageInfos.data(),
nullptr,
nullptr
} );
}
for( auto & buffer_descriptor : buffer_descriptor_infos ) {
write_descriptors.push_back( {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
buffer_descriptor.TargetDescriptorSet,
buffer_descriptor.TargetDescriptorBinding,
buffer_descriptor.TargetArrayElement,
static_cast<uint32_t>(buffer_descriptor.BufferInfos.size()),
buffer_descriptor.TargetDescriptorType,
nullptr,
buffer_descriptor.BufferInfos.data(),
nullptr
} );
}
for( auto & texel_buffer_descriptor : texel_buffer_descriptor_infos ) {
write_descriptors.push_back( {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
texel_buffer_descriptor.TargetDescriptorSet,
texel_buffer_descriptor.TargetDescriptorBinding,
texel_buffer_descriptor.TargetArrayElement,
static_cast<uint32_t>(texel_buffer_descriptor.TexelBufferViews.size()),
texel_buffer_descriptor.TargetDescriptorType,
nullptr,
nullptr,
texel_buffer_descriptor.TexelBufferViews.data()
} );
}

也能复用其他sets的descriptor,更快.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::vector<VkCopyDescriptorSet> copy_descriptors;
for( auto & copy_descriptor : copy_descriptor_infos ) {
copy_descriptors.push_back( {
VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
nullptr,
copy_descriptor.SourceDescriptorSet,
copy_descriptor.SourceDescriptorBinding,
copy_descriptor.SourceArrayElement,
copy_descriptor.TargetDescriptorSet,
copy_descriptor.TargetDescriptorBinding,
copy_descriptor.TargetArrayElement,
copy_descriptor.DescriptorCount
} );
}

update descriptor sets

1
2
3
vkUpdateDescriptorSets( logical_device,
static_cast<uint32_t>(write_descriptors.size()), write_descriptors.data(),
static_cast<uint32_t>(copy_descriptors.size()), copy_descriptors.data() );

binding descriptor sets

创建好descriptor set后,需要在recording 操作中将之绑定到cb.

1
2
3
4
5
6
7
8
9
10
VkCommandBuffer command_buffer;
VkPipelineLayout pipeline_layout;
td::vector<VkDescriptorSet> descriptor_sets;
uint32_t index_for_first_set;
std::vector<uint32_t> dynamic_offsets;
vkCmdBindDescriptorSets( command_buffer, pipeline_type,
pipeline_layout, index_for_first_set, static_cast<uint32_t>
(descriptor_sets.size()), descriptor_sets.data(),
static_cast<uint32_t>(dynamic_offsets.size()),
dynamic_offsets.data() )

当我们record a command buffer,它的state是未定义的额.在record 与image 或 buffer资源相关的drawing操作前,我们需要给cb绑定合适的resources.通过vkCmdBindDescriptorSets()绑定descriptor sets实现的.

create descriptors with a texture and a uniform buffer

创建a combined image sampler和a uniform buffer为descriptors创建做准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if( !CreateCombinedImageSampler( physical_device, logical_device,
VK_IMAGE_TYPE_2D, VK_FORMAT_R8G8B8A8_UNORM, sampled_image_size, 1, 1,
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_COLOR_BIT, VK_FILTER_LINEAR,
VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
VK_SAMPLER_ADDRESS_MODE_REPEAT, VK_SAMPLER_ADDRESS_MODE_REPEAT, 0.0f,
false, 1.0f, false, VK_COMPARE_OP_ALWAYS, 0.0f, 0.0f,
VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, false,
sampler, sampled_image, sampled_image_memory_object, sampled_image_view )
) {
return false;
}
if( !CreateUniformBuffer( physical_device, logical_device,
uniform_buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, uniform_buffer,
uniform_buffer_memory_object ) ) {
return false;
}

然后准备定义descriptor set核心结构的layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::vector<VkDescriptorSetLayoutBinding> bindings = {
{
0,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1,
VK_SHADER_STAGE_FRAGMENT_BIT,
nullptr
},
{
1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
nullptr
}
};
if( !CreateDescriptorSetLayout( logical_device, bindings,
descriptor_set_layout ) ) {
return false;
}

组后,用一开始创建的resources更新descriptor set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
std::vector<ImageDescriptorInfo> image_descriptor_infos = {
{
descriptor_sets[0],
0,
0,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
{
{
sampler,
sampled_image_view,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
}
}
}
};
std::vector<BufferDescriptorInfo> buffer_descriptor_infos = {
{
descriptor_sets[0],
1,
0,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
{
{
uniform_buffer,
0,
VK_WHOLE_SIZE
}
}
}
};
UpdateDescriptorSets( logical_device, image_descriptor_infos,
buffer_descriptor_infos, {}, {} );
return true;

destroy

free descriptor sets

如果向将descriptor set的内存归还给pool,可以free它.归还后可以用来创建另一个,但是可能会由于Pool内存的碎片化导致失败.

能够一次释放多个descriptor sets,(来自同一个pool的)

1
2
3
4
5
6
7
8
9
VkResult result = vkFreeDescriptorSets( logical_device, descriptor_pool,
static_cast<uint32_t>(descriptor_sets.size()), descriptor_sets.data() );
if( VK_SUCCESS != result ) {
std::cout << "Error occurred during freeing descriptor sets." <<
std::endl;
return false;
}
descriptor_sets.clear();
return true;

reset a descriptor pool

能够一次释放一个pool的所有descriptor sets.

如果pool创建flag没有VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,该方法是唯一释放其descriptor sets的方法.

1
2
3
4
5
6
7
VkResult result = vkResetDescriptorPool( logical_device, descriptor_pool, 0
);
if( VK_SUCCESS != result ) {
std::cout << "Error occurred during descriptor pool reset." << std::endl;
return false;
}
return true;

destroy a descriptor pool

1
2
3
4
if( VK_NULL_HANDLE != descriptor_pool ) {
vkDestroyDescriptorPool( logical_device, descriptor_pool, nullptr );
descriptor_pool = VK_NULL_HANDLE;
}

destroy a descriptor set layout

1
2
3
4
5
if( VK_NULL_HANDLE != descriptor_set_layout ) {
vkDestroyDescriptorSetLayout( logical_device, descriptor_set_layout,
nullptr );
descriptor_set_layout = VK_NULL_HANDLE;
}

destroy a sampler

1
2
3
4
if( VK_NULL_HANDLE != sampler ) {
vkDestroySampler( logical_device, sampler, nullptr );
sampler = VK_NULL_HANDLE;
}