There is a logical error that cause the first frame render error. Since the graphic rendering need to sample the compute shader 's output image, so the compute command need to run before the graphic rendering. If not, the graphic rendering will sample the gabage data (computer's output image) for the first frame and present on the display screen.This issue can be easy to be reproduced when the GPU device has a lower performance.
668 lines
27 KiB
C++
668 lines
27 KiB
C++
/*
|
|
* Vulkan Example - Compute shader image processing
|
|
*
|
|
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
|
*
|
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
|
*/
|
|
|
|
#include "vulkanexamplebase.h"
|
|
|
|
#define VERTEX_BUFFER_BIND_ID 0
|
|
#define ENABLE_VALIDATION false
|
|
|
|
// Vertex layout for this example
|
|
struct Vertex {
|
|
float pos[3];
|
|
float uv[2];
|
|
};
|
|
|
|
class VulkanExample : public VulkanExampleBase
|
|
{
|
|
private:
|
|
vks::Texture2D textureColorMap;
|
|
vks::Texture2D textureComputeTarget;
|
|
public:
|
|
struct {
|
|
VkPipelineVertexInputStateCreateInfo inputState;
|
|
std::vector<VkVertexInputBindingDescription> bindingDescriptions;
|
|
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
|
|
} vertices;
|
|
|
|
// Resources for the graphics part of the example
|
|
struct {
|
|
VkDescriptorSetLayout descriptorSetLayout; // Image display shader binding layout
|
|
VkDescriptorSet descriptorSetPreCompute; // Image display shader bindings before compute shader image manipulation
|
|
VkDescriptorSet descriptorSetPostCompute; // Image display shader bindings after compute shader image manipulation
|
|
VkPipeline pipeline; // Image display pipeline
|
|
VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline
|
|
VkSemaphore semaphore; // Execution dependency between compute & graphic submission
|
|
} graphics;
|
|
|
|
// Resources for the compute part of the example
|
|
struct Compute {
|
|
VkQueue queue; // 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)
|
|
VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers
|
|
VkSemaphore semaphore; // Execution dependency between compute & graphic submission
|
|
VkDescriptorSetLayout descriptorSetLayout; // Compute shader binding layout
|
|
VkDescriptorSet descriptorSet; // Compute shader bindings
|
|
VkPipelineLayout pipelineLayout; // Layout of the compute pipeline
|
|
std::vector<VkPipeline> pipelines; // Compute pipelines for image filters
|
|
int32_t pipelineIndex = 0; // Current image filtering compute pipeline index
|
|
} compute;
|
|
|
|
vks::Buffer vertexBuffer;
|
|
vks::Buffer indexBuffer;
|
|
uint32_t indexCount;
|
|
|
|
vks::Buffer uniformBufferVS;
|
|
|
|
struct {
|
|
glm::mat4 projection;
|
|
glm::mat4 modelView;
|
|
} uboVS;
|
|
|
|
int vertexBufferSize;
|
|
|
|
std::vector<std::string> shaderNames;
|
|
|
|
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
|
|
{
|
|
title = "Compute shader image load/store";
|
|
camera.type = Camera::CameraType::lookat;
|
|
camera.setPosition(glm::vec3(0.0f, 0.0f, -2.0f));
|
|
camera.setRotation(glm::vec3(0.0f));
|
|
camera.setPerspective(60.0f, (float)width * 0.5f / (float)height, 1.0f, 256.0f);
|
|
settings.overlay = true;
|
|
}
|
|
|
|
~VulkanExample()
|
|
{
|
|
// Graphics
|
|
vkDestroyPipeline(device, graphics.pipeline, nullptr);
|
|
vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
|
|
vkDestroySemaphore(device, graphics.semaphore, nullptr);
|
|
|
|
// Compute
|
|
for (auto& pipeline : compute.pipelines)
|
|
{
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
}
|
|
vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
|
|
vkDestroySemaphore(device, compute.semaphore, nullptr);
|
|
vkDestroyCommandPool(device, compute.commandPool, nullptr);
|
|
|
|
vertexBuffer.destroy();
|
|
indexBuffer.destroy();
|
|
uniformBufferVS.destroy();
|
|
|
|
textureColorMap.destroy();
|
|
textureComputeTarget.destroy();
|
|
}
|
|
|
|
// Prepare a texture target that is used to store compute shader calculations
|
|
void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format)
|
|
{
|
|
VkFormatProperties formatProperties;
|
|
|
|
// Get device properties for the requested texture format
|
|
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
|
|
// Check if requested image format supports image storage operations
|
|
assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
|
|
|
|
// Prepare blit target texture
|
|
tex->width = width;
|
|
tex->height = height;
|
|
|
|
VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
|
|
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imageCreateInfo.format = format;
|
|
imageCreateInfo.extent = { width, height, 1 };
|
|
imageCreateInfo.mipLevels = 1;
|
|
imageCreateInfo.arrayLayers = 1;
|
|
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
// Image will be sampled in the fragment shader and used as storage target in the compute shader
|
|
imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
|
|
imageCreateInfo.flags = 0;
|
|
// If compute and graphics queue family indices differ, we create an image that can be shared between them
|
|
// This can result in worse performance than exclusive sharing mode, but save some synchronization to keep the sample simple
|
|
std::vector<uint32_t> queueFamilyIndices;
|
|
if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) {
|
|
queueFamilyIndices = {
|
|
vulkanDevice->queueFamilyIndices.graphics,
|
|
vulkanDevice->queueFamilyIndices.compute
|
|
};
|
|
imageCreateInfo.sharingMode = VK_SHARING_MODE_CONCURRENT;
|
|
imageCreateInfo.queueFamilyIndexCount = 2;
|
|
imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data();
|
|
}
|
|
|
|
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
|
|
VkMemoryRequirements memReqs;
|
|
|
|
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image));
|
|
|
|
vkGetImageMemoryRequirements(device, tex->image, &memReqs);
|
|
memAllocInfo.allocationSize = memReqs.size;
|
|
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory));
|
|
VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0));
|
|
|
|
VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
|
|
|
tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
vks::tools::setImageLayout(
|
|
layoutCmd, tex->image,
|
|
VK_IMAGE_ASPECT_COLOR_BIT,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
tex->imageLayout);
|
|
|
|
vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
|
|
|
|
// Create sampler
|
|
VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
|
|
sampler.magFilter = VK_FILTER_LINEAR;
|
|
sampler.minFilter = VK_FILTER_LINEAR;
|
|
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
sampler.addressModeV = sampler.addressModeU;
|
|
sampler.addressModeW = sampler.addressModeU;
|
|
sampler.mipLodBias = 0.0f;
|
|
sampler.maxAnisotropy = 1.0f;
|
|
sampler.compareOp = VK_COMPARE_OP_NEVER;
|
|
sampler.minLod = 0.0f;
|
|
sampler.maxLod = tex->mipLevels;
|
|
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler));
|
|
|
|
// Create image view
|
|
VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
|
|
view.image = VK_NULL_HANDLE;
|
|
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view.format = format;
|
|
view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
|
|
view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
|
view.image = tex->image;
|
|
VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view));
|
|
|
|
// Initialize a descriptor for later use
|
|
tex->descriptor.imageLayout = tex->imageLayout;
|
|
tex->descriptor.imageView = tex->view;
|
|
tex->descriptor.sampler = tex->sampler;
|
|
tex->device = vulkanDevice;
|
|
}
|
|
|
|
void loadAssets()
|
|
{
|
|
textureColorMap.loadFromFile(getAssetPath() + "textures/vulkan_11_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_IMAGE_LAYOUT_GENERAL);
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Set target frame buffer
|
|
renderPassBeginInfo.framebuffer = frameBuffers[i];
|
|
|
|
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
|
|
|
|
// Image memory barrier to make sure that compute shader writes are finished before sampling from the texture
|
|
VkImageMemoryBarrier imageMemoryBarrier = {};
|
|
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
// We won't be changing the layout of the image
|
|
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
imageMemoryBarrier.image = textureComputeTarget.image;
|
|
imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
|
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
vkCmdPipelineBarrier(
|
|
drawCmdBuffers[i],
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
VK_FLAGS_NONE,
|
|
0, nullptr,
|
|
0, nullptr,
|
|
1, &imageMemoryBarrier);
|
|
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
VkViewport viewport = vks::initializers::viewport((float)width * 0.5f, (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);
|
|
|
|
VkDeviceSize offsets[1] = { 0 };
|
|
vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
|
|
vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
|
|
|
// Left (pre compute)
|
|
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSetPreCompute, 0, NULL);
|
|
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
|
|
|
|
vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
|
|
|
|
// Right (post compute)
|
|
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSetPostCompute, 0, NULL);
|
|
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
|
|
|
|
viewport.x = (float)width / 2.0f;
|
|
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
|
|
vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
|
|
|
|
drawUI(drawCmdBuffers[i]);
|
|
|
|
vkCmdEndRenderPass(drawCmdBuffers[i]);
|
|
|
|
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
|
|
}
|
|
|
|
}
|
|
|
|
void buildComputeCommandBuffer()
|
|
{
|
|
// Flush the queue if we're rebuilding the command buffer after a pipeline change to ensure it's not currently in use
|
|
vkQueueWaitIdle(compute.queue);
|
|
|
|
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
|
|
|
VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
|
|
|
|
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);
|
|
|
|
vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1);
|
|
|
|
vkEndCommandBuffer(compute.commandBuffer);
|
|
}
|
|
|
|
// Setup vertices for a single uv-mapped quad
|
|
void generateQuad()
|
|
{
|
|
// Setup vertices for a single uv-mapped quad made from two triangles
|
|
std::vector<Vertex> vertices =
|
|
{
|
|
{ { 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, 0.0f } },
|
|
{ { 1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f } }
|
|
};
|
|
|
|
// Setup indices
|
|
std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
|
|
indexCount = static_cast<uint32_t>(indices.size());
|
|
|
|
// Create buffers
|
|
// For the sake of simplicity we won't stage the vertex data to the gpu memory
|
|
// Vertex buffer
|
|
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
&vertexBuffer,
|
|
vertices.size() * sizeof(Vertex),
|
|
vertices.data()));
|
|
// Index buffer
|
|
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
&indexBuffer,
|
|
indices.size() * sizeof(uint32_t),
|
|
indices.data()));
|
|
}
|
|
|
|
void setupVertexDescriptions()
|
|
{
|
|
// Binding description
|
|
vertices.bindingDescriptions = {
|
|
vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX)
|
|
};
|
|
|
|
// Attribute descriptions
|
|
// Describes memory layout and shader positions
|
|
vertices.attributeDescriptions = {
|
|
// Location 0: Position
|
|
vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
|
|
// Location 1: Texture coordinates
|
|
vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
|
|
};
|
|
|
|
// Assign to vertex buffer
|
|
vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
|
vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size();
|
|
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
|
|
vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size();
|
|
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
|
|
}
|
|
|
|
void setupDescriptorPool()
|
|
{
|
|
std::vector<VkDescriptorPoolSize> poolSizes = {
|
|
// Graphics pipelines uniform buffers
|
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
|
|
// Graphics pipelines image samplers for displaying compute output image
|
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2),
|
|
// Compute pipelines uses a storage image for image reads and writes
|
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2),
|
|
};
|
|
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
|
|
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
|
|
}
|
|
|
|
void setupDescriptorSetLayout()
|
|
{
|
|
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, 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, 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.data(),
|
|
dynamicStateEnables.size(),
|
|
0);
|
|
|
|
// 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 = shaderStages.size();
|
|
pipelineCreateInfo.pStages = shaderStages.data();
|
|
pipelineCreateInfo.renderPass = renderPass;
|
|
|
|
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
|
|
}
|
|
|
|
void prepareGraphics()
|
|
{
|
|
// Semaphore for compute & graphics sync
|
|
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore));
|
|
|
|
// Signal the semaphore
|
|
VkSubmitInfo submitInfo = vks::initializers::submitInfo();
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &graphics.semaphore;
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
}
|
|
|
|
void prepareCompute()
|
|
{
|
|
// Get a compute queue from the device
|
|
vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue);
|
|
|
|
// Create compute pipeline
|
|
// Compute pipelines are created separate from graphics pipelines even if they use the same queue
|
|
|
|
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
|
|
// Binding 0: Input image (read-only)
|
|
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0),
|
|
// Binding 1: Output image (write)
|
|
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 1),
|
|
};
|
|
|
|
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
|
|
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout));
|
|
|
|
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
|
|
vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
|
|
|
|
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
|
|
|
|
VkDescriptorSetAllocateInfo allocInfo =
|
|
vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
|
|
|
|
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
|
|
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, 1, &textureComputeTarget.descriptor)
|
|
};
|
|
vkUpdateDescriptorSets(device, computeWriteDescriptorSets.size(), computeWriteDescriptorSets.data(), 0, NULL);
|
|
|
|
// Create compute shader pipelines
|
|
VkComputePipelineCreateInfo computePipelineCreateInfo =
|
|
vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
|
|
|
|
// One pipeline for each effect
|
|
shaderNames = { "emboss", "edgedetect", "sharpen" };
|
|
for (auto& shaderName : shaderNames) {
|
|
std::string fileName = getShadersPath() + "computeshader/" + shaderName + ".comp.spv";
|
|
computePipelineCreateInfo.stage = loadShader(fileName, VK_SHADER_STAGE_COMPUTE_BIT);
|
|
VkPipeline pipeline;
|
|
VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipeline));
|
|
compute.pipelines.push_back(pipeline);
|
|
}
|
|
|
|
// Separate command pool as queue family for compute may be different than graphics
|
|
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
|
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
|
|
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
|
|
|
|
// Create a command buffer for compute operations
|
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
vks::initializers::commandBufferAllocateInfo(
|
|
compute.commandPool,
|
|
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
1);
|
|
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
|
|
|
|
// Semaphore for compute & graphics sync
|
|
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
|
|
|
|
// Build a single command buffer containing the compute dispatch commands
|
|
buildComputeCommandBuffer();
|
|
}
|
|
|
|
// Prepare and initialize uniform buffer containing shader uniforms
|
|
void prepareUniformBuffers()
|
|
{
|
|
// Vertex shader uniform buffer block
|
|
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
&uniformBufferVS,
|
|
sizeof(uboVS)));
|
|
|
|
// Map persistent
|
|
VK_CHECK_RESULT(uniformBufferVS.map());
|
|
|
|
updateUniformBuffers();
|
|
}
|
|
|
|
void updateUniformBuffers()
|
|
{
|
|
uboVS.projection = camera.matrices.perspective;
|
|
uboVS.modelView = camera.matrices.view;
|
|
memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
// Wait for rendering finished
|
|
VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
|
|
// Submit compute commands
|
|
VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
|
|
computeSubmitInfo.commandBufferCount = 1;
|
|
computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
|
|
computeSubmitInfo.waitSemaphoreCount = 1;
|
|
computeSubmitInfo.pWaitSemaphores = &graphics.semaphore;
|
|
computeSubmitInfo.pWaitDstStageMask = &waitStageMask;
|
|
computeSubmitInfo.signalSemaphoreCount = 1;
|
|
computeSubmitInfo.pSignalSemaphores = &compute.semaphore;
|
|
VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE));
|
|
VulkanExampleBase::prepareFrame();
|
|
|
|
VkPipelineStageFlags graphicsWaitStageMasks[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
|
VkSemaphore graphicsWaitSemaphores[] = { compute.semaphore, semaphores.presentComplete };
|
|
VkSemaphore graphicsSignalSemaphores[] = { graphics.semaphore, semaphores.renderComplete };
|
|
|
|
// Submit graphics commands
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
|
|
submitInfo.waitSemaphoreCount = 2;
|
|
submitInfo.pWaitSemaphores = graphicsWaitSemaphores;
|
|
submitInfo.pWaitDstStageMask = graphicsWaitStageMasks;
|
|
submitInfo.signalSemaphoreCount = 2;
|
|
submitInfo.pSignalSemaphores = graphicsSignalSemaphores;
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
|
|
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()
|
|
{
|
|
if (!prepared)
|
|
return;
|
|
draw();
|
|
if (camera.updated) {
|
|
updateUniformBuffers();
|
|
}
|
|
}
|
|
|
|
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
|
|
{
|
|
if (overlay->header("Settings")) {
|
|
if (overlay->comboBox("Shader", &compute.pipelineIndex, shaderNames)) {
|
|
buildComputeCommandBuffer();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
VULKAN_EXAMPLE_MAIN()
|