procedural-3d-engine/examples/computeparticles/computeparticles.cpp

647 lines
26 KiB
C++
Raw Normal View History

2016-02-16 15:07:25 +01:00
/*
* Vulkan Example - Attraction based compute shader particle system
*
* Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher)
*
2024-01-21 12:52:14 +01:00
* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de
2016-02-16 15:07:25 +01:00
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#include "vulkanexamplebase.h"
#if defined(__ANDROID__)
// Lower particle count on Android for performance reasons
#define PARTICLE_COUNT 128 * 1024
#else
#define PARTICLE_COUNT 256 * 1024
#endif
2016-02-16 15:07:25 +01:00
class VulkanExample : public VulkanExampleBase
{
public:
float timer = 0.0f;
float animStart = 20.0f;
bool attachToCursor = false;
2016-02-16 15:07:25 +01:00
struct {
vks::Texture2D particle;
vks::Texture2D gradient;
} textures;
2024-01-21 12:52:14 +01:00
// SSBO particle declaration
struct Particle {
glm::vec2 pos; // Particle position
glm::vec2 vel; // Particle velocity
glm::vec4 gradientPos; // Texture coordinates for the gradient ramp map
};
// We use a shader storage buffer object to store the particlces
// This is updated by the compute pipeline and displayed as a vertex buffer by the graphics pipeline
vks::Buffer storageBuffer;
2016-02-16 15:07:25 +01:00
// Resources for the graphics part of the example
2024-01-21 12:52:14 +01:00
struct Graphics {
uint32_t queueFamilyIndex; // Used to check if compute and graphics queue families differ and require additional barriers
VkDescriptorSetLayout descriptorSetLayout; // Particle system rendering shader binding layout
VkDescriptorSet descriptorSet; // Particle system rendering shader bindings
VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline
VkPipeline pipeline; // Particle rendering pipeline
VkSemaphore semaphore; // Execution dependency between compute & graphic submission
} graphics;
2016-02-16 15:07:25 +01:00
// Resources for the compute part of the example
2024-01-21 12:52:14 +01:00
struct Compute {
uint32_t queueFamilyIndex; // Used to check if compute and graphics queue families differ and require additional barriers
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
VkPipeline pipeline; // Compute pipeline for updating particle positions
2024-01-21 12:52:14 +01:00
vks::Buffer uniformBuffer; // Uniform buffer object containing particle system parameters
struct UniformData { // Compute shader uniform block object
2016-08-17 20:32:05 +02:00
float deltaT; // Frame delta time
float destX; // x position of the attractor
float destY; // y position of the attractor
int32_t particleCount = PARTICLE_COUNT;
2024-01-21 12:52:14 +01:00
} uniformData;
} compute;
VulkanExample() : VulkanExampleBase()
2016-02-16 15:07:25 +01:00
{
title = "Compute shader particle system";
2016-02-16 15:07:25 +01:00
}
~VulkanExample()
{
2024-01-21 12:52:14 +01:00
if (device) {
// Graphics
vkDestroyPipeline(device, graphics.pipeline, nullptr);
vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
vkDestroySemaphore(device, graphics.semaphore, nullptr);
// Compute
compute.uniformBuffer.destroy();
vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
vkDestroyPipeline(device, compute.pipeline, nullptr);
vkDestroySemaphore(device, compute.semaphore, nullptr);
vkDestroyCommandPool(device, compute.commandPool, nullptr);
storageBuffer.destroy();
textures.particle.destroy();
textures.gradient.destroy();
}
}
void loadAssets()
{
textures.particle.loadFromFile(getAssetPath() + "textures/particle01_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
textures.gradient.loadFromFile(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
2016-02-16 15:07:25 +01:00
}
void buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
2016-02-16 15:07:25 +01:00
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
2016-02-16 15:07:25 +01:00
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));
2016-02-16 15:07:25 +01:00
// Acquire barrier
if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
{
VkBufferMemoryBarrier buffer_barrier =
{
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
0,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
compute.queueFamilyIndex,
graphics.queueFamilyIndex,
2024-01-21 12:52:14 +01:00
storageBuffer.buffer,
0,
2024-01-21 12:52:14 +01:00
storageBuffer.size
};
vkCmdPipelineBarrier(
drawCmdBuffers[i],
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
0,
0, nullptr,
1, &buffer_barrier,
0, nullptr);
}
// Draw the particle system using the update vertex buffer
2016-02-16 15:07:25 +01:00
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
2016-02-16 15:07:25 +01:00
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
2016-02-16 15:07:25 +01:00
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL);
2016-02-16 15:07:25 +01:00
VkDeviceSize offsets[1] = { 0 };
2024-01-21 12:52:14 +01:00
vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &storageBuffer.buffer, offsets);
2016-02-16 15:07:25 +01:00
vkCmdDraw(drawCmdBuffers[i], PARTICLE_COUNT, 1, 0, 0);
drawUI(drawCmdBuffers[i]);
2016-02-16 15:07:25 +01:00
vkCmdEndRenderPass(drawCmdBuffers[i]);
// Release barrier
if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
{
VkBufferMemoryBarrier buffer_barrier =
{
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
0,
graphics.queueFamilyIndex,
compute.queueFamilyIndex,
2024-01-21 12:52:14 +01:00
storageBuffer.buffer,
0,
2024-01-21 12:52:14 +01:00
storageBuffer.size
};
vkCmdPipelineBarrier(
drawCmdBuffers[i],
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0, nullptr,
1, &buffer_barrier,
0, nullptr);
}
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
2016-02-16 15:07:25 +01:00
}
}
void buildComputeCommandBuffer()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
// Compute particle movement
2016-08-17 20:32:05 +02:00
// Add memory barrier to ensure that the (graphics) vertex shader has fetched attributes before compute starts to write to the buffer
if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
{
VkBufferMemoryBarrier buffer_barrier =
{
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
0,
VK_ACCESS_SHADER_WRITE_BIT,
graphics.queueFamilyIndex,
compute.queueFamilyIndex,
2024-01-21 12:52:14 +01:00
storageBuffer.buffer,
0,
2024-01-21 12:52:14 +01:00
storageBuffer.size
};
vkCmdPipelineBarrier(
compute.commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0,
0, nullptr,
1, &buffer_barrier,
0, nullptr);
}
// Dispatch the compute job
vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline);
vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
vkCmdDispatch(compute.commandBuffer, PARTICLE_COUNT / 256, 1, 1);
// Add barrier to ensure that compute shader has finished writing to the buffer
// Without this the (rendering) vertex shader may display incomplete results (partial data from last frame)
if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
{
VkBufferMemoryBarrier buffer_barrier =
{
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_SHADER_WRITE_BIT,
0,
compute.queueFamilyIndex,
graphics.queueFamilyIndex,
2024-01-21 12:52:14 +01:00
storageBuffer.buffer,
0,
2024-01-21 12:52:14 +01:00
storageBuffer.size
};
vkCmdPipelineBarrier(
compute.commandBuffer,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0, nullptr,
1, &buffer_barrier,
0, nullptr);
}
vkEndCommandBuffer(compute.commandBuffer);
}
2016-08-17 20:32:05 +02:00
// Setup and fill the compute shader storage buffers containing the particles
2016-02-16 15:07:25 +01:00
void prepareStorageBuffers()
{
std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
std::uniform_real_distribution<float> rndDist(-1.0f, 1.0f);
2016-02-16 15:07:25 +01:00
// Initial particle positions
std::vector<Particle> particleBuffer(PARTICLE_COUNT);
for (auto& particle : particleBuffer) {
particle.pos = glm::vec2(rndDist(rndEngine), rndDist(rndEngine));
particle.vel = glm::vec2(0.0f);
particle.gradientPos.x = particle.pos.x / 2.0f;
2016-02-16 15:07:25 +01:00
}
2016-08-17 20:32:05 +02:00
VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle);
2016-02-16 15:07:25 +01:00
// Staging
// SSBO won't be changed on the host after upload so copy to device local memory
2016-02-16 15:07:25 +01:00
vks::Buffer stagingBuffer;
vulkanDevice->createBuffer(
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&stagingBuffer,
storageBufferSize,
particleBuffer.data());
vulkanDevice->createBuffer(
// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
2024-01-21 12:52:14 +01:00
&storageBuffer,
storageBufferSize);
// Copy from staging buffer to storage buffer
2020-04-20 22:13:51 +02:00
VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
VkBufferCopy copyRegion = {};
copyRegion.size = storageBufferSize;
2024-01-21 12:52:14 +01:00
vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, storageBuffer.buffer, 1, &copyRegion);
// Execute a transfer barrier to the compute queue, if necessary
if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
{
VkBufferMemoryBarrier buffer_barrier =
{
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
0,
graphics.queueFamilyIndex,
compute.queueFamilyIndex,
2024-01-21 12:52:14 +01:00
storageBuffer.buffer,
0,
2024-01-21 12:52:14 +01:00
storageBuffer.size
};
vkCmdPipelineBarrier(
copyCmd,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0, nullptr,
1, &buffer_barrier,
0, nullptr);
}
2020-04-20 22:13:51 +02:00
vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
stagingBuffer.destroy();
2016-02-16 15:07:25 +01:00
}
2024-01-21 12:52:14 +01:00
// The descriptor pool will be shared between graphics and compute
2016-02-16 15:07:25 +01:00
void setupDescriptorPool()
{
2024-01-21 12:52:14 +01:00
std::vector<VkDescriptorPoolSize> poolSizes = {
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
2016-02-16 15:07:25 +01:00
};
2024-01-21 12:52:14 +01:00
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
2016-02-16 15:07:25 +01:00
}
2024-01-21 12:52:14 +01:00
void prepareGraphics()
2016-02-16 15:07:25 +01:00
{
2024-01-21 12:52:14 +01:00
prepareStorageBuffers();
prepareUniformBuffers();
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
// Descriptor set layout
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0 : Particle color map
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
// Binding 1 : Particle gradient ramp
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));
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
// Descriptor set
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet));
std::vector<VkWriteDescriptorSet> writeDescriptorSets;
// Binding 0 : Particle color map
writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(
graphics.descriptorSet,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
0,
2016-08-17 20:32:05 +02:00
&textures.particle.descriptor));
// Binding 1 : Particle gradient ramp
writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(
graphics.descriptorSet,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1,
2016-08-17 20:32:05 +02:00
&textures.gradient.descriptor));
2016-08-17 20:32:05 +02:00
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
// Pipeline
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_POINT_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_FALSE, VK_FALSE, VK_COMPARE_OP_ALWAYS);
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;
// Vertex Input state
std::vector<VkVertexInputBindingDescription> inputBindings = {
vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX)
2016-02-16 15:07:25 +01:00
};
2024-01-21 12:52:14 +01:00
std::vector<VkVertexInputAttributeDescription> inputAttributes = {
// Location 0 : Position
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, pos)),
// Location 1 : Velocity (used for color gradient lookup)
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, gradientPos)),
};
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(inputBindings.size());
vertexInputState.pVertexBindingDescriptions = inputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(inputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = inputAttributes.data();
2016-02-16 15:07:25 +01:00
shaderStages[0] = loadShader(getShadersPath() + "computeparticles/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "computeparticles/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass, 0);
pipelineCreateInfo.pVertexInputState = &vertexInputState;
2016-02-16 15:07:25 +01:00
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
2016-08-17 20:32:05 +02:00
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
2016-02-16 15:07:25 +01:00
pipelineCreateInfo.pStages = shaderStages.data();
pipelineCreateInfo.renderPass = renderPass;
// Additive blending
blendAttachmentState.colorWriteMask = 0xF;
blendAttachmentState.blendEnable = VK_TRUE;
blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
// 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));
}
2016-02-16 15:07:25 +01:00
void prepareCompute()
{
// Create a compute capable device queue
2016-08-17 20:32:05 +02:00
// The VulkanDevice::createLogicalDevice functions finds a compute capable queue and prefers queue families that only support compute
// Depending on the implementation this may result in different queue family indices for graphics and computes,
// requiring proper synchronization (see the memory and pipeline barriers)
vkGetDeviceQueue(device, compute.queueFamilyIndex, 0, &compute.queue);
2016-02-16 15:07:25 +01:00
// Create compute pipeline
// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
2016-02-16 15:07:25 +01:00
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0 : Particle position storage buffer
vks::initializers::descriptorSetLayoutBinding(
2016-02-16 15:07:25 +01:00
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
VK_SHADER_STAGE_COMPUTE_BIT,
0),
// Binding 1 : Uniform buffer
vks::initializers::descriptorSetLayoutBinding(
2016-02-16 15:07:25 +01:00
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_SHADER_STAGE_COMPUTE_BIT,
1),
};
2024-01-21 12:52:14 +01:00
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout));
2016-02-16 15:07:25 +01:00
2024-01-21 12:52:14 +01:00
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout,1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
2024-01-21 12:52:14 +01:00
std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
2016-02-16 15:07:25 +01:00
// Binding 0 : Particle position storage buffer
vks::initializers::writeDescriptorSet(
compute.descriptorSet,
2016-02-16 15:07:25 +01:00
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
0,
2024-01-21 12:52:14 +01:00
&storageBuffer.descriptor),
2016-02-16 15:07:25 +01:00
// Binding 1 : Uniform buffer
vks::initializers::writeDescriptorSet(
compute.descriptorSet,
2016-02-16 15:07:25 +01:00
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1,
&compute.uniformBuffer.descriptor)
2016-02-16 15:07:25 +01:00
};
2016-08-17 20:32:05 +02:00
vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL);
2016-02-16 15:07:25 +01:00
// Create pipeline
2024-01-21 12:52:14 +01:00
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeparticles/particle.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.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 = compute.queueFamilyIndex;
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
2016-08-17 20:32:05 +02:00
// Create a command buffer for compute operations
compute.commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool);
// Semaphore for compute & graphics sync
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
2016-08-17 20:32:05 +02:00
// Build a single command buffer containing the compute dispatch commands
buildComputeCommandBuffer();
2016-02-16 15:07:25 +01:00
}
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers()
{
// Compute shader uniform buffer block
2024-01-21 12:52:14 +01:00
vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &compute.uniformBuffer, sizeof(Compute::UniformData));
// Map for host access
VK_CHECK_RESULT(compute.uniformBuffer.map());
2016-02-16 15:07:25 +01:00
updateUniformBuffers();
}
void updateUniformBuffers()
{
2024-01-21 12:52:14 +01:00
compute.uniformData.deltaT = paused ? 0.0f : frameTimer * 2.5f;
if (!attachToCursor)
{
2024-01-21 12:52:14 +01:00
compute.uniformData.destX = sin(glm::radians(timer * 360.0f)) * 0.75f;
compute.uniformData.destY = 0.0f;
}
else
{
float normalizedMx = (mousePos.x - static_cast<float>(width / 2)) / static_cast<float>(width / 2);
float normalizedMy = (mousePos.y - static_cast<float>(height / 2)) / static_cast<float>(height / 2);
2024-01-21 12:52:14 +01:00
compute.uniformData.destX = normalizedMx;
compute.uniformData.destY = normalizedMy;
}
2024-01-21 12:52:14 +01:00
memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData));
2016-02-16 15:07:25 +01:00
}
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 };
2016-08-17 20:32:05 +02:00
// 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();
}
2016-02-16 15:07:25 +01:00
void prepare()
{
2016-02-16 15:07:25 +01:00
VulkanExampleBase::prepare();
// We will be using the queue family indices to check if graphics and compute queue families differ
// If that's the case, we need additional barriers for acquiring and releasing resources
graphics.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
compute.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
loadAssets();
2016-02-16 15:07:25 +01:00
setupDescriptorPool();
prepareGraphics();
2016-02-16 15:07:25 +01:00
prepareCompute();
buildCommandBuffers();
2016-02-16 15:07:25 +01:00
prepared = true;
}
virtual void render()
{
if (!prepared)
return;
draw();
if (!attachToCursor)
2016-02-16 15:07:25 +01:00
{
if (animStart > 0.0f)
{
animStart -= frameTimer * 5.0f;
}
else if (animStart <= 0.0f)
2016-02-16 15:07:25 +01:00
{
timer += frameTimer * 0.04f;
if (timer > 1.f)
timer = 0.f;
2016-02-16 15:07:25 +01:00
}
}
2016-02-16 15:07:25 +01:00
updateUniformBuffers();
}
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
2016-02-16 15:07:25 +01:00
{
if (overlay->header("Settings")) {
overlay->checkBox("Attach attractor to cursor", &attachToCursor);
2016-02-16 15:07:25 +01:00
}
}
};
2016-02-16 15:07:25 +01:00
2020-03-15 11:04:13 -04:00
VULKAN_EXAMPLE_MAIN()