From dec7d2e9f898d3fc016cc415117a35297fcf3e96 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 7 Jan 2024 20:04:18 +0100 Subject: [PATCH] Code cleanup, rework, additional code comments --- examples/computecloth/computecloth.cpp | 474 ++++++++++------------- shaders/glsl/computecloth/cloth.comp | 8 - shaders/glsl/computecloth/cloth.comp.spv | Bin 15432 -> 15204 bytes shaders/glsl/computecloth/cloth.vert | 5 - shaders/hlsl/computecloth/cloth.comp | 9 +- 5 files changed, 206 insertions(+), 290 deletions(-) diff --git a/examples/computecloth/computecloth.cpp b/examples/computecloth/computecloth.cpp index 59176415..7dfc7b45 100644 --- a/examples/computecloth/computecloth.cpp +++ b/examples/computecloth/computecloth.cpp @@ -1,6 +1,9 @@ /* * Vulkan Example - Compute shader cloth simulation * +* A compute shader updates a shader storage buffer that contains particles held together by springs and also does basic +* collision detection against a sphere. This storage buffer is then used as the vertex input for the graphics part of the sample +* * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) @@ -13,81 +16,90 @@ class VulkanExample : public VulkanExampleBase { public: - uint32_t sceneSetup = 0; - uint32_t readSet = 0; - uint32_t indexCount; - bool simulateWind = false; - bool specializedComputeQueue = false; + uint32_t readSet{ 0 }; + uint32_t indexCount{ 0 }; + bool simulateWind{ false }; + // This will be set to true, if the device has a dedicated queue from a compute only queue family + // With such a queue graphics and compute workloads can run in parallel, but this also requires additional barriers (often called "async compute") + // These barriers will release and acquire the resources used in graphics and compute between the different queue families + bool dedicatedComputeQueue{ false }; vks::Texture2D textureCloth; vkglTF::Model modelSphere; - // Resources for the graphics part of the example - struct { - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - struct Pipelines { - VkPipeline cloth; - VkPipeline sphere; - } pipelines; - vks::Buffer indices; - vks::Buffer uniformBuffer; - struct graphicsUBO { - glm::mat4 projection; - glm::mat4 view; - glm::vec4 lightPos = glm::vec4(-2.0f, 4.0f, -2.0f, 1.0f); - } ubo; - } graphics; - - // Resources for the compute part of the example - struct { - struct StorageBuffers { - vks::Buffer input; - vks::Buffer output; - } storageBuffers; - struct Semaphores { - VkSemaphore ready{ 0L }; - VkSemaphore complete{ 0L }; - } semaphores; - vks::Buffer uniformBuffer; - VkQueue queue; - VkCommandPool commandPool; - std::array commandBuffers; - VkDescriptorSetLayout descriptorSetLayout; - std::array descriptorSets; - VkPipelineLayout pipelineLayout; - VkPipeline pipeline; - struct computeUBO { - float deltaT = 0.0f; - float particleMass = 0.1f; - float springStiffness = 2000.0f; - float damping = 0.25f; - float restDistH; - float restDistV; - float restDistD; - float sphereRadius = 1.0f; - glm::vec4 spherePos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - glm::vec4 gravity = glm::vec4(0.0f, 9.8f, 0.0f, 0.0f); - glm::ivec2 particleCount; - } ubo; - } compute; - - // SSBO cloth grid particle declaration + // The cloth is made from a grid of particles struct Particle { glm::vec4 pos; glm::vec4 vel; glm::vec4 uv; glm::vec4 normal; - float pinned; - glm::vec3 _pad0; }; + // Cloth definition parameters struct Cloth { - glm::uvec2 gridsize = glm::uvec2(60, 60); - glm::vec2 size = glm::vec2(5.0f); + glm::uvec2 gridsize{ 60, 60 }; + glm::vec2 size{ 5.0f, 5.0f }; } cloth; + // We put the resource "types" into structs to make this sample easier to understand + + // We use two buffers for our cloth simulation: One with the input cloth data and one for outputting updated values + // The compute pipeline will update the output buffer, and the graphics pipeline will it as a vertex buffer + struct StorageBuffers { + vks::Buffer input; + vks::Buffer output; + } storageBuffers; + + // Resources for the graphics part of the example + struct Graphics { + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; + struct Pipelines { + VkPipeline cloth{ VK_NULL_HANDLE }; + VkPipeline sphere{ VK_NULL_HANDLE }; + } pipelines; + // The vertices will be stored in the shader storage buffers, so we only need an index buffer in this structure + vks::Buffer indices; + struct UniformData { + glm::mat4 projection; + glm::mat4 view; + glm::vec4 lightPos{ -2.0f, 4.0f, -2.0f, 1.0f }; + } uniformData; + vks::Buffer uniformBuffer; + } graphics; + + // Resources for the compute part of the example + struct Compute { + struct Semaphores { + VkSemaphore ready{ VK_NULL_HANDLE }; + VkSemaphore complete{ VK_NULL_HANDLE }; + } semaphores; + VkQueue queue{ VK_NULL_HANDLE }; + VkCommandPool commandPool{ VK_NULL_HANDLE }; + std::array commandBuffers{}; + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; + std::array descriptorSets{ VK_NULL_HANDLE }; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; + VkPipeline pipeline{ VK_NULL_HANDLE }; + struct UniformData { + float deltaT{ 0.0f }; + // These arguments define the spring setup for the cloth piece + // Changing these changes how the cloth reacts + float particleMass{ 0.1f }; + float springStiffness{ 2000.0f }; + float damping{ 0.25f }; + float restDistH{ 0 }; + float restDistV{ 0 }; + float restDistD{ 0 }; + float sphereRadius{ 1.0f }; + glm::vec4 spherePos{ 0.0f, 0.0f, 0.0f, 0.0f }; + glm::vec4 gravity{ 0.0f, 9.8f, 0.0f, 0.0f }; + glm::ivec2 particleCount{ 0 }; + } uniformData; + vks::Buffer uniformBuffer; + } compute; + VulkanExample() : VulkanExampleBase() { title = "Compute shader cloth simulation"; @@ -99,25 +111,29 @@ public: ~VulkanExample() { - // Graphics - graphics.indices.destroy(); - graphics.uniformBuffer.destroy(); - vkDestroyPipeline(device, graphics.pipelines.cloth, nullptr); - vkDestroyPipeline(device, graphics.pipelines.sphere, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - textureCloth.destroy(); + if (device) { + // Graphics + graphics.indices.destroy(); + graphics.uniformBuffer.destroy(); + vkDestroyPipeline(device, graphics.pipelines.cloth, nullptr); + vkDestroyPipeline(device, graphics.pipelines.sphere, nullptr); + vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); + textureCloth.destroy(); - // Compute - compute.storageBuffers.input.destroy(); - compute.storageBuffers.output.destroy(); - compute.uniformBuffer.destroy(); - vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); - vkDestroyPipeline(device, compute.pipeline, nullptr); - vkDestroySemaphore(device, compute.semaphores.ready, nullptr); - vkDestroySemaphore(device, compute.semaphores.complete, nullptr); - vkDestroyCommandPool(device, compute.commandPool, nullptr); + // Compute + compute.uniformBuffer.destroy(); + vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); + vkDestroyPipeline(device, compute.pipeline, nullptr); + vkDestroySemaphore(device, compute.semaphores.ready, nullptr); + vkDestroySemaphore(device, compute.semaphores.complete, nullptr); + vkDestroyCommandPool(device, compute.commandPool, nullptr); + + // SSBOs + storageBuffers.input.destroy(); + storageBuffers.output.destroy(); + } } // Enable physical device features required for this example @@ -137,7 +153,7 @@ public: void addGraphicsToComputeBarriers(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) { - if (specializedComputeQueue) { + if (dedicatedComputeQueue) { VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); bufferBarrier.srcAccessMask = srcAccessMask; bufferBarrier.dstAccessMask = dstAccessMask; @@ -146,9 +162,9 @@ public: bufferBarrier.size = VK_WHOLE_SIZE; std::vector bufferBarriers; - bufferBarrier.buffer = compute.storageBuffers.input.buffer; + bufferBarrier.buffer = storageBuffers.input.buffer; bufferBarriers.push_back(bufferBarrier); - bufferBarrier.buffer = compute.storageBuffers.output.buffer; + bufferBarrier.buffer = storageBuffers.output.buffer; bufferBarriers.push_back(bufferBarrier); vkCmdPipelineBarrier(commandBuffer, srcStageMask, @@ -169,9 +185,9 @@ public: bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.size = VK_WHOLE_SIZE; std::vector bufferBarriers; - bufferBarrier.buffer = compute.storageBuffers.input.buffer; + bufferBarrier.buffer = storageBuffers.input.buffer; bufferBarriers.push_back(bufferBarrier); - bufferBarrier.buffer = compute.storageBuffers.output.buffer; + bufferBarrier.buffer = storageBuffers.output.buffer; bufferBarriers.push_back(bufferBarrier); vkCmdPipelineBarrier( commandBuffer, @@ -185,7 +201,7 @@ public: void addComputeToGraphicsBarriers(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) { - if (specializedComputeQueue) { + if (dedicatedComputeQueue) { VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); bufferBarrier.srcAccessMask = srcAccessMask; bufferBarrier.dstAccessMask = dstAccessMask; @@ -193,9 +209,9 @@ public: bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; bufferBarrier.size = VK_WHOLE_SIZE; std::vector bufferBarriers; - bufferBarrier.buffer = compute.storageBuffers.input.buffer; + bufferBarrier.buffer = storageBuffers.input.buffer; bufferBarriers.push_back(bufferBarrier); - bufferBarrier.buffer = compute.storageBuffers.output.buffer; + bufferBarrier.buffer = storageBuffers.output.buffer; bufferBarriers.push_back(bufferBarrier); vkCmdPipelineBarrier( commandBuffer, @@ -248,17 +264,15 @@ public: VkDeviceSize offsets[1] = { 0 }; // Render sphere - if (sceneSetup == 0) { - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.sphere); - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL); - modelSphere.draw(drawCmdBuffers[i]); - } + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.sphere); + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL); + modelSphere.draw(drawCmdBuffers[i]); // Render cloth vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.cloth); vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL); vkCmdBindIndexBuffer(drawCmdBuffers[i], graphics.indices.buffer, 0, VK_INDEX_TYPE_UINT32); - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &compute.storageBuffers.output.buffer, offsets); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &storageBuffers.output.buffer, offsets); vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0); drawUI(drawCmdBuffers[i]); @@ -273,7 +287,6 @@ public: } - // todo: check barriers (validation, separate compute queue) void buildComputeCommandBuffer() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); @@ -317,46 +330,24 @@ public: } } - // Setup and fill the compute shader storage buffers containing the particles + // Setup and fill the shader storage buffers containing the particles + // These buffers are used as shader storage buffers in the compute shader (to update them) and as vertex input in the vertex shader (to display them) void prepareStorageBuffers() { - std::vector particleBuffer(cloth.gridsize.x * cloth.gridsize.y); + std::vector particleBuffer(cloth.gridsize.x * cloth.gridsize.y); - float dx = cloth.size.x / (cloth.gridsize.x - 1); - float dy = cloth.size.y / (cloth.gridsize.y - 1); + float dx = cloth.size.x / (cloth.gridsize.x - 1); + float dy = cloth.size.y / (cloth.gridsize.y - 1); float du = 1.0f / (cloth.gridsize.x - 1); float dv = 1.0f / (cloth.gridsize.y - 1); - switch (sceneSetup) { - case 0 : - { - // Horz. cloth falls onto sphere - glm::mat4 transM = glm::translate(glm::mat4(1.0f), glm::vec3(- cloth.size.x / 2.0f, -2.0f, - cloth.size.y / 2.0f)); - for (uint32_t i = 0; i < cloth.gridsize.y; i++) { - for (uint32_t j = 0; j < cloth.gridsize.x; j++) { - particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, 0.0f, dy * i, 1.0f); - particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f); - particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(1.0f - du * i, dv * j, 0.0f, 0.0f); - } - } - break; - } - case 1: - { - // Vert. Pinned cloth - glm::mat4 transM = glm::translate(glm::mat4(1.0f), glm::vec3(- cloth.size.x / 2.0f, - cloth.size.y / 2.0f, 0.0f)); - for (uint32_t i = 0; i < cloth.gridsize.y; i++) { - for (uint32_t j = 0; j < cloth.gridsize.x; j++) { - particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, dy * i, 0.0f, 1.0f); - particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f); - particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(du * j, dv * i, 0.0f, 0.0f); - // Pin some particles - particleBuffer[i + j * cloth.gridsize.y].pinned = (i == 0) && ((j == 0) || (j == cloth.gridsize.x / 3) || (j == cloth.gridsize.x - cloth.gridsize.x / 3) || (j == cloth.gridsize.x - 1)); - // Remove sphere - compute.ubo.spherePos.z = -10.0f; - } - } - break; + // Set up a flat cloth that falls onto sphere + glm::mat4 transM = glm::translate(glm::mat4(1.0f), glm::vec3(-cloth.size.x / 2.0f, -2.0f, -cloth.size.y / 2.0f)); + for (uint32_t i = 0; i < cloth.gridsize.y; i++) { + for (uint32_t j = 0; j < cloth.gridsize.x; j++) { + particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, 0.0f, dy * i, 1.0f); + particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f); + particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(1.0f - du * i, dv * j, 0.0f, 0.0f); } } @@ -374,23 +365,24 @@ public: storageBufferSize, particleBuffer.data()); + // SSBOs will be used both as storage buffers (compute) and vertex buffers (graphics) vulkanDevice->createBuffer( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.input, + &storageBuffers.input, storageBufferSize); vulkanDevice->createBuffer( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.output, + &storageBuffers.output, storageBufferSize); // Copy from staging buffer VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkBufferCopy copyRegion = {}; copyRegion.size = storageBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.output.buffer, 1, ©Region); + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, storageBuffers.output.buffer, 1, ©Region); // Add an initial release barrier to the graphics queue, // so that when the compute command buffer executes for the first time // it doesn't complain about a lack of a corresponding "release" to its "acquire" @@ -401,10 +393,10 @@ public: // Indices std::vector indices; - for (uint32_t y = 0; y < cloth.gridsize.y - 1; y++) { - for (uint32_t x = 0; x < cloth.gridsize.x; x++) { - indices.push_back((y + 1) * cloth.gridsize.x + x); - indices.push_back((y)* cloth.gridsize.x + x); + for (uint32_t y = 0; y < cloth.gridsize.y - 1; y++) { + for (uint32_t x = 0; x < cloth.gridsize.x; x++) { + indices.push_back((y + 1) * cloth.gridsize.x + x); + indices.push_back((y)*cloth.gridsize.x + x); } // Primitive restart (signaled by special value 0xFFFFFFFF) indices.push_back(0xFFFFFFFF); @@ -435,93 +427,67 @@ public: stagingBuffer.destroy(); } - void setupDescriptorPool() + // Prepare the resources used for the graphics part of the sample + void prepareGraphics() { + // Uniform buffer for passing data to the vertex shader + 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_CHECK_RESULT(graphics.uniformBuffer.map()); + + // Descriptor pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2) }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); - + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - void setupLayoutsAndDescriptors() - { - // Set layout + // Descriptor layout std::vector 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); + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = - vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); + // Decscriptor set + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor), vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureCloth.descriptor) }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - void preparePipelines() - { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_TRUE); + // Layout + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - 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 dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0); + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_TRUE); + 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); // Rendering pipeline - std::array shaderStages; + std::array shaderStages; shaderStages[0] = loadShader(getShadersPath() + "computecloth/cloth.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "computecloth/cloth.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass); - // Input attributes - - // Binding description + // Vertex Input std::vector inputBindings = { vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) }; - - // Attribute descriptions + // Attribute descriptions based on the particles of the cloth std::vector inputAttributes = { vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Particle, pos)), vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, uv)), @@ -557,13 +523,29 @@ public: shaderStages[0] = loadShader(getShadersPath() + "computecloth/sphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "computecloth/sphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.sphere)); + + buildCommandBuffers(); } + // Prepare the resources used for the compute part of the sample void prepareCompute() { // Create a compute capable device queue vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); + // Uniform buffer for passing data to the compute shader + 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)); + VK_CHECK_RESULT(compute.uniformBuffer.map()); + + // Set some initial values + float dx = cloth.size.x / (cloth.gridsize.x - 1); + float dy = cloth.size.y / (cloth.gridsize.y - 1); + + compute.uniformData.restDistH = dx; + compute.uniformData.restDistV = dy; + compute.uniformData.restDistD = sqrtf(dx * dx + dy * dy); + compute.uniformData.particleCount = cloth.gridsize; + // Create compute pipeline std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 0), @@ -571,36 +553,30 @@ public: vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2), }; - 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 pipelineLayoutCreateInfo = - vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); // Push constants used to pass some parameters - VkPushConstantRange pushConstantRange = - vks::initializers::pushConstantRange(VK_SHADER_STAGE_COMPUTE_BIT, sizeof(uint32_t), 0); + VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_COMPUTE_BIT, sizeof(uint32_t), 0); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); // Create two descriptor sets with input and output buffers switched VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[0])); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[1])); std::vector computeWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.input.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.output.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &storageBuffers.input.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &storageBuffers.output.descriptor), vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.output.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.input.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &storageBuffers.output.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &storageBuffers.input.descriptor), vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor) }; @@ -619,9 +595,7 @@ public: 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, 2); - + VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 2); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffers[0])); // Semaphores for graphics / compute synchronization @@ -633,80 +607,50 @@ public: buildComputeCommandBuffer(); } - // Prepare and initialize uniform buffer containing shader uniforms - void prepareUniformBuffers() - { - // Compute shader uniform buffer block - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &compute.uniformBuffer, - sizeof(compute.ubo)); - VK_CHECK_RESULT(compute.uniformBuffer.map()); - - // Initial values - float dx = cloth.size.x / (cloth.gridsize.x - 1); - float dy = cloth.size.y / (cloth.gridsize.y - 1); - - compute.ubo.restDistH = dx; - compute.ubo.restDistV = dy; - compute.ubo.restDistD = sqrtf(dx * dx + dy * dy); - compute.ubo.particleCount = cloth.gridsize; - - updateComputeUBO(); - - // Vertex shader uniform buffer block - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &graphics.uniformBuffer, - sizeof(graphics.ubo)); - VK_CHECK_RESULT(graphics.uniformBuffer.map()); - - updateGraphicsUBO(); - } - void updateComputeUBO() { if (!paused) { // SRS - Clamp frameTimer to max 20ms refresh period (e.g. if blocked on resize), otherwise image breakup can occur - compute.ubo.deltaT = fmin(frameTimer, 0.02f) * 0.0025f; + compute.uniformData.deltaT = fmin(frameTimer, 0.02f) * 0.0025f; if (simulateWind) { std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr)); std::uniform_real_distribution rd(1.0f, 12.0f); - compute.ubo.gravity.x = cos(glm::radians(-timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); - compute.ubo.gravity.z = sin(glm::radians(timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); + compute.uniformData.gravity.x = cos(glm::radians(-timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); + compute.uniformData.gravity.z = sin(glm::radians(timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); } else { - compute.ubo.gravity.x = 0.0f; - compute.ubo.gravity.z = 0.0f; + compute.uniformData.gravity.x = 0.0f; + compute.uniformData.gravity.z = 0.0f; } } else { - compute.ubo.deltaT = 0.0f; + compute.uniformData.deltaT = 0.0f; } - memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo)); + memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData)); } void updateGraphicsUBO() { - graphics.ubo.projection = camera.matrices.perspective; - graphics.ubo.view = camera.matrices.view; - memcpy(graphics.uniformBuffer.mapped, &graphics.ubo, sizeof(graphics.ubo)); + graphics.uniformData.projection = camera.matrices.perspective; + graphics.uniformData.view = camera.matrices.view; + memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData)); } void draw() { + // As we use both graphics and compute, frame submission is a bit more involved + // We'll be using semaphores to synchronize between the compute shader updating the cloth and the graphics pipeline drawing it + static bool firstDraw = true; VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo(); - // FIXME find a better way to do this (without using fences, which is much slower) VkPipelineStageFlags computeWaitDstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; if (!firstDraw) { computeSubmitInfo.waitSemaphoreCount = 1; computeSubmitInfo.pWaitSemaphores = &compute.semaphores.ready; computeSubmitInfo.pWaitDstStageMask = &computeWaitDstStageMask; - } else { + } + else { firstDraw = false; } computeSubmitInfo.signalSemaphoreCount = 1; @@ -714,7 +658,7 @@ public: computeSubmitInfo.commandBufferCount = 1; computeSubmitInfo.pCommandBuffers = &compute.commandBuffers[readSet]; - VK_CHECK_RESULT( vkQueueSubmit( compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE) ); + VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE)); // Submit graphics commands VulkanExampleBase::prepareFrame(); @@ -745,19 +689,16 @@ public: { VulkanExampleBase::prepare(); // Make sure the code works properly both with different queues families for graphics and compute and the same queue family + // You can use DEBUG_FORCE_SHARED_GRAPHICS_COMPUTE_QUEUE preprocessor define to force graphics and compute from the same queue family #ifdef DEBUG_FORCE_SHARED_GRAPHICS_COMPUTE_QUEUE vulkanDevice->queueFamilyIndices.compute = vulkanDevice->queueFamilyIndices.graphics; #endif // Check whether the compute queue family is distinct from the graphics queue family - specializedComputeQueue = vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute; + dedicatedComputeQueue = vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute; loadAssets(); prepareStorageBuffers(); - prepareUniformBuffers(); - setupDescriptorPool(); - setupLayoutsAndDescriptors(); - preparePipelines(); + prepareGraphics(); prepareCompute(); - buildCommandBuffers(); prepared = true; } @@ -765,17 +706,12 @@ public: { if (!prepared) return; - draw(); - - updateComputeUBO(); - } - - virtual void viewChanged() - { updateGraphicsUBO(); + updateComputeUBO(); + draw(); } - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) + virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay) { if (overlay->header("Settings")) { overlay->checkBox("Simulate wind", &simulateWind); diff --git a/shaders/glsl/computecloth/cloth.comp b/shaders/glsl/computecloth/cloth.comp index 8c0ad682..cda4fa20 100644 --- a/shaders/glsl/computecloth/cloth.comp +++ b/shaders/glsl/computecloth/cloth.comp @@ -5,7 +5,6 @@ struct Particle { vec4 vel; vec4 uv; vec4 normal; - float pinned; }; layout(std430, binding = 0) buffer ParticleIn { @@ -53,13 +52,6 @@ void main() if (index > params.particleCount.x * params.particleCount.y) return; - // Pinned? - if (particleIn[index].pinned == 1.0) { - particleOut[index].pos = particleOut[index].pos; - particleOut[index].vel = vec4(0.0); - return; - } - // Initial force from gravity vec3 force = params.gravity.xyz * params.particleMass; diff --git a/shaders/glsl/computecloth/cloth.comp.spv b/shaders/glsl/computecloth/cloth.comp.spv index 959d720626a0bab3df57391de6a422af7d1dae22..bb50530219a936788dfcb6ad42024a22a8b3bf10 100644 GIT binary patch literal 15204 zcmaKy37nTz^~QfNY$~ECiYtO>q2MlVxUdQ+2#Sh3jxgZpFf-1AYl%ytR<`eHMW$tH zmR4$;+P>JXmf61Vipu|;E4 z$bl;Mmh=MBe)G1z*$d)hnK9NX2ivTe$`)d$T`Rv$2f@h$k&XmsO+ zc(nKc@$kl$;GVYLzPataeL$FD_|RyqdQop_?1+BK>?KW)#kN eu4QH{eD#?fTf= z+SAv*vZHNrYj3Y(V$<&d-ZBAEz zrw8RZ?L5G@u4Mtwbt-!MD)FdhF4uIdnBUQLUTep~&UIZYTl?C(Iv47nb1jJW&Q)!f zRQhrBtIb?fZ)tF}jczoaJnZSKio8AE29f1=xLzgQXnXx|UwOyQcTl^lNJRhMInTO~1LO zZ>;IJ)bx96`h7M1ftvnUO@E@MKUveCs_D>F;ai^AtE+@+ogVmsh;=E(Z_XlYv)2?iSbT?>&D`S7&b@^*w@` zmb0yF?O560(c0H`LbI5>KF$4a#ok@au^#S(`@foNY21(A+8p1~cnEx6g&zU01TSEv zyfKr}ZQ&TwkQ-KT*0`MaIpbO?&UY@Ha~$W}7S8uXZd}3nmdNc;aK0mQyA+)J$xSRc z-wrwNcJ}b?kei9j=iH4=pXU>W@5k?zC*z3cHxp_4rem~nJ?k;+S%c+z>XR5x z*PNevE9c7@oBLe~@O(VeZDHjnvAXMvw+HFZL}WbU&7->ZiRk7~PEj|188eBI=1$#l zigwr8p5Aq=sqOgx@$?gsA&7QsM@}Cm@Do=59RL4`Ybwswm!0UH@7x)cZoSA&t@gAo z)E&QTh22A+rZjo}kETz1(aR`mgSO>eio}_WkG`?;7rJ&nHCF9)Au- z_sn9S4z@Y}5PE&}T*iUs(P!TAV9(ZZi|8HaS;W2+tRE9NLF+hYm3~*EIu%~q1h6%$ z?s})uyPlkJsav;lS^o^ME&7FEa~u68ux~~5N5P(R^m#bvxvI-A(b>lOWAw{A5x=Z` zb^SV(ULWN2OWpd9U#_zPopwyt?H|xz+;=_tCcV!)^XgCi2V9$-cexZ%ImZBEf@ce2Fu02W59#?Hxpi4 z#?30mC0^s0-5k?s=(qYDWF{h?*ye)e?_iAY_OXb5$UlRR3+QdnaTeO?|)*y1`Pn;Oq z!NzbkXFG-d0z}Swn#F0G*R@FE(q5ilXJt=ZUFdR(t&GceS-X3i$BPip)U)lb^wdEw zx^-|N;&|(z50Q6#Po*EuO0Lz9j6vMPwX|oSbzp1cs;Yi22Fp1o^&_{5v&k9R=lm}L z*U$e_IQiVg%fR|ue^V}p)8_el7q39%tc~2omsR}TjPWkM9C3YlbGe@0W-hf|iL6J= zr8v2~3T!U(?pzI)D|2}*dUC0~%;hW4wI`Ra1j{M5GMCq&YtI>92Tm?GfYp=B8^HBk zz6ws>^S_>6J-K`}*j&1YYiZ9uuK}02yb)c_ImxA*xm-{08QCY7uLYY+&m`yeIcOr7;O`N=a7+mJcpN#5klTh7)#d3z9S-ctWlzz-o0BFWps6_>ny9^Gc%w0#bd zGjHOo{RMEnCcX$KpF8*^aB^9`3txuUmUrPRV7aA8^7~b=eBOnxfn8tTc;l1()BHr6 zNB?z1pFIP|>#w!=4SJg~Ipz^W&X~lh#czVkTKpDz{LdMVhx<07ExvxI;_`-k7u~hw zQ=i`h%X3Pwi!WejihlL+ImK4i=VR#FQ#U^Zr#_zmtEWDH1U9Be5yy|9 z|1lzOO+8uZsn4IFk3rnSwX|oSpMuN!{298Ob5fsj<}2rIpBnl(xL%*XfRj&s{t_&g z`usK6W-hh;3XwCH;;j7}aJ@c%3n!noe+QOxZGFr-_Nl4ggWX5J9q%62)F0_>`r(*A zAaeR4PEGv@T-MZ|(Fd!kzrbrtP5l)tm-_h|*tO)t{T(b{*3>`HQ&ZZ@ntB>tdur;R zU^&HB*3?tz+LP;N!KtZd!0M@~=fL%v`WKwMb+CzEJvH@j@EF8BTuXcQc^+KW)C=fx z&Ph$lnXjC;ee(VvaJ{AsOg`_@5cI)vFce-}-lZ0>+*0NyFT>E~^DYesyS}`2F;bmo zU1%GD$XOTS_&Ew(ug}fky*I#2AOK&qK$83$r8Iw4*z72SP#>M|_(c`~&EdGy!*OoKg4lI}Vb9=CB z$%h*cmM?34NA%RX_OjM@Lf4*J-x(~Y*zATP)|9{B(ViOI6`Wf4cRlK<_1(b6^eE!^ z5%d$`23Pqm;(?w{SbTJ?s*Wntj~kd2RobT@Y+(Jhk)hs z?j8zuE&0^vVPN^PK94|8eQGc3a|XKh)aQ|4ImKp|`tit`RECF%bzXELRnfEgE%+r=S^sHZwu08M5m0&r=W~YCi(<1t- zAUvn_^zP>xSA*rV#x-C$#b)Ok*Hm?VErk0xPX8~bmrHJ50hSxh4fU?P63n0G@9DKA zXV-!CWdpr)9eX`k-u$S)3M_X6avz+3e|$CAHMRM3{{~Sz66#pWBoFEV%1*8`W1BTiS?^s zImKpI#`-mgGS;u7J4fDFAEK8_tlt2av3?VMuvotZuPw2D8!VT&z5{kG`TWfIE;w;% zFXQ?iy7t8NC|FLh*_ClU22sZKIJ$G>jq4G5Ipgwu`aamaM*jiW*hgd4{sfpm&3}`1 ztoDrkA=t6ep9H&p=KcuGpXOZ0YR|j)W3X#GCi8y+_U>lPPr-7I&ApcMJ;{Bs&t3c( zxPBLZ4kw?#yZr(@Sl)gKugyGJv%dn%`IaS@zebnO-`#!#c71u@jNj4Qd^5ED7LoH_ zi;dGa?)Tubmi~Y~m`{I%*OvYN1eQxJ{Tb|9@~Nf2fKyA_%Ub#?y7tu4-@tN;&91Da zzeAL@^c1>tPlF#1-H-Y8L_ux77#HziF z^ZQ@oyM>>ECd4=g8+?%jGVO0N-Ev>pdQcUf%0b=t-K-iLeRK5kUT=Y( zxsKJIJ3AWe+K$QmEy3lz9)m9D*xYM5?{)5rz4|@$-XHti^Q{Q^Bq! zpLb;%SiZcY`=BRQ?PaX{qH9mA`+?;YTRqnO;mcSLKzELO-VnLOdZ0GeiGK&7%a#5e zj2{2Am;Oyh*B<{40m~`2x_^hlm;N1w?i~5NA#%yx;o$NOIRd?WLuR16hi`~uwP)^; z)!67qp_gyS(de1$SnYX3jsd&2V={jxxO_uqq02coZ-|_4M(&G!?&554{VvXdlh2zm z7d%+4&4bsLH{)2aT={0qN0-l=u>kD)^1d0z(c648v@JyByw_sm^j;qiwkA@?i_qoD znpljUn$TX>#0luyQxi+Ta*C~96HDRCnm7^NIr6!ma`EpZaCtwMp$~RuC&O#Y{X7LM zm$*&^yOw;o11-rImGQSgC-s>)OImhN+%XzPJ zU+mS#(|dpHUC;Z|4K`<4{~~nP_xD?V2lb%qpTG0bmfv~wg0UXi4ZLw9W2@ENpg{Eulj~5kv(mf+F^Y zs5B`m7O){AcI;wTP!Sa=3VPq)z7PNSUVHr`$9bOTOgl4YX5U?CS$F#AMq{1E=*F1F zmFqT|WkO?JltyFy#;9h!@W^A2+@*h@eV4uV-pvN%8!gRtw%L$2BdJGVt6Dp|b?l67 zgswIL8(W#}Tlk;TJO~*{i?ZKH)-WDle{WxB_gRPb^tE-&SiNG;S=x%-XVJceH8mQ& ztU^3ayt{a0V*_wsNB_Y5&i(-)$_UobXtck=TN+!ykDt4wxni-Uy`yWO^|%Jw=%(K_ z_O|v7bhdSM9M#(2Z=3jB_t4so9q3%KqPqjP(L0tq(%!nNmxZH^YHG%oH7x>1cfX>a zQ0SuyeST9PS`!Dvyem8UI*w^=?;NbwG&IMUSA0u*bYwm2fzRq|UEMiw0ou?wo>YldNfzF=p!(5+zO^D9! z_Kx$Ld}QNr>ZR5G0hxP5Qxo^H!f^L_bw}4L+`S!KJ+ywua6h|y`c}1e{h#K3vccUQ z&9#ke90|`{hmm>iubL%ssvFbdAsIZfaWXjlmJALw`>9#dPu+gYhGg)F#!B!Cvyxh0 zukDFRJP0-(+pSe^?Q31tY&W8DRgGU=<2Tm$%{6{Yjo(${_tyCRHU4moKU(9D)%a62 z{&bB$Q{!Ve%rf3_H9o$^cdqf>YkbcdUtHtI)cA4#!&@55;VW39XMHVr=jPNbbyxw_> z<5`?TE&Gz#)EuK+pIS?$WqoSdm)T&~a0In`uoQFrycL<#7}Olg_P!Zq402eVZJr!FA({DbhxrQnHBzAqa|NqP}HT$}j zEvfBq-&qwmhty_PbDAr1+fS~rbGW7%O`ZRvxTYPb)e`%TU}IIc|4!7dU+#Qzb5c$J zuGF@74ClAzQ={q6diH|5XYub1w%C6kYS-$%Oav{Up2CRcWnxcl43k-@`Kf;j*gTXw z)=|`srRG}Y=3==wOW>BQ?P##)C3k#2aVTpJaUSzF^O&D<=Q)MiG1Z($ZjP$yKfS`P zJM*-G*QvPkv}2Z?h}CxftW+y4eLKDs- zw*$3zR!uX1_8~$bqYf40c^<{MoqI>>nV56P%|qX`aN}E9 z;EsP|jr%>4@oew+NVx6&9x1usBjL8czu>lixW*qXxa}V+xb2^+@uv%Jd%s6ApY8n~ zDY@Sx;kF-NaNGMm62IKU}ZaTHiC@Fx&ZV z?T6{RxazlmrCrI`j&lI!Sn8Q?7TEbRj_nS_)Ynw~4g%X(-8GxvYFXdGV70985b$v8 zn~kn7?dBBi60dR0ZMJDNT(^84HXBn>{v|y-PQcZSDNp(^Pfmo;%gb2jD~cVb!3IhDSISKWN?!qhYi*MR7U#+XdK3UdtSa}4*j8&kiI zc^sn$a~yTow6f9?%en9}mOgmSOkcTQ{c!zR-vC%mvt)hF(+k(1c~*nVdCr4p9({?y zb2%T=pLs5*G<8ck&mdfX?&F2v`hC0zO+C4OF<8wzYpz{NZ86vMU4p5ZYvROk8Q2&u zXK!avuff#3d&!~8!HH`PxZJ-h;QABSm0&f^QpU9wuHU&m$Jb!)se9|sGr2LF2iIWc zL3zJkTj{pH8Z6(RksRw6ElEb~Uc9jsQKGy^H?D4z6Im?^D@|@+n!S%Cz z51P9Be+#udXZc>RXXzY{r9bn$4_uz*`{8Q#$yus-mf0igoaG0=o~3(|efuC-J!knL zaLzLAJi8BL`f`>ZskHJeKZ>TFv%D4T`0AeJZPXUeQs2igHP2Fa5eiR2h_}g?5%Zj;Pc>m z4txPkJ@4rk!D{Zc=k^e_#dFj5Ag1QIiF0mW0+;9ZW%zLC_7!w}Ik&HZ)$$I14eVIz zIk&Hab8h;}b9)4?Kj-!hu$pEm&+TEj{_N*B!8x}_!SbBjx4`vt`!<@od;J)-Jm>Zu zu;=C+j-@~Id>34v+xOsV_Q|=ad2ZQT>zv#7!JeCElKuVxSUuXY3z?9eZ~yWB&xKo;&zcaLzLACZYWd)0cPQ=atq*o%8(#SUvB;6JWu4bR)r<&)L{kBdHJqNDm=ksXl z$-6dxHL)^#p8V4ntI-)&A`K*!6bBjd6zZ^tF^J# zoXZw)^}I`4f*oJoyx5xBVqWOm3R5#L#98NLa6Lb#ps8n_+kmr9&p5d<6LUDjlsT;C4tn%z&^yIx~@6}82fY_mP4W=vw&H3R>v!DX(`fM@+> zuJ3@ZFMGNpSS|18Ot53AN81UkUgr8P@Z`GwGS_#7>rbxl23FH7R>^h$7N$QrxCc17 z?%&1a$@RU!^<3W@P2K$T?`HDk`aWQD-8mdff9Ba2T;}?Ia5eiR*VR0)?6-AtXn$}$ z*AGBbPY%rj4|fI!qU%cz9RyY@bLe2WdUEIxu;Z(n7jvjB=7ql5n3{Rf#Qc7?EB?7? z^<1Bark=NWK3FYpamKMuel7qzk9%Ny=WrbhsV%Oupik}8n%bcf!2WgkTU7Z0}vt8Pp2`<;w3Rkmj)}@yAZD8AHUF~q!Wv*vk z9cb#;kh89H1^75>^{nMAuw&_Sp5@ePjxmF_E5YVh=Iw-gPT^<6jV`bHQpoSmx^k*XQd;Q%}DEuzl6jZxCGXw;D}7 z{muj1S3T=IAM9H79bN6~1z>ecv-T)}Eqw`m8Qi_} z9IQPfb^GOvE(MoobQ#<;$v&=uyN`)|Ew~>0h??QuLB!<`n?{We)^J!?)4ks`t$Si zI6BeK7_>Toqs>vHm*}&V*LQP z9_t6u)D!E6z-q>toc%D^G43|My>7ugPutoi`*s_+%*T(zGnc;Pv3dt%_;V)vZ|xr3L47B7JC?Qi7T;Crx6;Nv zyBn_N&u7o}91+9pgL?fGy6W z@3WYi^N2I{=fL%ExzD4iXY4P4)iU;j;Eb*Bijw>sR6Wle=F7t7#UiGIt+F=taxD{yN+~>h9}9)M{DZH^4)AF!b+cABCIK_1}@d ziLNi-WsiZ?64$rDj-?*$+hFxF*6+X*tNt?9@51#b*6)GUG>cUk>-Q1LSbqSwkGiow zLamlqe+VvP{Skb)SbvPJFR}gvtd_Wb3U(~@+|{3f6PNxnuAjs8C$3+B)ijG$8P^jC zWn906+eh8F9;a3_F5iw{fj!soUxST(9A5pu0rOAu-!0o#f7<>QY}@ePfgL}6e-GxL zW?$Rt&ztoJuw&aM{r?E|?xxL?U^Uz3UaR?@-ftd?AQ9_(1^$)y*-$tC?|F1-lXpIrJESWUB7mAUk9gff?2g4;(u?}1w4dKp~C z^&j|faXE>;#I=r-Vu@>Auw$v`_p}zUdifrVfG1Y{WvnCN`V;FYu$pFJ`2UG@GgyYiUaoILxP8=fuhnvw zHUgLTdIG$>*Bir~V?JhE{pq_&)i!)0yu8<&!qeBb`g3PD13R{D(ti@Tyw{t<)oh!4 zt>(SXeX*85K<)jp&OP6Pw)LOqTcWAwo^J(C?s+Dz#e2QAI+lAr8LU>`^C@uk-1BX~ zj<4=LpGIx*p6i>6sku&Z*10XX{vJ(7Q_r`-c3`!98@vjfvGr|_sX4aTIDA814KDB6 z40z&oti-zmy1v}C9l>hJ+nHd;QqLQ*6F7ORzs%d6;rf%eyMWa+i+Lf;+3yM|^L975 zebn=Ys3or5!DU=~z=w-#Pjr2WYcH@`;@TVRSn7FK_5n9_)|XiKMN6#u%UJh=>rbrv zgVi)kJ=O!z%UEZ@?W3MIL@lu%2sYNN?;yBZ`Gy<}&-(P2>pKLlKkJ(fR?{r?_02&q z*Ebh#AN9N;YB{@k;PMTb4=>-41#su^4Y95M^gXm{8@>=;z9Ea?>1$j4c|#5ZJGO1o z|8Q{mh8zJ`vu)lGHQ$Wf7wg=`Bf<5%codp?-i*cI;c{&Wy1u*_OTlX8n{hN;J#WS_ zV8>VY%{Y$Q;+vuGSWL}(EjCW?_3>bHB6)lQT&>KB6XD4T{bf#^1lOOOI2o*_S?W1) z3VN9n%i#7=&;3-(`c4JAzUHFJoN+w~u=6r&?k?3tYy!5)Bwn#B~nXvDEYKbb%9> z{xYsraQ%s^8?2@c7grB@8CNgdKI*yGYPn12g3Ei|2QTk+KRkESw))d|plTaF2ruvT zYIyqER)6mGd0@x3P5Pe?F7NdPa5dZJUaNVpb6>3G)2O{a){f`>xe)A`W&Dfaj_==| z{C(zPxa;$83;Oc6yGy|OEYU8lG}}kJtkP_nzi+OA*x&a0+wtX`*TR> params.particleCount.x * params.particleCount.y) return; - // Pinned? - if (particleIn[index].pinned == 1.0) { - particleOut[index].pos = particleOut[index].pos; - particleOut[index].vel = float4(0, 0, 0, 0); - return; - } - // Initial force from gravity float3 force = params.gravity.xyz * params.particleMass;