操作系統:Windows8.1
顯卡:Nivida GTX965M
開發工具:Visual Studio 2017
Introduction
描述符布局描述了前一章節討論過的可以綁定的描述符的類型。在本章節,我們創建描述符集,它將實際指定一個VkBuffer來綁定到一個uniform buffer描述符。
Descriptor pool
描述符集合不能直接創建,它們必須像命令緩沖區一樣,從對象池中分配使用。對於描述符集合相當於調用描述符對象池。我們將寫一個新的函數createDescriptorPool來配置。
void initVulkan() { ... createUniformBuffer(); createDescriptorPool(); ... } ... void createDescriptorPool() { }
我們首先需要明確我們使用的描述符集合包含的描述符類型與數量,這里使用VkDescriptorPoolSize結構體。
VkDescriptorPoolSize poolSize = {}; poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; poolSize.descriptorCount = 1;
現在我們只有一個uniform buffer類型的單描述符。對象池大小將被VkDescriptorPoolCreateInfo結構體引用:
VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize;
我們也需要指定最大的描述符集合的分配數量:
poolInfo.maxSets = 1;
該結構體與命令對象池類似,有一些可選項用於決定每個描述符集合是否可以獨立管理生命周期:VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT。創建完畢后我們不會進一步使用它,所以我們不需要該flag。在這里設置flags默認值為0。
VkDescriptorPool descriptorPool; ... if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("failed to create descriptor pool!"); }
添加新的類成員對象保存描述符對象池的句柄,通過調用vkCreateDescriptorPool創建它。與其他繪制資源一樣,描述符對象池應該僅在程序退出的時候銷毀:
void cleanup() { cleanupSwapChain(); vkDestroyDescriptorPool(device, descriptorPool, nullptr); ... }
Descriptor set
為了從對象池中分配描述符集合,我們需要添加一個createDescriptorSet函數:
void initVulkan() { ... createDescriptorPool(); createDescriptorSet(); ... } ... void createDescriptorSet() { }
描述符集合通過VkDescriptorSetAllocateInfo結構體描述具體的分配。需要指定用於分配的描述符對象池,分配的描述符集合數量,以及基於此的描述符布局:
VkDescriptorSetLayout layouts[] = {descriptorSetLayout}; VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = layouts;
添加類成員存儲描述符集合的句柄,並使用vkAllocateDescriptorSets分配:
VkDescriptorPool descriptorPool; VkDescriptorSet descriptorSet; ... if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { throw std::runtime_error("failed to allocate descriptor set!"); }
我們不需要明確清理描述符集合,因為它們會在描述符對象池銷毀的時候自動清理。調用vkAllocateDescriptorSets會分配一個具有uniform buffer描述符的描述符集合。
描述符集合已經分配了,但是內部的描述符需要配置。描述符需要引用緩沖區,就像uniform buffer描述符,使用VkDescriptorBufferInfo結構體進行配置。該結構體指定緩沖區和描述符內部包含的數據的區域:
VkDescriptorBufferInfo bufferInfo = {}; bufferInfo.buffer = uniformBuffer; bufferInfo.offset = 0; bufferInfo.range = sizeof(UniformBufferObject);
描述符的配置更新使用vkUpdateDescriptorSets函數,它需要VkWriteDescriptorSet結構體的數組作為參數。
VkWriteDescriptorSet descriptorWrite = {}; descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrite.dstSet = descriptorSet; descriptorWrite.dstBinding = 0; descriptorWrite.dstArrayElement = 0;
前兩個字段指定描述符集合更新和綁定的設置。我們為 uniform buffer 綁定的索引設定為0。描述符可以是數組,所以我們需要指定要更新的數組索引。在這里沒有使用數組,所以簡單的設置為0。
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorWrite.descriptorCount = 1;
我們在這里再一次指定描述符類型。可以通過數組一次性更新多個描述符,使用dstArrayElement起始索引。descriptorCount字段描述多少描述符需要被更新。
descriptorWrite.pBufferInfo = &bufferInfo; descriptorWrite.pImageInfo = nullptr; // Optional descriptorWrite.pTexelBufferView = nullptr; // Optional
最后的字段引用descriptorCount結構體的數組,它配置了實際的描述符。它的類型根據實際需要的三個描述符類型來設定。pBufferInfo字段用於指定描述符引用的緩沖區數據,pImageInfo字段用於指定描述符引用的圖像數據,描述符使用pTexelBufferView引用緩沖區視圖。我們的描述符是基於緩沖區的,所以我們使用pBufferInfo。
vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
使用vkUpdateDescriptorSets應用實際的更新。它接受兩種數組的參數:一個數組是VkWriteDescriptorSet,另一個是VkCopyDescriptorSet。后一個數組可以用於兩個描述符之間進行拷貝操作。
Using a descriptor set
我們現在需要更新createCommandBuffers函數,使用cmdBindDescriptorSets將描述符集合綁定到實際的着色器的描述符中:
vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
與頂點和索引緩沖區不同,描述符集合不是圖形管線唯一的。因此,我們需要指定是否要將描述符集綁定到圖形或者計算管線。下一個參數是描述符所基於的布局。接下來的三個參數指定首個描述符的索引,要綁定的集合的數量以及要綁定的集合的數組。我們稍后回來。最后兩個參數指定用於動態描述符的偏移數組。我們在后續的章節中會看到這些。
如果此時運行程序,會看不到任何內容在屏幕上。問題在於,由於我們在投影矩陣中進行了Y-flip操作,所以頂點現在以順時針順序而不是逆時針順序繪制。這導致背面剔除以防止任何背面的集合體被繪制。來到createGraphicsPipeline函數,修改VkPipelineRasterizationStateCreateInfo結構體的frontFace如下:
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
運行程序如下:

矩形已經變為正方形,因為投影矩陣現在修正了寬高比。updateUniformData需要考慮屏幕的尺寸大小變化,所以我們不需要重新創建描述符集合在recreateSwapChain中。
Multiple descriptor sets
正如某些結構體和函數調用時候的提示所示,實際上可以綁定多個描述符集合。你需要在管線創建布局的時候為每個描述符集合指定描述符布局。着色器可以引用具體的描述符集合如下:
layout(set = 0, binding = 0) uniform UniformBufferObject { ... }
我們可以使用此功能將每個對象和發生變化的描述符分配到單獨的描述符集合中,在這種情況下,可以避免重新綁定大部分描述符,而這些描述符可能會更有效率。
項目代碼 GitHub 地址。
