Heavily reworked this sample

Code cleanup, code restructuring, simplified, new comments and stage buffers to device
This commit is contained in:
Sascha Willems 2024-01-13 11:27:06 +01:00
parent d82ebc8f32
commit 4ae651ce51

View file

@ -1,6 +1,8 @@
/* /*
* Vulkan Example - Compute shader image processing * Vulkan Example - Compute shader image processing
* *
* This sample uses a compute shader to apply different filters to an image
*
* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de
* *
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
@ -16,53 +18,47 @@ struct Vertex {
class VulkanExample : public VulkanExampleBase class VulkanExample : public VulkanExampleBase
{ {
private:
vks::Texture2D textureColorMap;
vks::Texture2D textureComputeTarget;
public: public:
struct { // Input image
VkPipelineVertexInputStateCreateInfo inputState; vks::Texture2D textureColorMap;
std::vector<VkVertexInputBindingDescription> bindingDescriptions; // Storage image that the compute shader uses to apply the filter effect to
std::vector<VkVertexInputAttributeDescription> attributeDescriptions; vks::Texture2D storageImage;
} vertices;
// Resources for the graphics part of the example // Resources for the graphics part of the example
struct { struct Graphics {
VkDescriptorSetLayout descriptorSetLayout; // Image display shader binding layout VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Image display shader binding layout
VkDescriptorSet descriptorSetPreCompute; // Image display shader bindings before compute shader image manipulation VkDescriptorSet descriptorSetPreCompute{ VK_NULL_HANDLE }; // Image display shader bindings before compute shader image manipulation
VkDescriptorSet descriptorSetPostCompute; // Image display shader bindings after compute shader image manipulation VkDescriptorSet descriptorSetPostCompute{ VK_NULL_HANDLE }; // Image display shader bindings after compute shader image manipulation
VkPipeline pipeline; // Image display pipeline VkPipeline pipeline{ VK_NULL_HANDLE }; // Image display pipeline
VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the graphics pipeline
VkSemaphore semaphore; // Execution dependency between compute & graphic submission VkSemaphore semaphore{ VK_NULL_HANDLE }; // Execution dependency between compute & graphic submission
// Used to pass data to the graphics shaders
struct UniformData {
glm::mat4 projection;
glm::mat4 modelView;
} uniformData;
vks::Buffer uniformBuffer;
} graphics; } graphics;
// Resources for the compute part of the example // Resources for the compute part of the example
struct Compute { struct Compute {
VkQueue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics) VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics)
VkCommandPool commandPool; // Use a separate command pool (queue family may differ from the one used for graphics) VkCommandPool commandPool{ VK_NULL_HANDLE }; // Use a separate command pool (queue family may differ from the one used for graphics)
VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers VkCommandBuffer commandBuffer{ VK_NULL_HANDLE }; // Command buffer storing the dispatch commands and barriers
VkSemaphore semaphore; // Execution dependency between compute & graphic submission VkSemaphore semaphore{ VK_NULL_HANDLE }; // Execution dependency between compute & graphic submission
VkDescriptorSetLayout descriptorSetLayout; // Compute shader binding layout VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout
VkDescriptorSet descriptorSet; // Compute shader bindings VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings
VkPipelineLayout pipelineLayout; // Layout of the compute pipeline VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline
std::vector<VkPipeline> pipelines; // Compute pipelines for image filters std::vector<VkPipeline> pipelines{}; // Compute pipelines for image filters
int32_t pipelineIndex = 0; // Current image filtering compute pipeline index int32_t pipelineIndex{ 0 }; // Current image filtering compute pipeline index
} compute; } compute;
vks::Buffer vertexBuffer; vks::Buffer vertexBuffer;
vks::Buffer indexBuffer; vks::Buffer indexBuffer;
uint32_t indexCount; uint32_t indexCount{ 0 };
uint32_t vertexBufferSize{ 0 };
vks::Buffer uniformBufferVS; std::vector<std::string> filterNames{};
struct {
glm::mat4 projection;
glm::mat4 modelView;
} uboVS;
int vertexBufferSize;
std::vector<std::string> shaderNames;
VulkanExample() : VulkanExampleBase() VulkanExample() : VulkanExampleBase()
{ {
@ -75,11 +71,13 @@ public:
~VulkanExample() ~VulkanExample()
{ {
if (device) {
// Graphics // Graphics
vkDestroyPipeline(device, graphics.pipeline, nullptr); vkDestroyPipeline(device, graphics.pipeline, nullptr);
vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
vkDestroySemaphore(device, graphics.semaphore, nullptr); vkDestroySemaphore(device, graphics.semaphore, nullptr);
graphics.uniformBuffer.destroy();
// Compute // Compute
for (auto& pipeline : compute.pipelines) for (auto& pipeline : compute.pipelines)
@ -93,30 +91,31 @@ public:
vertexBuffer.destroy(); vertexBuffer.destroy();
indexBuffer.destroy(); indexBuffer.destroy();
uniformBufferVS.destroy();
textureColorMap.destroy(); textureColorMap.destroy();
textureComputeTarget.destroy(); storageImage.destroy();
}
} }
// Prepare a texture target that is used to store compute shader calculations // Prepare a storage image that is used to store the compute shader filter
void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format) void prepareStorageImage()
{ {
VkFormatProperties formatProperties; const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
VkFormatProperties formatProperties;
// Get device properties for the requested texture format // Get device properties for the requested texture format
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
// Check if requested image format supports image storage operations // Check if requested image format supports image storage operations required for storing pixel from the compute shader
assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
// Prepare blit target texture // Prepare blit target texture
tex->width = width; storageImage.width = textureColorMap.width;
tex->height = height; storageImage.height = textureColorMap.height;
VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = format; imageCreateInfo.format = format;
imageCreateInfo.extent = { width, height, 1 }; imageCreateInfo.extent = { storageImage.width, storageImage.height, 1 };
imageCreateInfo.mipLevels = 1; imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1; imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
@ -136,27 +135,20 @@ public:
imageCreateInfo.queueFamilyIndexCount = 2; imageCreateInfo.queueFamilyIndexCount = 2;
imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data(); imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data();
} }
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &storageImage.image));
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
VkMemoryRequirements memReqs; VkMemoryRequirements memReqs;
vkGetImageMemoryRequirements(device, storageImage.image, &memReqs);
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image));
vkGetImageMemoryRequirements(device, tex->image, &memReqs);
memAllocInfo.allocationSize = memReqs.size; memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory)); VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &storageImage.deviceMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0)); VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0));
// Transition image to the general layout, so we can use it as a storage image in the compute shader
VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL; vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.imageLayout);
vks::tools::setImageLayout(
layoutCmd, tex->image,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
tex->imageLayout);
vulkanDevice->flushCommandBuffer(layoutCmd, queue, true); vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
// Create sampler // Create sampler
@ -171,9 +163,9 @@ public:
sampler.maxAnisotropy = 1.0f; sampler.maxAnisotropy = 1.0f;
sampler.compareOp = VK_COMPARE_OP_NEVER; sampler.compareOp = VK_COMPARE_OP_NEVER;
sampler.minLod = 0.0f; sampler.minLod = 0.0f;
sampler.maxLod = static_cast<float>(tex->mipLevels); sampler.maxLod = 1.0f;
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler)); VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &storageImage.sampler));
// Create image view // Create image view
VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo(); VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
@ -181,14 +173,14 @@ public:
view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.viewType = VK_IMAGE_VIEW_TYPE_2D;
view.format = format; view.format = format;
view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
view.image = tex->image; view.image = storageImage.image;
VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view)); VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.view));
// Initialize a descriptor for later use // Initialize a descriptor for later use
tex->descriptor.imageLayout = tex->imageLayout; storageImage.descriptor.imageLayout = storageImage.imageLayout;
tex->descriptor.imageView = tex->view; storageImage.descriptor.imageView = storageImage.view;
tex->descriptor.sampler = tex->sampler; storageImage.descriptor.sampler = storageImage.sampler;
tex->device = vulkanDevice; storageImage.device = vulkanDevice;
} }
void loadAssets() void loadAssets()
@ -226,7 +218,7 @@ public:
// We won't be changing the layout of the image // We won't be changing the layout of the image
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
imageMemoryBarrier.image = textureComputeTarget.image; imageMemoryBarrier.image = storageImage.image;
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
@ -287,17 +279,16 @@ public:
vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelines[compute.pipelineIndex]); vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelines[compute.pipelineIndex]);
vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0); vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1); vkCmdDispatch(compute.commandBuffer, storageImage.width / 16, storageImage.height / 16, 1);
vkEndCommandBuffer(compute.commandBuffer); vkEndCommandBuffer(compute.commandBuffer);
} }
// Setup vertices for a single uv-mapped quad // Setup vertices for a single uv-mapped quad used to display the input and output images
void generateQuad() void generateQuad()
{ {
// Setup vertices for a single uv-mapped quad made from two triangles // Setup vertices for a single uv-mapped quad made from two triangles
std::vector<Vertex> vertices = std::vector<Vertex> vertices = {
{
{ { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f } }, { { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f } },
{ { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f } }, { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f } },
{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f } }, { { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f } },
@ -308,48 +299,31 @@ public:
std::vector<uint32_t> indices = { 0,1,2, 2,3,0 }; std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
indexCount = static_cast<uint32_t>(indices.size()); indexCount = static_cast<uint32_t>(indices.size());
// Create buffers // Create buffers and upload data to the GPU
// For the sake of simplicity we won't stage the vertex data to the gpu memory
// Vertex buffer struct StagingBuffers {
VK_CHECK_RESULT(vulkanDevice->createBuffer( vks::Buffer vertices;
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vks::Buffer indices;
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, } stagingBuffers;
&vertexBuffer,
vertices.size() * sizeof(Vertex), // Host visible source buffers (staging)
vertices.data())); VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffers.vertices, vertices.size() * sizeof(Vertex), vertices.data()));
// Index buffer VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffers.indices, indices.size() * sizeof(uint32_t), indices.data()));
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_INDEX_BUFFER_BIT, // Device local destination buffers
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertexBuffer, vertices.size() * sizeof(Vertex)));
&indexBuffer, VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t)));
indices.size() * sizeof(uint32_t),
indices.data())); // Copy from host do device
} vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue);
vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue);
void setupVertexDescriptions()
{ // Clean up
// Binding description stagingBuffers.vertices.destroy();
vertices.bindingDescriptions = { stagingBuffers.indices.destroy();
vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX)
};
// Attribute descriptions
// Describes memory layout and shader positions
vertices.attributeDescriptions = {
// Location 0: Position
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
// Location 1: Texture coordinates
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
};
// Assign to vertex buffer
vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
} }
// The descriptor pool will be shared between graphics and compute
void setupDescriptorPool() void setupDescriptorPool()
{ {
std::vector<VkDescriptorPoolSize> poolSizes = { std::vector<VkDescriptorPoolSize> poolSizes = {
@ -364,122 +338,10 @@ public:
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
} }
void setupDescriptorSetLayout() // Prepare the graphics resources used to display the ray traced output of the compute shader
{
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0: Vertex shader uniform buffer
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
// Binding 1: Fragment shader input image
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
}
void setupDescriptorSet()
{
VkDescriptorSetAllocateInfo allocInfo =
vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1);
// Input image (before compute post processing)
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPreCompute));
std::vector<VkWriteDescriptorSet> baseImageWriteDescriptorSets = {
vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureColorMap.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(baseImageWriteDescriptorSets.size()), baseImageWriteDescriptorSets.data(), 0, nullptr);
// Final image (after compute shader processing)
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPostCompute));
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureComputeTarget.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
}
void preparePipelines()
{
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
vks::initializers::pipelineInputAssemblyStateCreateInfo(
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
0,
VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState =
vks::initializers::pipelineRasterizationStateCreateInfo(
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_COUNTER_CLOCKWISE,
0);
VkPipelineColorBlendAttachmentState blendAttachmentState =
vks::initializers::pipelineColorBlendAttachmentState(
0xf,
VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendState =
vks::initializers::pipelineColorBlendStateCreateInfo(
1,
&blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState =
vks::initializers::pipelineDepthStencilStateCreateInfo(
VK_TRUE,
VK_TRUE,
VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState =
vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState =
vks::initializers::pipelineMultisampleStateCreateInfo(
VK_SAMPLE_COUNT_1_BIT,
0);
std::vector<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
// Rendering pipeline
// Load shaders
std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
shaderStages[0] = loadShader(getShadersPath() + "computeshader/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "computeshader/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
vks::initializers::pipelineCreateInfo(
graphics.pipelineLayout,
renderPass,
0);
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
pipelineCreateInfo.renderPass = renderPass;
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
}
void prepareGraphics() void prepareGraphics()
{ {
// Semaphore for compute & graphics sync // Create a semaphore for compute & graphics sync
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo(); VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore)); VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore));
@ -489,6 +351,86 @@ public:
submitInfo.pSignalSemaphores = &graphics.semaphore; submitInfo.pSignalSemaphores = &graphics.semaphore;
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VK_CHECK_RESULT(vkQueueWaitIdle(queue)); VK_CHECK_RESULT(vkQueueWaitIdle(queue));
// Setup descriptors
// The graphics pipeline uses two sets with two bindings
// One set for displaying the input image and one set for displaying the output image with the compute filter applied
// Binding 0: Vertex shader uniform buffer
// Binding 1: Sampled image (before/after compute filter is applied)
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
VkDescriptorSetAllocateInfo allocInfo =
vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1);
// Input image (before compute post processing)
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPreCompute));
std::vector<VkWriteDescriptorSet> baseImageWriteDescriptorSets = {
vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor),
vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureColorMap.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(baseImageWriteDescriptorSets.size()), baseImageWriteDescriptorSets.data(), 0, nullptr);
// Final image (after compute shader processing)
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPostCompute));
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor),
vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &storageImage.descriptor)
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
// Graphics pipeline used to display the images (before and after the compute effect is applied)
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
// Shaders
shaderStages[0] = loadShader(getShadersPath() + "computeshader/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "computeshader/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
// Vertex input state
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX)
};
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
};
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass, 0);
pipelineCreateInfo.pVertexInputState = &vertexInputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
} }
void prepareCompute() void prepareCompute()
@ -509,28 +451,23 @@ public:
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout));
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
VkDescriptorSetAllocateInfo allocInfo =
vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = { std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &textureColorMap.descriptor), vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &textureColorMap.descriptor),
vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &textureComputeTarget.descriptor) vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImage.descriptor)
}; };
vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr);
// Create compute shader pipelines // Create compute shader pipelines
VkComputePipelineCreateInfo computePipelineCreateInfo = VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
// One pipeline for each effect // One pipeline for each available image filter
shaderNames = { "emboss", "edgedetect", "sharpen" }; filterNames = { "emboss", "edgedetect", "sharpen" };
for (auto& shaderName : shaderNames) { for (auto& shaderName : filterNames) {
std::string fileName = getShadersPath() + "computeshader/" + shaderName + ".comp.spv"; std::string fileName = getShadersPath() + "computeshader/" + shaderName + ".comp.spv";
computePipelineCreateInfo.stage = loadShader(fileName, VK_SHADER_STAGE_COMPUTE_BIT); computePipelineCreateInfo.stage = loadShader(fileName, VK_SHADER_STAGE_COMPUTE_BIT);
VkPipeline pipeline; VkPipeline pipeline;
@ -546,12 +483,7 @@ public:
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool)); VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
// Create a command buffer for compute operations // Create a command buffer for compute operations
VkCommandBufferAllocateInfo cmdBufAllocateInfo = VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo( compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
vks::initializers::commandBufferAllocateInfo(
compute.commandPool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1);
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer)); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
// Semaphore for compute & graphics sync // Semaphore for compute & graphics sync
@ -562,27 +494,35 @@ public:
buildComputeCommandBuffer(); buildComputeCommandBuffer();
} }
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers() void prepareUniformBuffers()
{ {
// Vertex shader uniform buffer block // Vertex shader uniform buffer block
VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &graphics.uniformBuffer, sizeof(Graphics::UniformData)));
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&uniformBufferVS,
sizeof(uboVS)));
// Map persistent // Map persistent
VK_CHECK_RESULT(uniformBufferVS.map()); VK_CHECK_RESULT(graphics.uniformBuffer.map());
updateUniformBuffers();
} }
void updateUniformBuffers() void updateUniformBuffers()
{ {
uboVS.projection = camera.matrices.perspective; // We need to adjust the perspective as this sample displays two viewports side-by-side
uboVS.modelView = camera.matrices.view; camera.setPerspective(60.0f, (float)width * 0.5f / (float)height, 1.0f, 256.0f);
memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS)); graphics.uniformData.projection = camera.matrices.perspective;
graphics.uniformData.modelView = camera.matrices.view;
memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData));
}
void prepare()
{
VulkanExampleBase::prepare();
loadAssets();
generateQuad();
prepareUniformBuffers();
prepareStorageImage();
setupDescriptorPool();
prepareGraphics();
prepareCompute();
buildCommandBuffers();
prepared = true;
} }
void draw() void draw()
@ -619,44 +559,18 @@ public:
VulkanExampleBase::submitFrame(); VulkanExampleBase::submitFrame();
} }
void prepare()
{
VulkanExampleBase::prepare();
loadAssets();
generateQuad();
setupVertexDescriptions();
prepareUniformBuffers();
prepareTextureTarget(&textureComputeTarget, textureColorMap.width, textureColorMap.height, VK_FORMAT_R8G8B8A8_UNORM);
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSet();
prepareGraphics();
prepareCompute();
buildCommandBuffers();
prepared = true;
}
virtual void render() virtual void render()
{ {
if (!prepared) if (!prepared)
return; return;
draw(); draw();
if (camera.updated) {
updateUniformBuffers();
}
}
virtual void viewChanged()
{
camera.setPerspective(60.0f, (float)width * 0.5f / (float)height, 1.0f, 256.0f);
updateUniformBuffers(); updateUniformBuffers();
} }
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
{ {
if (overlay->header("Settings")) { if (overlay->header("Settings")) {
if (overlay->comboBox("Shader", &compute.pipelineIndex, shaderNames)) { if (overlay->comboBox("Shader", &compute.pipelineIndex, filterNames)) {
buildComputeCommandBuffer(); buildComputeCommandBuffer();
} }
} }