APIwithoutSecrets:IntroductiontoVulkan*Part6
TableofContentsTutorial6:DescriptorSets—UsingTexturesinShaders.........................................................................................................2
CreatinganImage..............................................................................................................................................................2
AllocatingImageMemory..............................................................................................................................................4
CreatingImageView......................................................................................................................................................6
CopyingDatatoanImage..............................................................................................................................................6
CreatingaSampler...........................................................................................................................................................10
UsingDescriptorSets.......................................................................................................................................................11
CreatingaDescriptorSetLayout..................................................................................................................................12
CreatingaDescriptorPool............................................................................................................................................14
AllocatingDescriptorSets............................................................................................................................................15
UpdatingDescriptorSets..............................................................................................................................................15
CreatingaPipelineLayout............................................................................................................................................17
BindingDescriptorSets................................................................................................................................................18
AccessingDescriptorsinShaders.................................................................................................................................19
Tutorial06Execution........................................................................................................................................................20
CleaningUp......................................................................................................................................................................20
Conclusion........................................................................................................................................................................21
Tutorial6:DescriptorSets—UsingTexturesinShadersWeknowhowtocreateagraphicspipelineandhowtouseshaderstodrawgeometryonscreen.Wehavealsolearned
howtocreatebuffersandusethemasasourceofvertexdata(vertexbuffers).Nowweneedtoknowhowtoprovidedatatoshaders—wewillseehowtouseresourceslikesamplersandimagesinsideshadersourcecodeandhowtosetupaninterfacebetweentheapplicationandtheprogrammableshaderstages.
Inthistutorial,wewillfocusonafunctionalitythatissimilartoOpenGL*textures.ButinVulkan*therearenosuchobjects.Wehaveonlytworesourcetypesinwhichwecanstoredata:buffersandimages(therearealsopushconstants,butwewill cover them inadedicated tutorial).Eachof themcanbeprovided toshaders, inwhichcasewecall suchresourcesdescriptors,butwecan’tprovidethemtoshadersdirectly.Instead,theyareaggregatedinwrapperorcontainerobjectscalleddescriptorsets.Wecanplacemultipleresourcesinasingledescriptorsetbutweneedtodoitaccordingtoapredefinedstructureofsuchset.Thisstructuredefinesthecontentsofasingledescriptorset—typesofresourcesthatareplacedinsideit,numberofeachoftheseresourcetypes,andtheirorder.Thisdescriptionisspecifiedinsideobjectsnameddescriptorsetlayouts.Similardescriptionsneedtobespecifiedwhenwewriteshaderprograms.TogethertheyformaninterfacebetweenAPI(ourapplication)andtheprogrammablepipeline(shaders).
Whenwehavepreparedalayout,andcreatedadescriptorset,wecanfillit;inthiswaywedefinespecificobjects(buffersand/orimages)thatwewanttouseinshaders.Afterthat,beforeissuingdrawingcommandsinsideacommandbuffer,weneedtobindsuchasettothecommandbuffer.Thisallowsustousetheresourcesfrominsidetheshadersourcecode;forexample,fetchdatafromasampledimage(atexture),orreadavalueofauniformvariablestoredinauniformbuffer.
Inthispartofthetutorial,wewillseehowtocreatedescriptorsetlayoutsanddescriptorsetsthemselves.Wewillalsoprepareasamplerandanimagesowecanmakethemavailableasatextureinsideshaders.Wewillalsolearnhowwecanusetheminsideshaders.
Asmentionedpreviously,thistutorialisbasedontheknowledgepresentedinallthepreviouspartsoftheAPIwithoutSecrets: Introduction to Vulkan tutorials, and only the differences and parts important for the described topics arepresented.
CreatinganImageWestartbycreatinganimagethatwillactasourtexture.Imagesrepresentacontinuousareaofmemory,whichis
interpretedaccordingtotherulesdefinedduringimagecreation.InVulkan,wehaveonlythreebasicimagetypes:1D,2D,and3D.Imagesmayhavemipmaps(levelsofdetail),manyarraylayers(atleastoneisrequired),orsamplesperframe.All these parameters are specified during image creation. In the code sample, we create the most commonly usedtwo-dimensionalimage,withonesampleperpixelandthefourRGBAcomponents.
VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; nullptr, // const void *pNext 0, // VkImageCreateFlags flags VK_IMAGE_TYPE_2D, // VkImageType imageType VK_FORMAT_R8G8B8A8_UNORM, // VkFormat format { // VkExtent3D extent width, // uint32_t width height, // uint32_t height 1 // uint32_t depth }, 1, // uint32_t mipLevels 1, // uint32_t arrayLayers VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling VK_IMAGE_USAGE_TRANSFER_DST_BIT | // VkImageUsageFlags usage VK_IMAGE_USAGE_SAMPLED_BIT, VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode
0, // uint32_t queueFamilyIndexCount
nullptr, // const uint32_t *pQueueFamilyIndices VK_IMAGE_LAYOUT_UNDEFINED // VkImageLayout initialLayout }; return vkCreateImage( GetDevice(), &image_create_info, nullptr, image ) ==
VK_SUCCESS; 1. Tutorial06.cpp,functionCreateImage()
TocreateanimageweneedtoprepareastructureoftypeVkImageCreateInfo.Thisstructurecontainsthebasicsetofparametersrequiredtocreateanimage.Theseparametersarespecifiedthroughthefollowingmembers:
• sType–Typicaltypeofthestructure.ItmustbeequaltoaVK_STRUCTURE_TYPE_IMAGE_CREATE_INFOvalue.• pNext–Pointerreservedforextensions.• flags–Parameterthatdescribesadditionalpropertiesofanimage.Throughthisparameterwecanspecify
thattheimagecanbebackedbyasparsememory.ButamoreinterestingvalueisaVK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT,whichallowsustousetheimageasacubemap.Ifwedon’thaveadditionalrequirements,wecansetthisparameterto0.
• imageType–Basictype(numberofdimensions)oftheimage:1D,2D,or3D.• format–Formatoftheimage:numberofitscomponents,numberofbitsforeachcomponent,andadata
type.• extent–Sizeoftheimage(numberoftexels/pixels)ineachdimension.• mipLevels–Numberoflevelsofdetail(mipmaps).• arrayLayers–Numberofarraylayers.• samples–Numberofpertexelsamples(onefornormalimagesandmorethanoneformultisampledimages).• tiling–Definestheinnermemorystructureoftheimage:linearoroptimal.• usage–Definesallthewaysinwhichwewanttouseanimageduringitsoveralllifetime.• sharingMode–Specifieswhetheranimagewillbeaccessedbyqueuesfrommultiplefamiliesatatime(the
sameasthesharingModeparameterusedduringswapchainorbuffercreation).• queueFamilyIndexCount–NumberofelementsinapQueueFamilyIndicesarray(usedonlywhenconcurrent
sharingmodeisspecified).• pQueueFamilyIndices–Arraywithindicesofallqueuefamiliesfromwhichqueueswillaccessanimage(used
onlywhenconcurrentsharingmodeisspecified).• initialLayout–Memorylayoutimagewillbecreatedwith.Wecanonlyprovideanundefinedorpreinitialized
layout.Wealsoneedtoperformalayouttransitionbeforewecanuseanimageinsidecommandbuffers.
Mostoftheparametersdefinedduringimagecreationarequiteself-explanatoryorsimilartoparametersusedduringcreationofotherresources.Butthreeparametersrequireadditionalexplanation.
Tilingdefinestheinnermemorystructureofanimage(butdon’tconfuseitwithalayout).Imagesmayhavelinearoroptimaltiling(buffersalwayshavelineartiling).Imageswithlineartilinghavetheirtexelslaidoutlinearly,onetexelafteranother,onerowafteranother,andsoon.Wecanqueryforalltherelevantimage’smemoryparameters(offsetandsize,row,array,anddepthstride).Thiswayweknowhowtheimage’scontentsarekeptinmemory.Suchtilingcanbeusedtocopydatatoanimagedirectly(bymappingtheimage’smemory).Unfortunately,therearesevererestrictionsonimageswith linear tiling.Forexample, theVulkanspecificationsays thatonly2D imagesmustsupport linear tiling.Hardwarevendorsmayimplementsupportforlineartilinginotherimagetypes,butthisisnotobligatory,andwecan’trelyonsuchsupport.But,what’smoreimportant,linearlytiledimagesmayhaveworseperformancethantheiroptimalcounterparts.
Whenwespecifyanoptimaltilingfor images, itmeansthatwedon’tknowhowtheirmemory isstructured.Eachplatform we execute our application on may keep an image’s contents in a totally different way, so it’s practically
impossibletomapanimage’smemoryandcopyittoorfromtheCPUdirectly(weneedtouseastagingresource,abufferoran image).But thiswaywecancreatewhatever imageswewant (thereareno restrictions similar to linearly tiledimages)andourapplicationwillhavebetterperformance.That’swhyitisstronglysuggestedtoalwaysspecifyoptimaltilingforimages.
Nowlet’sfocusonaninitialLayoutparameter.Layout,asitwasdescribedinatutorialaboutswapchains,definesanimage’smemorylayoutandisstrictlyconnectedwiththewayinwhichwewanttouseanimage.Eachspecificusagehasitsownmemorylayout.Beforewecanuseanimageinagivenwayweneedtoperformalayouttransition.Forexample,swapchainimagescanbedisplayedonscreenonlyinVK_IMAGE_LAYOUT_PRESENT_SRC_KHRlayout.Whenwewanttorenderintoanimage,weneedtosetitsmemorylayouttoVK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.Thereisalsoagenerallayoutthatallowsustouseimagesanywaywewantto,butasitimpactsperformance,it’suseisstronglydiscouraged(useonlywhenreallynecessary).
Now,whenwewanttochangethewayinwhichanimageisused,weneedtoperformtheabove-mentionedlayouttransition.Wemustspecifyacurrent(old)layoutandanewone.Theoldlayoutcanhaveoneoftwovalues:currentimagelayoutoranundefinedlayout.Whenwespecifythevalueofacurrentimage’slayout,theimagecontentsarepreservedduringtransition.Butwhenwedon’tneedanimage’scontents,wecanprovideanundefinedlayout.Inthiswaylayouttransitionmaybeperformedfaster.
And this is when the initialLayout parameter comes in. We can specify only two values for it—undefined orpreinitialized.Thepreinitialized layoutvalueallowsus topreservean image’scontentsduring the image’s first layouttransition.Thiswaywecancopydatatoanimagewithmemorymapping;butthisisquiteimpractical.Wecanonlycopydata directly (through memory mapping) to images with linear tiling, which have restrictions as mentioned above.Practicallyspeaking,theseimagescanonlybeusedasstagingresources—fortransferringdatabetweenGPUandCPU.Butforthispurposewecanalsousebuffers;that’swhyitismucheasiertocopydatausingabufferthanusinganimagewithlineartiling.
Allthisleadstotheconclusionthat,inmostcases,anundefinedlayoutcanbeusedforaninitialLayoutparameter.Insuchacase,animage’scontentscannotbeinitializeddirectly(bymappingitsmemory).Butifwewantto,wecancopydatatosuchanimagebyusingastagingbuffer.Thatapproachispresentedinthistutorial.
Onelastthingweneedtorememberistheusage.Similartobuffers,whenwecreateanimageweneedtodesignateALLthewaysinwhichweintendtousetheimage.Wecan’tchangeit laterandwecan’tusetheimageinawaythatwasn’tspecifiedduringitscreation.Here,wewanttouseanimageasatextureinsideshaders.ForthispurposewespecifytheVK_IMAGE_USAGE_SAMPLED_BITusage.Wealsoneedawaytouploaddatatotheimage.Wearegoingtoreaditfromanimagefileandcopyittotheimageobject.Thiscanbedonebytransferringdatausingastagingresource.Insucha case, the image will be a target of a transfer operation; that’s why we also specify theVK_IMAGE_USAGE_TRANSFER_DST_BITusage.
Now,whenwehave provided values for all the parameters,we can create an image. This is done by calling thevkCreateImage()functionforwhichweneedtoprovideahandleofalogicaldevice,apointertothestructuredescribedabove,andapointertoavariableoftypeVkImageinwhichthehandleofthecreatedimagewillbestored.
AllocatingImageMemorySimilartobuffers,imagesdon’thavetheirownmemory,sobeforewecanuseimagesweneedtobindmemoryto
them.Todothis,wefirstneedtoknowwhatthepropertiesofmemorythatcanbeboundtoanimageare.WedothisbycallingthevkGetImageMemoryRequirements()function.
VkMemoryRequirements image_memory_requirements; vkGetImageMemoryRequirements( GetDevice(), Vulkan.Image.Handle,
&image_memory_requirements ); 2. Tutorial06.cpp,functionAllocateImageMemory()
Theabovecallstorestherequiredmemoryparametersinanimage_memory_requirementsvariable.Thistellsushowmuchmemoryweneedandwhichmemorytypesupportedbyagivenphysicaldevicecanbeusedforanimage’smemoryallocation.Ifwedon’tknowwhatmemorytypesaresupportedbyagivenphysicaldevicewecanlearnaboutthembycalling thevkGetPhysicalDeviceMemoryProperties() function. Thiswas covered in aprevious tutorial,whenwewereallocatingmemoryforabuffer.Next,wecaniterateoveravailablememorytypesandcheckwhicharecompatiblewithourimage.
for( uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i ) { if( (image_memory_requirements.memoryTypeBits & (1 << i)) && (memory_properties.memoryTypes[i].propertyFlags & property) ) { VkMemoryAllocateInfo memory_allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType nullptr, // const void *pNext image_memory_requirements.size, // VkDeviceSize allocationSize i // uint32_t memoryTypeIndex }; if( vkAllocateMemory( GetDevice(), &memory_allocate_info, nullptr, memory ) ==
VK_SUCCESS ) { return true; } } } return false;
3. Tutorial06.cpp,functionAllocateImageMemory()
Eachmemorytypehasaspecificsetofproperties.Whenwewanttobindmemorytoanimage,wecanhaveourownspecificrequirementstoo.Forexample,wemayneedtoaccessmemorydirectly,bymappingit,sosuchmemorymustbehost-visible.Ifwehaveadditionalrequirementswecancomparethemwiththepropertiesofeachavailablememorytype.When we find the match, we can use a given memory type and allocate a memory object from it by calling thevkAllocateMemory()function.
Afterthat,weneedtobindsuchmemorytoourimage.WedothisbycallingthevkBindImageMemory()functionandprovidingthehandleofanimagetowhichwewanttobindmemory,ahandleofamemoryobject,andanoffsetfromthebeginningofthememoryobject,likethis:
if( vkBindImageMemory( GetDevice(), Vulkan.Image.Handle, Vulkan.Image.Memory, 0 ) != VK_SUCCESS ) {
std::cout << "Could not bind memory to an image!" << std::endl; return false; }
4. Tutorial06.cpp,functionCreateTexture()
Offsetvalueisveryimportantwhenwebindmemorytoanobject.ResourcesinVulkanhavespecificrequirementsformemoryoffsetalignment.Informationabouttherequirementsisalsoavailableintheimage_memory_requirementsvariable.Theoffsetthatweprovidewhenwebindamemorymustbeamultipleofthevariable’salignmentmember.Zeroisalwaysavalidoffsetvalue.
Ofcourse,whenwewanttobindamemorytoanimage,wedon’tneedtocreateanewmemoryobjecteachtime.Itismoreoptimaltocreateasmallnumberoflargermemoryobjectsandbindpartsofthembyprovidingaproperoffsetvalue.
CreatingImageViewWhenwewanttouseanimageinourapplicationwerarelyprovidetheimage’shandle.Imageviewsareusuallyused
instead.Theyprovideanadditionallayerthatinterpretsthecontentsofanimageforthepurposeofusingitinaspecificcontext.Forexample,wemayhaveamultilayerimage(2Darray)andwewanttorenderonlytoaspecificarraylayer.Todothiswecreateanimageviewinwhichwedefinethelayerwewanttouse.Anotherexampleisanimagewithsixarraylayers.Usingimageviews,wecaninterpretitasacubemap.
CreationofimageviewswasdescribedinIntroductiontoVulkanPart3:FirstTriangle,soIwillprovideonlythesourcecodeusedinthispart.
VkImageViewCreateInfo image_view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // VkStructureType sType nullptr, // const void *pNext 0, // VkImageViewCreateFlags flags image_parameters.Handle, // VkImage image VK_IMAGE_VIEW_TYPE_2D, // VkImageViewType viewType VK_FORMAT_R8G8B8A8_UNORM, // VkFormat format { // VkComponentMapping components VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle r VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle g VK_COMPONENT_SWIZZLE_IDENTITY, // VkComponentSwizzle b VK_COMPONENT_SWIZZLE_IDENTITY // VkComponentSwizzle a }, { // VkImageSubresourceRange
subresourceRange VK_IMAGE_ASPECT_COLOR_BIT, // VkImageAspectFlags aspectMask 0, // uint32_t
baseMipLevel 1, // uint32_t levelCount 0, // uint32_t
baseArrayLayer 1 // uint32_t layerCount } }; return vkCreateImageView( GetDevice(), &image_view_create_info, nullptr,
&image_parameters.View ) == VK_SUCCESS; 5. Tutorial06.cpp,functionCreateImageView()
CopyingDatatoanImageNowweneedtocopydatatoourimage.Wedothisbyusingastagingbuffer.Wefirstcreateabufferbigenoughto
holdourimagedata.Next,weallocatememorythatishost-visible(thatcanbemapped),andbindittothebuffer.Then,wecopydatatothebuffer’smemorylikethis:
// Prepare data in staging buffer void *staging_buffer_memory_pointer; if( vkMapMemory( GetDevice(), Vulkan.StagingBuffer.Memory, 0, data_size, 0,
&staging_buffer_memory_pointer ) != VK_SUCCESS ) { std::cout << "Could not map memory and upload texture data to a staging buffer!" <<
std::endl; return false; } memcpy( staging_buffer_memory_pointer, texture_data, data_size ); VkMappedMemoryRange flush_range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, // VkStructureType sType nullptr, // const void *pNext Vulkan.StagingBuffer.Memory, // VkDeviceMemory memory
0, // VkDeviceSize offset data_size // VkDeviceSize size }; vkFlushMappedMemoryRanges( GetDevice(), 1, &flush_range ); vkUnmapMemory( GetDevice(), Vulkan.StagingBuffer.Memory );
6. Tutorial06.cpp,functionCopyTextureData()
Wemapthebuffer’smemory.ThisoperationgivesusapointerthatcanbeusedthewaythatallotherC++pointersareused.Wecopytexturedatatoitandinformthedriverwhichpartsofthebuffer’smemorywerechangedduringthisoperation(weflushthememory).Attheend,weunmapthememory,butthisisnotnecessary.
Imagedataisreadfromafilewiththefollowingcode:
int width = 0, height = 0, data_size = 0; std::vector<char> texture_data = Tools::GetImageData( "Data06/texture.png", 4,
&width, &height, nullptr, &data_size ); if( texture_data.size() == 0 ) { return false; } if( !CopyTextureData( &texture_data[0], data_size, width, height ) ) { std::cout << "Could not upload texture data to device memory!" << std::endl; return false; }
7. Tutorial06.cpp,functionCreateTexture()
Forthepurposeofthistutorialwewillusethefollowingimageasatexture:
Theoperationofcopyingdatafromabuffertoanimagerequiresrecordingacommandbufferandsubmittingittoaqueue.CallingthevkBeginCommandBuffer()functionstartstherecordingoperation:
// Prepare command buffer to copy data from staging buffer to a vertex buffer VkCommandBufferBeginInfo command_buffer_begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, // VkStructureType
sType nullptr, // const void
*pNext VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, // VkCommandBufferUsageFlags
flags
nullptr // const VkCommandBufferInheritanceInfo *pInheritanceInfo
}; VkCommandBuffer command_buffer = Vulkan.RenderingResources[0].CommandBuffer; vkBeginCommandBuffer( command_buffer, &command_buffer_begin_info);
8. Tutorial06.cpp,functionCopyTextureData()
Atthebeginningofthecommandbufferrecordingweneedtoperformalayouttransitiononourimage.WewanttocopydatatotheimagesoweneedtochangeitslayouttoaVK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL.WeneedtodothisexplicitlyusinganimagememorybarrierandcallingthevkCmdPipelineBarrier()function:
VkImageSubresourceRange image_subresource_range = { VK_IMAGE_ASPECT_COLOR_BIT, // VkImageAspectFlags aspectMask 0, // uint32_t baseMipLevel 1, // uint32_t levelCount 0, // uint32_t baseArrayLayer 1 // uint32_t layerCount }; VkImageMemoryBarrier image_memory_barrier_from_undefined_to_transfer_dst = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType nullptr, // const void *pNext 0, // VkAccessFlags srcAccessMask VK_ACCESS_TRANSFER_WRITE_BIT, // VkAccessFlags dstAccessMask VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout oldLayout VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // VkImageLayout newLayout VK_QUEUE_FAMILY_IGNORED, // uint32_t
srcQueueFamilyIndex VK_QUEUE_FAMILY_IGNORED, // uint32_t
dstQueueFamilyIndex Vulkan.Image.Handle, // VkImage image image_subresource_range // VkImageSubresourceRange
subresourceRange }; vkCmdPipelineBarrier( command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier_from_undefined_to_transfer_dst);
9. Tutorial06.cpp,functionCopyTextureData()
Next,wecancopythedataitself.Todothisweneedtoprovideparametersdescribingbothasourceandadestinationfor the data:which parts of the imagewewant to update (imageSubresourcemember), a specific regionwithin theprovidedpart(imageOffset),andthetotalsizeoftheimage.Forthesourceofthedataweneedtoprovideanoffsetfromthebeginningofabuffer’smemorywherethedatastarts,andhowthisdataisstructured,andthesizeofanimaginaryimageinsidethebuffer(thesizeofitsrowsandcolumns).Fortunately,wecanstoreourdatainsuchawaythatitfitsourimage.Thisallowsustosetazerovalueforbothparameters(bufferRowLengthandbufferImageHeight),specifyingthatthedataistightlypackedaccordingtotheimagesize.
VkBufferImageCopy buffer_image_copy_info = { 0, // VkDeviceSize bufferOffset 0, // uint32_t bufferRowLength 0, // uint32_t bufferImageHeight { // VkImageSubresourceLayers imageSubresource VK_IMAGE_ASPECT_COLOR_BIT, // VkImageAspectFlags aspectMask 0, // uint32_t mipLevel 0, // uint32_t baseArrayLayer 1 // uint32_t layerCount
}, { // VkOffset3D imageOffset 0, // int32_t x 0, // int32_t y 0 // int32_t z }, { // VkExtent3D imageExtent width, // uint32_t width height, // uint32_t height 1 // uint32_t depth } }; vkCmdCopyBufferToImage( command_buffer, Vulkan.StagingBuffer.Handle,
Vulkan.Image.Handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &buffer_image_copy_info ); 10. Tutorial06.cpp,functionCopyTextureData()
Onelastthingistoperformanotherlayouttransition.Ourimagewillbeusedasatextureinsideshaders,soweneedto transition it toaVK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL layout.After that,wecanendourcommandbuffer,submitittoaqueue,andwaitforthetransfertocomplete(inareal-lifeapplication,weshouldskipwaitingandsynchronizeoperationsinsomeotherway;forexample,usingsemaphores,toavoidunnecessarypipelinestalls).
VkImageMemoryBarrier image_memory_barrier_from_transfer_to_shader_read = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // VkStructureType sType nullptr, // const void *pNext VK_ACCESS_TRANSFER_WRITE_BIT, // VkAccessFlags
srcAccessMask VK_ACCESS_SHADER_READ_BIT, // VkAccessFlags
dstAccessMask VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // VkImageLayout oldLayout VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, // VkImageLayout newLayout VK_QUEUE_FAMILY_IGNORED, // uint32_t
srcQueueFamilyIndex VK_QUEUE_FAMILY_IGNORED, // uint32_t
dstQueueFamilyIndex Vulkan.Image.Handle, // VkImage image image_subresource_range // VkImageSubresourceRange
subresourceRange }; vkCmdPipelineBarrier( command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier_from_transfer_to_shader_read);
vkEndCommandBuffer( command_buffer ); // Submit command buffer and copy data from staging buffer to a vertex buffer VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO, // VkStructureType sType nullptr, // const void *pNext 0, // uint32_t
waitSemaphoreCount nullptr, // const VkSemaphore
*pWaitSemaphores nullptr, // const VkPipelineStageFlags
*pWaitDstStageMask; 1, // uint32_t
commandBufferCount &command_buffer, // const VkCommandBuffer
*pCommandBuffers 0, // uint32_t
signalSemaphoreCount
nullptr // const VkSemaphore *pSignalSemaphores
}; if( vkQueueSubmit( GetGraphicsQueue().Handle, 1, &submit_info, VK_NULL_HANDLE ) !=
VK_SUCCESS ) { return false; } vkDeviceWaitIdle( GetDevice() );
11. Tutorial06.cpp,functionCopyTextureData()
Nowourimageiscreatedandfullyinitialized(containsproperdata).Butwearenotyetdonepreparingourtexture.
CreatingaSamplerInOpenGL,whenwecreateda texture,both the imageand its samplingparametershad tobe specified. In later
versionsofOpenGLwecouldalsocreateseparatesamplerobjects.Insideashader,weusuallycreatedvariablesoftypesampler2D,whichalsocombinedbothimagesandtheirsamplingparameters(samplers).InVulkan,weneedtocreateimagesandsamplersseparately.
Samplersdefinethewayinwhichimagedataisreadinsideshaders:whetherfilteringisenabled,whetherwewanttousemipmaps(ormaybeaspecificsubrangeofmipmaps),orwhatkindofaddressingmodewewanttouse(clampingorwrapping).
VkSamplerCreateInfo sampler_create_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkSamplerCreateFlags flags VK_FILTER_LINEAR, // VkFilter magFilter VK_FILTER_LINEAR, // VkFilter minFilter VK_SAMPLER_MIPMAP_MODE_NEAREST, // VkSamplerMipmapMode mipmapMode VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeU VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeV VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // VkSamplerAddressMode addressModeW 0.0f, // float mipLodBias VK_FALSE, // VkBool32 anisotropyEnable 1.0f, // float maxAnisotropy VK_FALSE, // VkBool32 compareEnable VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp 0.0f, // float minLod 0.0f, // float maxLod VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,// VkBorderColor borderColor VK_FALSE // VkBool32
unnormalizedCoordinates }; return vkCreateSampler( GetDevice(), &sampler_create_info, nullptr, sampler ) ==
VK_SUCCESS; 12. Tutorial06.cpp,functionCreateSampler()
AlltheaboveparametersaredefinedthroughvariablesoftypeVkSamplerCreateInfo.Ithasmanymembers:
• sType–Typeofthestructure.ItshouldbeequaltoaVK_STRUCTURE_TYPE_SAMPLER_CREATE_INFOvalue.• pNext–Pointerreservedforextensions.• flags–Mustbesettozero.Thisparameterisreservedforfutureuse.• magFilter–Typeoffiltering(nearestorlinear)usedformagnification.• minFilter–Typeoffiltering(nearestorlinear)usedforminification.
• mipmapMode–Typeoffiltering(nearestorlinear)usedformipmaplookup.• addressModeU–AddressingmodeforUcoordinatesthatareoutsideofa<0.0;1.0>range.• addressModeV–AddressingmodeforVcoordinatesthatareoutsideofa<0.0;1.0>range.• addressModeW–AddressingmodeforWcoordinatesthatareoutsideofa<0.0;1.0>range.• mipLodBias–Valueofbiasaddedtomipmap’slevelofdetailcalculations.Ifwewanttooffsetfetchingdata
fromaspecificmipmap,wecanprovideavalueotherthan0.0.• anisotropyEnable–Parameterdefiningwhetheranisotropicfilteringshouldbeused.• maxAnisotropy–Maximalallowedvalueusedforanisotropicfiltering(clampingvalue).• compareEnable–Enablescomparisonagainstareferencevalueduringtexturelookups.• compareOp–TypeofcomparisonperformedduringlookupsifthecompareEnableparameterissettotrue.• minLod–Minimalallowedlevelofdetailusedduringdatafetching.Ifcalculatedlevelofdetail(mipmaplevel)
islowerthanthisvalue,itwillbeclamped.• maxLod–Maximalallowedlevelofdetailusedduringdatafetching.Ifthecalculatedlevelofdetail(mipmap
level)isgreaterthanthisvalue,itwillbeclamped.• borderColor–Specifiespredefinedcolorofborderpixels.Bordercolorisusedwhenaddressmodeincludes
clampingtobordercolors.• unnormalizedCoordinates–Usually(whenthisparameterissettofalse)weprovidetexturecoordinatesusing
anormalized<0.0;1.0>range.Whenset to true, thisparameterallowsus tospecify thatwewant touseunnormalized coordinates and address texture using texels (in a <0; texturedimension> range, similar toOpenGL’srectangletextures).
SamplerobjectiscreatedbycallingthevkCreateSampler()function,forwhichweprovideapointertothestructuredescribedabove.
UsingDescriptorSetsWecreatedanimage,boundamemorytoit,andweevenuploadeddatatotheimage.Wealsocreatedasamplerto
setupsamplingparameters forourtexture.Nowwewanttousethetexture.Howcanwedothis?Wedo it throughdescriptorsets.
Asmentionedatthebeginning,resourcesusedinsideshadersarecalleddescriptors.InVulkanwehave11typesofdescriptors:
• Samplers–Definethewayimagedataisread.Insideshaders,samplerscanbeusedwithmultipleimages.• Sampledimages–Defineimagesfromwhichwecanreaddatainsideshaders.Wecanreaddatafromasingle
imageusingdifferentsamplers.• Combinedimagesamplers–Thesedescriptorscombinebothsamplerandsampledimageasoneobject.From
theAPIperspective (ourapplication),westillneedtocreatebothasamplerandan image,but inside theshadertheyappearasasingleobject.Usingthemmaybemoreoptimal(mayhavebetterperformance)thanusingseparatesamplersandsampledimages.
• Storageimages–Thisdescriptorallowsustobothreadandstoredatainsideanimage.• Inputattachments–Thisaspecificusageofrenderpass’sattachments.Whenwewanttoreaddatafroman
imagewhich is used as an attachment inside the same render pass, we can only do it through an inputattachment.Thiswaywedonotneedtoendarenderpassandstartanotherone,butwearerestrictedtoonly fragment shaders, and to only a single location per fragment shader instance (a given instance of afragmentshadercanreaddatafromcoordinatesassociatedwiththefragmentshader’scoordinates).
• Uniformbuffers(andtheirdynamicvariation)–Uniformbuffersallowustoreaddatafromuniformvariables.InVulkan,suchvariablescannotbeplacedinsidetheglobalscope;weneedtouseuniformbuffers.
• Storagebuffers(andtheirdynamicvariation)–Storagebuffersallowustobothreadandstoredatainsidevariables.
• Uniformtexelbuffers–Theseallowthecontentsofbufferstobetreatedasiftheycontaintexturedata,theyareinterpretedastexelswithaselectednumberofcomponentsandformat.Inthisway,wecanaccessverylargearraysofdata(muchlargerthanuniformbuffers).
• Storagetexelbuffers–Thesearesimilartouniformtexelbuffers.Notonlycantheybeusedforreading,buttheycanalsobeusedforstoringdata.
Alloftheabovedescriptorsarecreatedfromsamplers,images,orbuffers.Thedifferenceisinthewaythatweusethem and access inside shaders. All additional parameters of such access may have performance implications. Forexample,withstoragebufferswecanonlyreaddata,butreadingdataisprobablymuchfasterthanstoringdatainsidestoragebuffers.Similarly,texelbuffersallowustoaccessmoreelementsthanwithuniformbuffers,butthismayalsocomewiththecostofworseperformance.Weshouldremembertoselectadescriptorthatfitsourneeds.
Inthistutorialwewanttouseatexture.Forthispurposewecreatedanimageandasampler.Wewillusebothtoprepareacombinedimagesamplerdescriptor.
CreatingaDescriptorSetLayoutPreparing resources tobeusedby shaders shouldbeginwith creatingadescriptor set layout.Descriptor setsare
opaqueobjectsinwhichwestorehandlesofresources.Layoutsdefinethestructureofdescriptorsets—whattypesofdescriptorstheycontain,howmanydescriptorsofeachtypethereare,andwhattheirorderis.
Descriptorsetlayoutcreationstartsbydefiningtheparametersofalldescriptorsavailableinagivenset.ThisisdonebyfillingastructurevariableoftypeVkDescriptorSetLayoutBinding:
VkDescriptorSetLayoutBinding layout_binding = { 0, // uint32_t binding VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // VkDescriptorType descriptorType 1, // uint32_t descriptorCount VK_SHADER_STAGE_FRAGMENT_BIT, // VkShaderStageFlags stageFlags nullptr // const VkSampler
*pImmutableSamplers };
13. Tutorial06.cpp,functionCreateDescriptorSetLayout()
Theabovedescriptioncontainsthefollowingmembers:
• binding–Indexofadescriptorwithinagivenset.Alldescriptorsfromasinglelayout(andset)musthaveauniquebinding.Thissamebindingisalsousedinsideshaderstoaccessadescriptor.
• descriptorType–Thetypeofadescriptor(sampler,uniformbuffer,andsoon.)• descriptorCount–Numberofdescriptorsofaselectedtypeaccessedasanarray.Forasingledescriptor,1
valueshouldbeused.• stageFlags – Set of flags defining all shader stages thatwill have access to a given descriptor. For better
performance,weshouldspecifyonlythosestagesthatwillaccessthegivenresource.• pImmutableSamplers–Affectsonlysamplersthatshouldbepermanentlyboundintothelayout(andcannot
bechangedlater).Butwedon’thavetoworryaboutthisparameter,andwecanbindsamplersasanyotherdescriptorsbysettingthisparametertonull.
Inourexample,wewanttouseonlyonedescriptorofacombinedimagesampler,whichwillbeaccessedonlybyafragmentshader.Itwillbethefirst(bindingzero)descriptorinagivenlayout.Toavoidwastingmemory,weshouldkeepbindingsascompactlyaspossible(asclosetozeroaspossible),becausedriversmayallocatememoryfordescriptorslotseveniftheyarenotused.
Wecanpreparesimilarparametersforotherdescriptorsaccessedfromasingleset.Then,pointerstosuchvariablesareprovidedtoavariableoftypeVkDescriptorSetLayoutCreateInfo:
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, // VkStructureType
sType nullptr, // const void
*pNext 0, //
VkDescriptorSetLayoutCreateFlags flags 1, // uint32_t
bindingCount &layout_binding // const
VkDescriptorSetLayoutBinding *pBindings }; if( vkCreateDescriptorSetLayout( GetDevice(), &descriptor_set_layout_create_info,
nullptr, &Vulkan.DescriptorSet.Layout ) != VK_SUCCESS ) { std::cout << "Could not create descriptor set layout!" << std::endl; return false; }
14. Tutorial06.cpp,functionCreateDescriptorSetLayout()
Thisstructurecontainsjustafewmembers:
• sType – The type of the structure. It should be equal toVK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO.
• pNext–Pointerreservedforextensions.• flags–Thisparameterallowsustoprovideadditionaloptionsforlayoutcreation.Butastheyareconnected
withusingextensions,wecansetthisparametertozero.• bindingCount–Thenumberofbindings,elementsinthepBindingsarray.• pBindings–Apointertoanarraywithdescriptionsofallresourcesinagivenlayout.Thisarraymustbeno
smallerthanthevalueofthebindingCountparameter.
Afterwehavefilledinthestructure,wecancallthevkCreateDescriptorSetLayout()functiontocreateadescriptorsetlayout.Wewillneedthislayoutlater,multipletimes.
CreatingaDescriptorPoolNextstepistoprepareadescriptorset.Descriptorsets,similartocommandbuffers,arenotcreateddirectly;theyare
insteadallocatedfrompools.Beforewecanallocateadescriptorset,weneedtocreateadescriptorpool.
VkDescriptorPoolSize pool_size = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // VkDescriptorType
type 1 // uint32_t
descriptorCount }; VkDescriptorPoolCreateInfo descriptor_pool_create_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, // VkStructureType
sType nullptr, // const void
*pNext 0, // VkDescriptorPoolCreateFlags
flags 1, // uint32_t
maxSets 1, // uint32_t
poolSizeCount &pool_size // const VkDescriptorPoolSize
*pPoolSizes }; if( vkCreateDescriptorPool( GetDevice(), &descriptor_pool_create_info, nullptr,
&Vulkan.DescriptorSet.Pool ) != VK_SUCCESS ) { std::cout << "Could not create descriptor pool!" << std::endl; return false; }
15. Tutorial06.cpp,functionCreateDescriptorPool()
Creatingadescriptorpoolinvolvesspecifyinghowmanydescriptorsetscanbeallocatedfromit.Atthesametime,wealsoneedtospecifywhattypesofdescriptors,andhowmanyofthemcanbeallocatedfromthepoolacrossallsets.Forexample,let’simaginethatwewanttoallocateasinglesampledimageandasinglestoragebufferfromagivenpool,andthatwecanallocatetwodescriptorsets fromthepool.Whendoingthis, ifweallocateonedescriptorsetwithasampledimage,theseconddescriptorcancontainonlyastoragebuffer.Ifasingledescriptorsetallocatedfromthatpoolcontainsbothresources,wecan’tallocateanothersetbecauseitwouldhavetobeempty.Duringdescriptorpoolcreationwedefinethetotalnumberofdescriptorsandtotalnumberofsetsthatcanbeallocatedfromit.Thisisdoneintwosteps.
First,wepreparevariablesoftypeVkDescriptorPoolSizethatspecifythetypeofadescriptorandthetotalnumberofdescriptorsofaselectedtypethatcanbeallocatedfromthepool.Next,weprovideanarrayofsuchvariablestoavariableoftypeVkDescriptorPoolCreateInfo.Itcontainsthefollowingmembers:
• sType – The type of the structure. In this case it should be set toVK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO.
• pNext–Pointerreservedforextensions.• flags – This parameter defines (when using a VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT
flag)whetherindividualsetsallocatedfromthepoolcanbefreedorresetseparately.Ifthisparameterissettozero,alldescriptorsetsallocatedfromthepoolcanonlyberesetatonce(inbulk)byresettingthewholepool.
• maxSets–Isthetotalnumberofsetsthatcanbeallocatedfromthepool.• poolSizeCount–DefinesthenumberofelementsinthepPoolSizesarray.
• pPoolSizes–IsapointertoanarraycontainingnolessthanpoolSizeCountelementscontainingdescriptortypesandatotalnumberofdescriptorsofthattypethatcanbeallocatedfromthepool.
Inourexamplewewanttoallocateonlyasingledescriptorsetwithonlyonedescriptorofacombinedimagesamplertype. We prepare parameters according to our example and create a descriptor pool by calling thevkCreateDescriptorPool()function.
AllocatingDescriptorSetsNowwearereadytoallocatethedescriptorsetitself.Codethatdoesthisisquiteshort:
VkDescriptorSetAllocateInfo descriptor_set_allocate_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, // VkStructureType
sType nullptr, // const void
*pNext Vulkan.DescriptorSet.Pool, // VkDescriptorPool
descriptorPool 1, // uint32_t
descriptorSetCount &Vulkan.DescriptorSet.Layout // const VkDescriptorSetLayout
*pSetLayouts }; if( vkAllocateDescriptorSets( GetDevice(), &descriptor_set_allocate_info,
&Vulkan.DescriptorSet.Handle ) != VK_SUCCESS ) { std::cout << "Could not allocate descriptor set!" << std::endl; return false; }
16. Tutorial06.cpp,functionAllocateDescriptorSet()
ToallocateadescriptorsetweneedtoprepareavariableofVkDescriptorSetAllocateInfotype,whichhasthefollowingmembers:
• sType – Standard type of the structure. For the purpose of descriptor set allocationwe need to set thismembertoavalueofVK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO.
• pNext–Pointerreservedforextensions.• descriptorPool–Handleofadescriptorpoolfromwhichthecommandbuffershouldbeallocated.• descriptorSetCount – Number of descriptor sets we want to allocate (and number of elements in the
pSetLayoutsmember).• pSetLayouts–PointertoanarraywithatleastdescriptorSetCountelements.Eachelementofthisarraymust
containadescriptorsetlayoutthatdefinestheinnerstructureoftheallocateddescriptorset(elementsmayrepeat;forexample,wecanallocatefivedescriptorsetsatonce,allwiththesamelayout).
Aswecanseeintheabovestructure,weneedtoprovidedescriptorsetlayouts.That’swhyweneededtocreatethemearlier.ToallocateaselectednumberofdescriptorsetsfromaprovidedpoolweneedtoprovideapointertotheabovestructuretothevkAllocateDescriptorSets()function.
UpdatingDescriptorSetsWepreparedadescriptorset,butitisempty;it’suninitialized.Nowweneedtofillitorupdateit.Thismeansthatwe
tellthedriverwhichresourcesshouldbeusedfordescriptorsinsidetheset.
Wecanupdateadescriptorsetintwoways:
• Bywritingtothedescriptorset—thiswayweprovidenewresources.
• Bycopyingdatafromanotherdescriptorset—ifwehaveapreviouslyupdateddescriptorsetandifwealsowanttousesomeofitsdescriptorsinanotherdescriptorwecancopythem;thisapproachcanbemuchfasterthanwritingdescriptorsetsdirectlyfromtheCPU.
Aswedon’thaveanotherdescriptorset,weneedtowritetooursingledescriptorsetdirectly.Foreachdescriptortypeweneedtopreparetwostructures.One,commonforalldescriptortypes,istheVkWriteDescriptorSetstructure.Itcontainsthefollowingmembers:
• sType–Typeofthestructure.WeneedtouseaVK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SETvalue.• pNext–Pointerreservedforextensions.• dstSet–Handleofadescriptorsetthatwewanttoupdate(fillwithspecificresources).• dstBinding–Indexwithinthedescriptorsetthatwewanttoupdate.Wemustprovideoneofthebindings
specified during descriptor set layout creation.What’smore, the selected bindingmust correspond to aprovidedtypeofthedescriptor.
• dstArrayElement– Specifies the first array indexwewant toupdate.Using a singleVkWriteDescriptorSetstructure we can updatemultiple elements of a single array. Let’s say we have a four-element array ofsamplersandwewanttoupdatethelasttwo(withindices2and3);wecanprovidetwosamplersandupdatethearraystartingfromindex2.
• descriptorCount – Number of descriptors we want to update (number of elements in pImageInfo orpBufferInfo,orpTexelBufferViewarray).Forordinarydescriptorswesetthevaluetoone.Butforarrayswecanprovidelargervalues.
• descriptorType–Typeofthedescriptorwearegoingtoupdate.Itmustbethesameasthedescriptortypeprovidedduringdescriptorsetlayoutcreationwiththesamebinding(indexwithinadescriptorset).
• pImageInfo–PointertoanarraywithatleastdescriptorCountelementsoftypeVkDescriptorImageInfo.Eachsuch element must contain handles of specific resources when we want to updateVK_DESCRIPTOR_TYPE_SAMPLER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, orVK_DESCRIPTOR_TYPE_INPUT_ATTACHMENTdescriptors.
• pBufferInfo–PointertoanarraywithatleastdescriptorCountelementsoftypeVkDescriptorBufferInfo.Eachsuch element must contain handles of specific resources when we want to updateVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, orVK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMICdescriptors.
• pTexelBufferView–Arraywithat leastdescriptorCountVkBufferViewhandles.Thisarray isusedwhenwewant to update VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, orVK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMICdescriptors.
Dependingonthetypeofdescriptorwewanttoupdate,weneedtoprepareavariable(oranarrayofvariables)oftype VkDescriptorImageInfo, VkDescriptorBufferInfo, or VkBufferView. Here, we want to update a combined imagesamplerdescriptor,soweneedtoprepareavariableoftypeVkDescriptorImageInfo.Itcontainsthefollowingmembers:
• sampler–Handleofasamplerobject.• imageView–Handleofanimageview.• imageLayout –Hereweprovide a layout that the imagewill havewhen thedescriptor is accessed inside
shaders.
Inthisstructureweprovideparametersofspecificresources;wepointtocreatedandvalidresourcesthatwewanttouseinsideshaders.Membersofthisstructureareinitializedbasedonthedescriptortype.Forexample,ifweupdateasampler,weneedtoprovideonlythehandleofasampler.Ifwewanttoupdateasampledimage,weneedtoprovideanimageview’shandleandan image’s layout.But imagewon’tbetransitionedtothis layoutautomatically(as inrender
passes).Weneedtoperformthetransitiontothislayoutourselves,explicitlythroughpipelinebarriersor,incaseofinputattachments,throughrenderpasses.What’smore,weneedtoprovidealayoutthatcorrespondstoagivenusage.
In our examplewewant to use a texture.We can do this either by using separate sampler and sampled imagedescriptorsorbyusingacombinedimagesamplerdescriptor(asintypicalOpenGLapplications).Thelatterapproachcanbemoreoptimal(somehardwareplatformsmaysampledatafromcombinedimagesamplersfasterthanfromseparatesamplersandsampledimages),andwepresentthatapproachhere.Whenwewanttoupdateacombinedimagesampler,weneedtoprovideallthreemembersoftheVkDescriptorImageInfostructure:
VkDescriptorImageInfo image_info = { Vulkan.Image.Sampler, // VkSampler
sampler Vulkan.Image.View, // VkImageView
imageView VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL // VkImageLayout
imageLayout }; VkWriteDescriptorSet descriptor_writes = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, // VkStructureType sType nullptr, // const void *pNext Vulkan.DescriptorSet.Handle, // VkDescriptorSet
dstSet 0, // uint32_t
dstBinding 0, // uint32_t
dstArrayElement 1, // uint32_t
descriptorCount VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // VkDescriptorType
descriptorType &image_info, // const VkDescriptorImageInfo
*pImageInfo nullptr, // const VkDescriptorBufferInfo
*pBufferInfo nullptr // const VkBufferView
*pTexelBufferView }; vkUpdateDescriptorSets( GetDevice(), 1, &descriptor_writes, 0, nullptr );
17. Tutorial06.cpp,functionUpdateDescriptorSet()
ApointertoavariableoftypeVkDescriptorImageInfoisthenprovidedinavariableoftypeVkWriteDescriptorSet.Aswe update only one descriptor, we need only one instance of both structures. But of course we can update moredescriptors at a time, in which case we need to prepare more variables, which are then provided to thevkUpdateDescriptorSets()function.
CreatingaPipelineLayoutWearenotyetdone.Whenwewanttousedescriptors,allocatingandupdatingdescriptorsetsisnottheonlyjobwe
needtoperform.Wehavepreparedspecificresourcesthatarealmostreadytobeusedinsideshaders,butdescriptorsetsareusedtostorehandlesofspecificresources.Thesehandlesareprovidedduringcommandbufferrecording.Weneedtoprepareinformationfortheothersideofthebarricade:thedriveralsoneedstoknowwhattypesofresourcesthegivenpipelineneedsaccessto.Thisinformationiscrucialwhenwecreateapipelineasitmayimpactitsinternalstructureormaybeevenashadercompilation.Andthisinformationisprovidedinaso-calledpipelinelayout.
Thepipelinelayoutstoresinformationaboutresourcetypesthatthegivenpipelinehasaccessto.Theseresourcesinvolvedescriptorsandpushconstantranges.Fornowwecanskippushconstantsandfocusonlyondescriptors.
Tocreateapipelinelayoutandprepareinformationaboutthetypesofresourcesaccessedbythepipeline,weneedto provide an array of descriptor set layouts. This is done through the following members of a variable of typeVkPipelineLayoutCreateInfo:
• sType–Typeofthestructure.AVK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFOvalueshouldbeusedinthiscase.
• pNext–Pointerreservedforextensions.• flags–Thisparameterisreservedforfutureuse.• setLayoutCount–NumberofelementsinthepSetLayoutsmemberandnumberofseparatedescriptorsets
thatcanbeusedwiththispipeline.• pSetLayouts–Arraywithdescriptorsetlayouts.• pushConstantRangeCount–Numberofseparatepushconstantranges.• pPushConstantRanges–Arraywithelementsdescribingpushconstantranges.
And this is when descriptor set layouts are used again. The single descriptor set layout defines resource typescontainedwithinasingledescriptorset.Andanarrayoftheselayoutsdefinesresourcetypesthatthegivenpipelineneedsaccessto.
TocreateapipelinelayoutwejustcallthevkCreatePipelineLayout()function.WedidthisinIntroductiontoVulkanPart3:FirstTriangle.Buttherewecreatedanempty layout(withnopushconstantsandwithnoaccesstodescriptorresources).Here,wecreateamoretypicalpipelinelayout.
VkPipelineLayoutCreateInfo layout_create_info = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, // VkStructureType
sType nullptr, // const void
*pNext 0, // VkPipelineLayoutCreateFlags
flags 1, // uint32_t
setLayoutCount &Vulkan.DescriptorSet.Layout, // const VkDescriptorSetLayout
*pSetLayouts 0, // uint32_t
pushConstantRangeCount nullptr // const VkPushConstantRange
*pPushConstantRanges }; if( vkCreatePipelineLayout( GetDevice(), &layout_create_info, nullptr,
&Vulkan.PipelineLayout ) != VK_SUCCESS ) { std::cout << "Could not create pipeline layout!" << std::endl; return false; } return true;
18. Tutorial06.cpp,functionCreatePipelineLayout()
Suchlayoutisthenprovidedduringpipelinecreation.Wealsoneedtousethislayoutwhenwebinddescriptorsetsduringcommandbufferrecording.Soweneedtostorethepipelinelayouthandle.
BindingDescriptorSetsOne last thing is tobinddescriptor sets to thecommandbufferduring recording.Wecanhavemultipledifferent
descriptor sets ormultiple, similar descriptor sets (with the same layouts), but theymay contain different resourcehandles.Whichofthesedescriptorsareusedduringrenderingisdefinedduringcommandbufferrecording.Beforewe
candrawanything,weneedtosetupavalidstate(accordingtothedrawingparameters).Foreachcommandbufferwerecordweneedtodoitfromscratch.
Drawingoperations requiresus touse renderpasses andpipelines. If a pipelineusesdescriptor resources (whenshadersaccessimagesorbuffers),weneedtobinddescriptorsetsbycallingthevkCmdBindDescriptorSets()function.Forthisfunctionwemustprovideahandleofthepipelinelayoutandanarrayofdescriptorsethandles.Webinddescriptorsetstospecificindices.Thegivenindexwebindadescriptorsettomustcorrespondtoitslayoutprovidedatthesameindexduringpipelinecreation.
vkCmdBeginRenderPass( command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE );
vkCmdBindPipeline( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
Vulkan.GraphicsPipeline ); // ... vkCmdBindDescriptorSets( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
Vulkan.PipelineLayout, 0, 1, &Vulkan.DescriptorSet.Handle, 0, nullptr ); vkCmdDraw( command_buffer, 4, 1, 0, 0 ); vkCmdEndRenderPass( command_buffer );
19. Tutorial06.cpp,functionPrepareFrame()
AccessingDescriptorsinShadersOnemorething.Weneedtowritepropershaders.Inthisexample,weaccessatextureinsideafragmentshaderonly,
soonlythefragmentshaderwillbepresented.
Fromthebeginningof this tutorialwehavebeenreferring todescriptorsets,bindingswithindescriptorsets,andaboutbindingdescriptorsetsthemselves.Atthesametime,wemayhavemultipledescriptorsetsboundtoacommandbuffer.Eachdescriptorsetmaycontainmultipleresources.Thisdataconformstoaspecificaddressthatweuseinsideshaders.Thisaddressisdefinedthroughalayout()specifierlikethis:
layout(set=S, binding=B) uniform <variable type> <variable name>
Setdefinesan indexthatthegivendescriptorsetwasboundtothroughthevkCmdBindDescriptorSets() function.Bindingspecifiestheindexofaresourcewithintheprovidedsetandcorrespondstothebindingdefinedduringdescriptorsetlayoutcreation.Inourcase,wehaveonlyonedescriptorsetprovidedatindexzero,withonlyonecombinedimagesampler at binding zero. Combined image samplers are accessed inside shaders through sampler1D, sampler2D, orsampler3Dvariables.Soourfragmentshader’ssourcecodelookslikethis:
#version 450 layout(set=0, binding=0) uniform sampler2D u_Texture; layout(location = 0) in vec2 v_Texcoord; layout(location = 0) out vec4 o_Color; void main() { o_Color = texture( u_Texture, v_Texcoord ); }
20. shader.frag,-
Tutorial06ExecutionWecanseebelowhowthefinalimagegeneratedbythesampleprogramshouldlook:
Werenderaquadthathasatextureappliedtoitssurface.Thequadshouldadjustitssize(andaspect)tomatchthewindow’ssizeandshape(ifwestretchthewindow,thequadandtheimagewillbestretchedtoo).
CleaningUpBeforewecanendourapplication,weshouldperformacleanup.
// ... if( Vulkan.GraphicsPipeline != VK_NULL_HANDLE ) { vkDestroyPipeline( GetDevice(), Vulkan.GraphicsPipeline, nullptr ); Vulkan.GraphicsPipeline = VK_NULL_HANDLE; } if( Vulkan.PipelineLayout != VK_NULL_HANDLE ) { vkDestroyPipelineLayout( GetDevice(), Vulkan.PipelineLayout, nullptr ); Vulkan.PipelineLayout = VK_NULL_HANDLE; } // ... if( Vulkan.DescriptorSet.Pool != VK_NULL_HANDLE ) { vkDestroyDescriptorPool( GetDevice(), Vulkan.DescriptorSet.Pool, nullptr ); Vulkan.DescriptorSet.Pool = VK_NULL_HANDLE; } if( Vulkan.DescriptorSet.Layout != VK_NULL_HANDLE ) { vkDestroyDescriptorSetLayout( GetDevice(), Vulkan.DescriptorSet.Layout, nullptr ); Vulkan.DescriptorSet.Layout = VK_NULL_HANDLE; } if( Vulkan.Image.Sampler != VK_NULL_HANDLE ) { vkDestroySampler( GetDevice(), Vulkan.Image.Sampler, nullptr ); Vulkan.Image.Sampler = VK_NULL_HANDLE; }
if( Vulkan.Image.View != VK_NULL_HANDLE ) { vkDestroyImageView( GetDevice(), Vulkan.Image.View, nullptr ); Vulkan.Image.View = VK_NULL_HANDLE; } if( Vulkan.Image.Handle != VK_NULL_HANDLE ) { vkDestroyImage( GetDevice(), Vulkan.Image.Handle, nullptr ); Vulkan.Image.Handle = VK_NULL_HANDLE; } if( Vulkan.Image.Memory != VK_NULL_HANDLE ) { vkFreeMemory( GetDevice(), Vulkan.Image.Memory, nullptr ); Vulkan.Image.Memory = VK_NULL_HANDLE; }
21. Tutorial06.cpp,functiondestructor
WedestroybothpipelineanditslayoutbycallingthevkDestroyPipeline()andvkDestroyPipelineLayout()functions.Next,wedestroythedescriptorpoolwiththevkDestroyDescriptorPool()functionandthedescriptorsetlayoutwiththevkDestroyDescriptorSetLayout()function.Weofcoursedestroyotherresources,butwealreadyknowhowtodothis.Youmaynoticethatwedon’tfreeadescriptorset.Wecanfreeeachdescriptorsetseparatelyifaproperflagwasprovidedduringdescriptorpoolcreation.Butwedon’thaveto—whenwedestroyadescriptorpoolallsetsallocatedfromthispoolarealsofreed.
ConclusionThispartofthetutorialpresentedawaytousetextures(combinedimagesamplers,infact)insideshaders.Todothis
wecreatedanimageandallocatedandboundamemorytoit.Wealsocreatedanimageview.Next,wecopieddatafromastagingbuffertotheimagetoinitializeitscontents.Wealsocreatedasamplerobjectthatdefinedawayinwhichimagedatawasreadinsideshaders.
Next,wepreparedadescriptorset.First,wecreatedadescriptorsetlayout.Afterthat,adescriptorpoolwascreatedfromwhichasingledescriptorsetwasallocated.Weupdatedthissetwiththesamplerandtheimageviewhandles.
Thedescriptorsetlayoutwasalsousedtodefineresourcestowhichourgraphicspipelinehadaccess.Thiswasdoneduringpipelinelayoutcreation.Thislayoutwasthenusedwhenweboundthedescriptorsetstoacommandbuffer.
Wealsolearnedhowtoprepareashadercodethataccessedthecombinedimagesamplertoreaditsdata(tosampleitasatexture).Itwasdoneinsideafragmentshaderthatwasusedduringrenderingofoursimplegeometry.Thiswayweappliedatexturetothesurfaceofthisgeometry.
Inthenexttutorialwewillseehowwecanuseuniformbuffersinsideshaders.
Notices
Intel technologies’ features and benefits depend on system configuration and may require enabled hardware,software or service activation. Performance varies depending on system configuration. Check with your systemmanufacturerorretailerorlearnmoreatintel.com.
No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by thisdocument.
Intel disclaims all express and implied warranties, including without limitation, the implied warranties ofmerchantability,fitnessforaparticularpurpose,andnon-infringement,aswellasanywarrantyarisingfromcourseofperformance,courseofdealing,orusageintrade.
Thisdocumentcontainsinformationonproducts,servicesand/orprocessesindevelopment.Allinformationprovidedhere is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule,specificationsandroadmaps.
Theproductsandservicesdescribedmaycontaindefectsorerrorsknownaserratawhichmaycausedeviationsfrompublishedspecifications.Currentcharacterizederrataareavailableonrequest.
Copiesofdocumentswhichhaveanordernumberandarereferencedinthisdocumentmaybeobtainedbycalling1-800-548-4725orbyvisitingwww.intel.com/design/literature.htm.
ThissamplesourcecodeisreleasedundertheIntelSampleSourceCodeLicenseAgreement.
IntelandtheIntellogoaretrademarksofIntelCorporationintheU.S.and/orothercountries.
*Othernamesandbrandsmaybeclaimedasthepropertyofothers.
©2017IntelCorporation