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 | VkSamplerCreateInfo sampler_create_info = { |
1 | VkResult result = vkCreateSampler( logical_device, &sampler_create_info, |
为了在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 | VK_FORMAT_B4G4R4A4_UNORM_PACK16 |
其他格式性需要自己检查一下
1 | VkFormatProperties format_properties; |
如果满足需求,可以创建一个image,一个memory object,一个image view(vulkan中images are represented with iamge views most of the time).指明usage为VK_IMAGE_USAGE_SAMPLED_BIT
1 | if( !CreateImage( logical_device, type, format, size, num_mipmaps, |
当想用一个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 | if( !CreateSampler( logical_device, mag_filter, min_filter, mipmap_mode, |
使用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 | VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, |
如果想要原子操作,只能用如下格式
1 | VK_FORMAT_R32_UINT |
如果想用其他的,需要检查是否支持或是否支持原子操作.
1 | VkFormatProperties format_properties; |
如果支持,如常创建images.指明usage为VK_IMAGE_USAGE_STORAGE_BIT,然后创建memory object,绑定都image,然后是image view.
1 | if( !CreateImage( logical_device, type, format, size, num_mipmaps, |
在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 | VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_UINT, and |
检查是否支持
1 | VkFormatProperties format_properties; |
然后create a buffer,memory object and bind it to the buffer,create a buffer view:
1 | if( !CreateBuffer( logical_device, size, usage | |
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 | layout (set=m, binding=n) buffer <variable name> |
这里有个非常关键的信息没有说,关于对齐的计算
1 | size_t minUboAlignment = device->properties.limits.minUniformBufferOffsetAlignment; |
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 | VK_FORMAT_R5G6B5_UNORM_PACK16 |
depth/stencil 强制支持的
1 | VK_FORMAT_D16_UNORM |
其他格式需要检查
1 | VkFormatProperties format_properties; |
然后创建image,allocate a memory object(或使用已有的),bind it to the image,create an image view.
1 | if( !CreateImage( logical_device, type, format, size, 1, 1, |
需要准备合适的render pass的descrition,包括framebuffers的image views.
GLSL
1 | layout (input_attachment_index=i, set=m, binding=n) uniform subpassInput |
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 | VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = { |
create layout
1 | VkResult result = vkCreateDescriptorSetLayout( logical_device, |
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 | VkDescriptorPoolCreateInfo descriptor_pool_create_info = { |
注意多线程问题.
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 | VkDescriptorSetAllocateInfo descriptor_set_allocate_info = { |
然后,allocate descriptor sets
1 | descriptor_sets.resize( descriptor_set_layouts.size() ); |
不幸的是,当我们分配和释放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 | struct ImageDescriptorInfo { |
uniform 和 storage buffers
1 | struct BufferDescriptorInfo { |
uniform and storage texel buffer
1 | struct TexelBufferDescriptorInfo { |
可以从另一个descriptor拷贝.
1 | struct CopyDescriptorInfo { |
前面所有的结构都定义了应该更新的descriptor set的句柄、给定集内描述符的索引以及数组中的索引如果要更新的话.通过数组访问的描述符.其余参数是特定于类型的.
1 | std::vector<VkWriteDescriptorSet> write_descriptors; |
也能复用其他sets的descriptor,更快.
1 | std::vector<VkCopyDescriptorSet> copy_descriptors; |
update descriptor sets
1 | vkUpdateDescriptorSets( logical_device, |
binding descriptor sets
创建好descriptor set后,需要在recording 操作中将之绑定到cb.
1 | VkCommandBuffer command_buffer; |
当我们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 | if( !CreateCombinedImageSampler( physical_device, logical_device, |
然后准备定义descriptor set核心结构的layout
1 | std::vector<VkDescriptorSetLayoutBinding> bindings = { |
组后,用一开始创建的resources更新descriptor set
1 | std::vector<ImageDescriptorInfo> image_descriptor_infos = { |
destroy
free descriptor sets
如果向将descriptor set的内存归还给pool,可以free它.归还后可以用来创建另一个,但是可能会由于Pool内存的碎片化导致失败.
能够一次释放多个descriptor sets,(来自同一个pool的)
1 | VkResult result = vkFreeDescriptorSets( logical_device, descriptor_pool, |
reset a descriptor pool
能够一次释放一个pool的所有descriptor sets.
如果pool创建flag没有VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,该方法是唯一释放其descriptor sets的方法.
1 | VkResult result = vkResetDescriptorPool( logical_device, descriptor_pool, 0 |
destroy a descriptor pool
1 | if( VK_NULL_HANDLE != descriptor_pool ) { |
destroy a descriptor set layout
1 | if( VK_NULL_HANDLE != descriptor_set_layout ) { |
destroy a sampler
1 | if( VK_NULL_HANDLE != sampler ) { |