2024-07-03 22:04:57 +02:00
/*
* Vulkan Example - Buffer device address
*
* This sample shows how to read data from a buffer device address ( aka " reference " ) instead of using uniforms
* The application passes buffer device addresses to the shader via push constants , and the shader then simply reads the data behind that address
* See cube . vert for the shader side of things
*
* Copyright ( C ) 2024 by Sascha Willems - www . saschawillems . de
*
*/
# include "vulkanexamplebase.h"
# include "VulkanglTFModel.h"
class VulkanExample : public VulkanExampleBase
{
public :
bool animate = true ;
struct Cube {
glm : : mat4 modelMatrix ;
vks : : Buffer buffer ;
glm : : vec3 rotation ;
VkDeviceAddress bufferDeviceAddress { } ;
} ;
std : : array < Cube , 2 > cubes { } ;
vks : : Texture2D texture ;
vkglTF : : Model model ;
// Global matrices
struct Scene {
glm : : mat4 mvp ;
vks : : Buffer buffer ;
VkDeviceAddress bufferDeviceAddress { } ;
} scene ;
VkPipeline pipeline { VK_NULL_HANDLE } ;
VkPipelineLayout pipelineLayout { VK_NULL_HANDLE } ;
VkDescriptorSet descriptorSet { VK_NULL_HANDLE } ;
VkDescriptorSetLayout descriptorSetLayout { VK_NULL_HANDLE } ;
PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR { VK_NULL_HANDLE } ;
VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures { } ;
// This sample passes the buffer references ("pointer") using push constants, the shader then reads data from that buffer address
struct PushConstantBlock {
// Reference to the global matrices
VkDeviceAddress sceneReference ;
// Reference to the per model matrices
VkDeviceAddress modelReference ;
} ;
VulkanExample ( ) : VulkanExampleBase ( )
{
title = " Buffer device address " ;
camera . type = Camera : : CameraType : : lookat ;
camera . setPerspective ( 60.0f , ( float ) width / ( float ) height , 0.1f , 512.0f ) ;
camera . setRotation ( glm : : vec3 ( 0.0f , 0.0f , 0.0f ) ) ;
camera . setTranslation ( glm : : vec3 ( 0.0f , 0.0f , - 5.0f ) ) ;
enabledInstanceExtensions . push_back ( VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME ) ;
enabledInstanceExtensions . push_back ( VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME ) ;
enabledDeviceExtensions . push_back ( VK_KHR_DEVICE_GROUP_EXTENSION_NAME ) ;
enabledBufferDeviceAddresFeatures . sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES ;
enabledBufferDeviceAddresFeatures . bufferDeviceAddress = VK_TRUE ;
deviceCreatepNextChain = & enabledBufferDeviceAddresFeatures ;
}
~ VulkanExample ( )
{
if ( device ) {
vkDestroyPipeline ( device , pipeline , nullptr ) ;
vkDestroyPipelineLayout ( device , pipelineLayout , nullptr ) ;
vkDestroyDescriptorSetLayout ( device , descriptorSetLayout , nullptr ) ;
texture . destroy ( ) ;
for ( auto cube : cubes ) {
cube . buffer . destroy ( ) ;
}
scene . buffer . destroy ( ) ;
}
}
virtual void getEnabledFeatures ( )
{
if ( deviceFeatures . samplerAnisotropy ) {
enabledFeatures . samplerAnisotropy = VK_TRUE ;
} ;
}
void loadAssets ( )
{
const uint32_t glTFLoadingFlags = vkglTF : : FileLoadingFlags : : PreTransformVertices | vkglTF : : FileLoadingFlags : : PreMultiplyVertexColors | vkglTF : : FileLoadingFlags : : FlipY ;
model . loadFromFile ( getAssetPath ( ) + " models/cube.gltf " , vulkanDevice , queue , glTFLoadingFlags ) ;
texture . loadFromFile ( getAssetPath ( ) + " textures/crate01_color_height_rgba.ktx " , VK_FORMAT_R8G8B8A8_UNORM , vulkanDevice , queue ) ;
}
// We pass all data via buffer device addresses, so we only allocate descriptors for the images
void setupDescriptors ( )
{
// Pool
std : : vector < VkDescriptorPoolSize > descriptorPoolSizes = {
vks : : initializers : : descriptorPoolSize ( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 1 )
} ;
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks : : initializers : : descriptorPoolCreateInfo ( descriptorPoolSizes , 2 ) ;
VK_CHECK_RESULT ( vkCreateDescriptorPool ( device , & descriptorPoolInfo , nullptr , & descriptorPool ) ) ;
// Layout
std : : vector < VkDescriptorSetLayoutBinding > setLayoutBindings = {
vks : : initializers : : descriptorSetLayoutBinding ( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , VK_SHADER_STAGE_FRAGMENT_BIT , 0 )
} ;
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks : : initializers : : descriptorSetLayoutCreateInfo ( setLayoutBindings ) ;
VK_CHECK_RESULT ( vkCreateDescriptorSetLayout ( device , & descriptorLayout , nullptr , & descriptorSetLayout ) ) ;
// Set
VkDescriptorSetAllocateInfo allocInfo = vks : : initializers : : descriptorSetAllocateInfo ( descriptorPool , & descriptorSetLayout , 1 ) ;
VK_CHECK_RESULT ( vkAllocateDescriptorSets ( device , & allocInfo , & descriptorSet ) ) ;
std : : vector < VkWriteDescriptorSet > writeDescriptorSets = {
vks : : initializers : : writeDescriptorSet ( descriptorSet , VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 0 , & texture . descriptor )
} ;
vkUpdateDescriptorSets ( device , static_cast < uint32_t > ( writeDescriptorSets . size ( ) ) , writeDescriptorSets . data ( ) , 0 , nullptr ) ;
}
void preparePipelines ( )
{
// The buffer addresses will be passed to the shader using push constants
// That way it's very easy to do a draw call, change the reference to another buffer (or part of that buffer) and do the next draw call using different data
VkPushConstantRange pushConstantRange { } ;
pushConstantRange . stageFlags = VK_SHADER_STAGE_VERTEX_BIT ;
pushConstantRange . offset = 0 ;
pushConstantRange . size = sizeof ( PushConstantBlock ) ;
VkPipelineLayoutCreateInfo pipelineLayoutCI = vks : : initializers : : pipelineLayoutCreateInfo ( ) ;
pipelineLayoutCI . pushConstantRangeCount = 1 ;
pipelineLayoutCI . pPushConstantRanges = & pushConstantRange ;
pipelineLayoutCI . setLayoutCount = 1 ;
pipelineLayoutCI . pSetLayouts = & descriptorSetLayout ;
VK_CHECK_RESULT ( vkCreatePipelineLayout ( device , & pipelineLayoutCI , nullptr , & pipelineLayout ) ) ;
const std : : vector < VkDynamicState > dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT , VK_DYNAMIC_STATE_SCISSOR } ;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks : : initializers : : pipelineInputAssemblyStateCreateInfo ( VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST , 0 , VK_FALSE ) ;
VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks : : initializers : : pipelineRasterizationStateCreateInfo ( VK_POLYGON_MODE_FILL , VK_CULL_MODE_BACK_BIT , VK_FRONT_FACE_COUNTER_CLOCKWISE , 0 ) ;
VkPipelineColorBlendAttachmentState blendAttachmentState = vks : : initializers : : pipelineColorBlendAttachmentState ( 0xf , VK_FALSE ) ;
VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks : : initializers : : pipelineColorBlendStateCreateInfo ( 1 , & blendAttachmentState ) ;
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks : : initializers : : pipelineDepthStencilStateCreateInfo ( VK_TRUE , VK_TRUE , VK_COMPARE_OP_LESS_OR_EQUAL ) ;
VkPipelineViewportStateCreateInfo viewportStateCI = vks : : initializers : : pipelineViewportStateCreateInfo ( 1 , 1 , 0 ) ;
VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks : : initializers : : pipelineMultisampleStateCreateInfo ( VK_SAMPLE_COUNT_1_BIT , 0 ) ;
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks : : initializers : : pipelineDynamicStateCreateInfo ( dynamicStateEnables . data ( ) , static_cast < uint32_t > ( dynamicStateEnables . size ( ) ) , 0 ) ;
std : : array < VkPipelineShaderStageCreateInfo , 2 > shaderStages = {
loadShader ( getShadersPath ( ) + " bufferdeviceaddress/cube.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ,
loadShader ( getShadersPath ( ) + " bufferdeviceaddress/cube.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT )
} ;
VkGraphicsPipelineCreateInfo pipelineCI = vks : : initializers : : pipelineCreateInfo ( pipelineLayout , renderPass , 0 ) ;
pipelineCI . pInputAssemblyState = & inputAssemblyStateCI ;
pipelineCI . pRasterizationState = & rasterizationStateCI ;
pipelineCI . pColorBlendState = & colorBlendStateCI ;
pipelineCI . pMultisampleState = & multisampleStateCI ;
pipelineCI . pViewportState = & viewportStateCI ;
pipelineCI . pDepthStencilState = & depthStencilStateCI ;
pipelineCI . pDynamicState = & dynamicStateCI ;
pipelineCI . stageCount = static_cast < uint32_t > ( shaderStages . size ( ) ) ;
pipelineCI . pStages = shaderStages . data ( ) ;
pipelineCI . pVertexInputState = vkglTF : : Vertex : : getPipelineVertexInputState ( { vkglTF : : VertexComponent : : Position , vkglTF : : VertexComponent : : Normal , vkglTF : : VertexComponent : : UV , vkglTF : : VertexComponent : : Color } ) ;
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device , pipelineCache , 1 , & pipelineCI , nullptr , & pipeline ) ) ;
}
void prepareBuffers ( )
{
// Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
VK_CHECK_RESULT ( vulkanDevice - > createBuffer ( VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT , & scene . buffer , sizeof ( glm : : mat4 ) ) ) ;
VK_CHECK_RESULT ( scene . buffer . map ( ) ) ;
// Get the device of this buffer that is later on passed to the shader (aka "reference")
VkBufferDeviceAddressInfo bufferDeviceAdressInfo { } ;
bufferDeviceAdressInfo . sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO ;
bufferDeviceAdressInfo . buffer = scene . buffer . buffer ;
scene . bufferDeviceAddress = vkGetBufferDeviceAddressKHR ( device , & bufferDeviceAdressInfo ) ;
for ( auto & cube : cubes ) {
// Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag
VK_CHECK_RESULT ( vulkanDevice - > createBuffer ( VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT , & cube . buffer , sizeof ( glm : : mat4 ) ) ) ;
VK_CHECK_RESULT ( cube . buffer . map ( ) ) ;
// Get the device of this buffer that is later on passed to the shader (aka "reference")
bufferDeviceAdressInfo . buffer = cube . buffer . buffer ;
cube . bufferDeviceAddress = vkGetBufferDeviceAddressKHR ( device , & bufferDeviceAdressInfo ) ;
}
updateBuffers ( ) ;
}
void updateBuffers ( )
{
scene . mvp = camera . matrices . perspective * camera . matrices . view ;
memcpy ( scene . buffer . mapped , & scene , sizeof ( glm : : mat4 ) ) ;
cubes [ 0 ] . modelMatrix = glm : : translate ( glm : : mat4 ( 1.0f ) , glm : : vec3 ( - 2.0f , 0.0f , 0.0f ) ) ;
cubes [ 1 ] . modelMatrix = glm : : translate ( glm : : mat4 ( 1.0f ) , glm : : vec3 ( 1.5f , 0.5f , 0.0f ) ) ;
for ( auto & cube : cubes ) {
cube . modelMatrix = glm : : rotate ( cube . modelMatrix , glm : : radians ( cube . rotation . x ) , glm : : vec3 ( 1.0f , 0.0f , 0.0f ) ) ;
cube . modelMatrix = glm : : rotate ( cube . modelMatrix , glm : : radians ( cube . rotation . y ) , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
cube . modelMatrix = glm : : rotate ( cube . modelMatrix , glm : : radians ( cube . rotation . z ) , glm : : vec3 ( 0.0f , 0.0f , 1.0f ) ) ;
cube . modelMatrix = glm : : scale ( cube . modelMatrix , glm : : vec3 ( 0.25f ) ) ;
memcpy ( cube . buffer . mapped , & cube . modelMatrix , sizeof ( glm : : mat4 ) ) ;
}
}
void prepare ( )
{
VulkanExampleBase : : prepare ( ) ;
// We need this extension function to get the address of a buffer so we can pass it to the shader
vkGetBufferDeviceAddressKHR = reinterpret_cast < PFN_vkGetBufferDeviceAddressKHR > ( vkGetDeviceProcAddr ( device , " vkGetBufferDeviceAddressKHR " ) ) ;
loadAssets ( ) ;
prepareBuffers ( ) ;
setupDescriptors ( ) ;
preparePipelines ( ) ;
buildCommandBuffers ( ) ;
prepared = true ;
}
void buildCommandBuffers ( )
{
VkCommandBufferBeginInfo cmdBufInfo = vks : : initializers : : commandBufferBeginInfo ( ) ;
VkClearValue clearValues [ 2 ] ;
clearValues [ 0 ] . color = defaultClearColor ;
clearValues [ 1 ] . depthStencil = { 1.0f , 0 } ;
VkRenderPassBeginInfo renderPassBeginInfo = vks : : initializers : : renderPassBeginInfo ( ) ;
renderPassBeginInfo . renderPass = renderPass ;
renderPassBeginInfo . renderArea . offset . x = 0 ;
renderPassBeginInfo . renderArea . offset . y = 0 ;
renderPassBeginInfo . renderArea . extent . width = width ;
renderPassBeginInfo . renderArea . extent . height = height ;
renderPassBeginInfo . clearValueCount = 2 ;
renderPassBeginInfo . pClearValues = clearValues ;
for ( int32_t i = 0 ; i < drawCmdBuffers . size ( ) ; + + i ) {
renderPassBeginInfo . framebuffer = frameBuffers [ i ] ;
VK_CHECK_RESULT ( vkBeginCommandBuffer ( drawCmdBuffers [ i ] , & cmdBufInfo ) ) ;
vkCmdBeginRenderPass ( drawCmdBuffers [ i ] , & renderPassBeginInfo , VK_SUBPASS_CONTENTS_INLINE ) ;
vkCmdBindPipeline ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , pipeline ) ;
VkViewport viewport = vks : : initializers : : viewport ( ( float ) width , ( float ) height , 0.0f , 1.0f ) ;
vkCmdSetViewport ( drawCmdBuffers [ i ] , 0 , 1 , & viewport ) ;
VkRect2D scissor = vks : : initializers : : rect2D ( width , height , 0 , 0 ) ;
vkCmdSetScissor ( drawCmdBuffers [ i ] , 0 , 1 , & scissor ) ;
vkCmdBindDescriptorSets ( drawCmdBuffers [ i ] , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelineLayout , 0 , 1 , & descriptorSet , 0 , nullptr ) ;
model . bindBuffers ( drawCmdBuffers [ i ] ) ;
// Instead of using descriptors to pass global and per-model matrices to the shader, we can now simply pass buffer references via push constants
2024-09-05 21:35:58 +02:00
// The shader then simply reads data from the address of that reference
2024-07-03 22:04:57 +02:00
PushConstantBlock references { } ;
// Pass pointer to the global matrix via a buffer device address
references . sceneReference = scene . bufferDeviceAddress ;
for ( auto & cube : cubes ) {
// Pass pointer to this cube's data buffer via a buffer device address
// So instead of having to bind different descriptors, we only pass a different device address
// This doesn't have to be an address from a different buffer, but could very well be just another address in the same buffer
references . modelReference = cube . bufferDeviceAddress ;
vkCmdPushConstants ( drawCmdBuffers [ i ] , pipelineLayout , VK_SHADER_STAGE_VERTEX_BIT , 0 , sizeof ( PushConstantBlock ) , & references ) ;
model . draw ( drawCmdBuffers [ i ] ) ;
}
drawUI ( drawCmdBuffers [ i ] ) ;
vkCmdEndRenderPass ( drawCmdBuffers [ i ] ) ;
VK_CHECK_RESULT ( vkEndCommandBuffer ( drawCmdBuffers [ i ] ) ) ;
}
}
void draw ( )
{
VulkanExampleBase : : prepareFrame ( ) ;
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & drawCmdBuffers [ currentBuffer ] ;
VK_CHECK_RESULT ( vkQueueSubmit ( queue , 1 , & submitInfo , VK_NULL_HANDLE ) ) ;
VulkanExampleBase : : submitFrame ( ) ;
}
virtual void render ( )
{
if ( ! prepared )
return ;
draw ( ) ;
if ( animate & & ! paused ) {
cubes [ 0 ] . rotation . x + = 2.5f * frameTimer ;
if ( cubes [ 0 ] . rotation . x > 360.0f )
cubes [ 0 ] . rotation . x - = 360.0f ;
cubes [ 1 ] . rotation . y + = 2.0f * frameTimer ;
if ( cubes [ 1 ] . rotation . x > 360.0f )
cubes [ 1 ] . rotation . x - = 360.0f ;
}
if ( ( camera . updated ) | | ( animate & & ! paused ) ) {
updateBuffers ( ) ;
}
}
virtual void OnUpdateUIOverlay ( vks : : UIOverlay * overlay )
{
if ( overlay - > header ( " Settings " ) ) {
overlay - > checkBox ( " Animate " , & animate ) ;
}
}
} ;
VULKAN_EXAMPLE_MAIN ( )