diff --git a/README.md b/README.md index cc78f441..fb532fad 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,10 @@ Generating a complete mip-chain at runtime instead of loading it from a file, by Capturing and saving an image after a scene has been rendered using blits to copy the last swapchain image from optimal device to host local linear memory, so that it can be stored into a ppm image. +#### [08 - Order Independent Transparency](examples/oit) + +Implements order independent transparency based on the linked list. This example use the storage buffer, the image load and store operations and atomic operations. + ### Performance #### [01 - Multi threaded command buffer generation](examples/multithreading/) diff --git a/data/shaders/glsl/oit/color.frag b/data/shaders/glsl/oit/color.frag new file mode 100644 index 00000000..06a53080 --- /dev/null +++ b/data/shaders/glsl/oit/color.frag @@ -0,0 +1,56 @@ +#version 450 + +#define MAX_FRAGMENT_COUNT 128 + +struct Node +{ + vec4 color; + float depth; + uint next; +}; + +layout (location = 0) out vec4 outFragColor; + +layout (set = 0, binding = 0, r32ui) uniform uimage2D headIndexImage; + +layout (set = 0, binding = 1) buffer LinkedListSBO +{ + Node nodes[]; +}; + +void main() +{ + Node fragments[MAX_FRAGMENT_COUNT]; + int count = 0; + + uint nodeIdx = imageLoad(headIndexImage, ivec2(gl_FragCoord.xy)).r; + + while (nodeIdx != 0xffffffff && count < MAX_FRAGMENT_COUNT) + { + fragments[count] = nodes[nodeIdx]; + nodeIdx = fragments[count].next; + ++count; + } + + // Do the insertion sort + for (uint i = 1; i < count; ++i) + { + Node insert = fragments[i]; + uint j = i; + while (j > 0 && insert.depth > fragments[j - 1].depth) + { + fragments[j] = fragments[j-1]; + --j; + } + fragments[j] = insert; + } + + // Do blending + vec4 color = vec4(0.025, 0.025, 0.025, 1.0f); + for (int i = 0; i < count; ++i) + { + color = mix(color, fragments[i].color, fragments[i].color.a); + } + + outFragColor = color; +} \ No newline at end of file diff --git a/data/shaders/glsl/oit/color.frag.spv b/data/shaders/glsl/oit/color.frag.spv new file mode 100644 index 00000000..8b5d3670 Binary files /dev/null and b/data/shaders/glsl/oit/color.frag.spv differ diff --git a/data/shaders/glsl/oit/color.vert b/data/shaders/glsl/oit/color.vert new file mode 100644 index 00000000..ccfa5756 --- /dev/null +++ b/data/shaders/glsl/oit/color.vert @@ -0,0 +1,7 @@ +#version 450 + +void main() +{ + vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(uv * 2.0f + -1.0f, 0.0f, 1.0f); +} \ No newline at end of file diff --git a/data/shaders/glsl/oit/color.vert.spv b/data/shaders/glsl/oit/color.vert.spv new file mode 100644 index 00000000..b24b3211 Binary files /dev/null and b/data/shaders/glsl/oit/color.vert.spv differ diff --git a/data/shaders/glsl/oit/geometry.frag b/data/shaders/glsl/oit/geometry.frag new file mode 100644 index 00000000..a8fff040 --- /dev/null +++ b/data/shaders/glsl/oit/geometry.frag @@ -0,0 +1,49 @@ +#version 450 + +#define MAX_FRAGMENT_COUNT 75 + +layout (early_fragment_tests) in; + +struct Node +{ + vec4 color; + float depth; + uint next; +}; + +layout (set = 0, binding = 1) uniform ObjectUBO +{ + mat4 model; + vec4 color; +} objectUBO; + +layout (set = 0, binding = 2) buffer GeometrySBO +{ + uint count; + uint maxNodeCount; +}; + +layout (set = 0, binding = 3, r32ui) uniform uimage2D headIndexImage; + +layout (set = 0, binding = 4) buffer LinkedListSBO +{ + Node nodes[]; +}; + +void main() +{ + // Increase the node count + uint nodeIdx = atomicAdd(count, 1); + + // Check LinkedListSBO is full + if (nodeIdx < maxNodeCount) + { + // Exchange new head index and previous head index + uint prevHeadIdx = imageAtomicExchange(headIndexImage, ivec2(gl_FragCoord.xy), nodeIdx); + + // Store node data + nodes[nodeIdx].color = objectUBO.color; + nodes[nodeIdx].depth = gl_FragCoord.z; + nodes[nodeIdx].next = prevHeadIdx; + } +} \ No newline at end of file diff --git a/data/shaders/glsl/oit/geometry.frag.spv b/data/shaders/glsl/oit/geometry.frag.spv new file mode 100644 index 00000000..ddc2d691 Binary files /dev/null and b/data/shaders/glsl/oit/geometry.frag.spv differ diff --git a/data/shaders/glsl/oit/geometry.vert b/data/shaders/glsl/oit/geometry.vert new file mode 100644 index 00000000..4ed12046 --- /dev/null +++ b/data/shaders/glsl/oit/geometry.vert @@ -0,0 +1,21 @@ +#version 450 + +layout (location = 0) in vec3 inPos; + +layout (set = 0, binding = 0) uniform RenderPassUBO +{ + mat4 projection; + mat4 view; +} renderPassUBO; + +layout (set = 0, binding = 1) uniform ObjectUBO +{ + mat4 model; + vec4 color; +} objectUBO; + +void main() +{ + mat4 PVM = renderPassUBO.projection * renderPassUBO.view * objectUBO.model; + gl_Position = PVM * vec4(inPos, 1.0); +} diff --git a/data/shaders/glsl/oit/geometry.vert.spv b/data/shaders/glsl/oit/geometry.vert.spv new file mode 100644 index 00000000..a7d75996 Binary files /dev/null and b/data/shaders/glsl/oit/geometry.vert.spv differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2dfed453..f8311364 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -87,6 +87,7 @@ set(EXAMPLES negativeviewportheight occlusionquery offscreen + oit parallaxmapping particlefire pbrbasic diff --git a/examples/oit/oit.cpp b/examples/oit/oit.cpp new file mode 100644 index 00000000..2e0742dc --- /dev/null +++ b/examples/oit/oit.cpp @@ -0,0 +1,684 @@ +/* +* Vulkan Example - Order Independent Transparency rendering +* +* Note: Requires the separate asset pack (see data/README.md) +* +* Copyright by Sascha Willems - www.saschawillems.de +* Copyright by Daemyung Jang - dm86.jang@gmail.com +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include "vulkanexamplebase.h" +#include "VulkanglTFModel.h" + +#define ENABLE_VALIDATION false +#define SPHERE_COUNT 5 * 5 * 5 +#define CUBE_COUNT 2 +#define NODE_COUNT 20 + +class VulkanExample : public VulkanExampleBase +{ +public: + struct { + vkglTF::Model sphere; + vkglTF::Model cube; + } models; + + struct { + vks::Buffer renderPass; + vks::Buffer objects; + } uniformBuffers; + + struct Node { + glm::vec4 color; + float depth; + uint32_t next; + }; + + struct { + uint32_t count; + uint32_t maxNodeCount; + } geometrySBO; + + struct GeometryPass { + VkRenderPass renderPass; + VkFramebuffer framebuffer; + vks::Buffer geometry; + vks::Texture headIndex; + vks::Buffer linkedList; + } geometryPass; + + struct { + glm::mat4 projection; + glm::mat4 view; + } renderPassUBO; + + struct { + glm::mat4 model; + glm::vec4 color; + } objectUBO; + + struct { + VkDescriptorSetLayout geometry; + VkDescriptorSetLayout color; + } descriptorSetLayouts; + + struct { + VkPipelineLayout geometry; + VkPipelineLayout color; + } pipelineLayouts; + + struct { + VkPipeline geometry; + VkPipeline color; + } pipelines; + + struct { + VkDescriptorSet geometry; + VkDescriptorSet color; + } descriptorSets; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Order independent transparency rendering"; + + camera.type = Camera::CameraType::lookat; + camera.setPosition(glm::vec3(0.0f, 0.0f, -6.0f)); + camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f)); + camera.setPerspective(60.0f, (float) width / (float) height, 0.1f, 256.0f); + + settings.validation = ENABLE_VALIDATION; + settings.overlay = true; + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipelines.geometry, nullptr); + vkDestroyPipeline(device, pipelines.color, nullptr); + + vkDestroyPipelineLayout(device, pipelineLayouts.geometry, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.color, nullptr); + + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.geometry, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.color, nullptr); + + destroyGeometryPass(); + + uniformBuffers.renderPass.destroy(); + uniformBuffers.objects.destroy(); + } + + void getEnabledFeatures() override + { + if (deviceFeatures.fragmentStoresAndAtomics) + enabledFeatures.fragmentStoresAndAtomics = VK_TRUE; + }; + + void prepare() override + { + VulkanExampleBase::prepare(); + loadAssets(); + prepareUniformBuffers(); + prepareGeometryPass(); + setupDescriptorSetLayout(); + preparePipelines(); + setupDescriptorPool(); + setupDescriptorSets(); + buildCommandBuffers(); + updateUniformBuffers(); + prepared = true; + } + + void render() override + { + if (!prepared) + return; + draw(); + } + + void windowResized() override + { + destroyGeometryPass(); + prepareGeometryPass(); + vkResetDescriptorPool(device, descriptorPool, 0); + setupDescriptorSets(); + + resized = false; + buildCommandBuffers(); + } + + void viewChanged() override + { + updateUniformBuffers(); + } + +private: + void loadAssets() + { + const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY; + models.sphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags); + models.cube.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags); + } + + void prepareUniformBuffers() + { + // Create an uniform buffer for a render pass. + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.renderPass, + sizeof(renderPassUBO))); + + VK_CHECK_RESULT(uniformBuffers.renderPass.map()); + + // This example has many object and the information of objects will be stored in one buffer. + // This buffer will be used for the uniform buffer dynamic. + // So we need to calculate a object uniform buffer size based on minUniformBufferOffsetAlignment. + objectUniformBufferSize = + (sizeof(objectUBO) + deviceProperties.limits.minUniformBufferOffsetAlignment) & ~(deviceProperties.limits.minUniformBufferOffsetAlignment - 1); + + // Create an uniform buffer for objects. + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &uniformBuffers.objects, + objectUniformBufferSize * (SPHERE_COUNT + CUBE_COUNT))); + + VK_CHECK_RESULT(uniformBuffers.objects.map()); + + // Set up the scene. + uint8_t* objectUniformBufferData = static_cast(uniformBuffers.objects.mapped); + assert(SPHERE_COUNT == 5 * 5 * 5); + for (int i = 0; i != 5; i++) + { + for (int j = 0; j != 5; j++) + { + for (int k = 0; k != 5; k++) + { + auto T = glm::translate(glm::mat4(1.0f), glm::vec3(i - 2, j - 2, k - 2)); + auto S = glm::scale(glm::mat4(1.0f), glm::vec3(0.3f)); + objectUBO.model = T * S; + objectUBO.color = glm::vec4(1.0f, 0.0f, 0.0f, 0.5f); + memcpy(objectUniformBufferData, &objectUBO, sizeof(objectUBO)); + objectUniformBufferData += objectUniformBufferSize; + } + } + } + for (auto i = 0; i != CUBE_COUNT; ++i) + { + auto T = glm::translate(glm::mat4(1.0f), glm::vec3(3.0f * i - 1.5f, 0.0f, 0.0f)); + auto S = glm::scale(glm::mat4(1.0f), glm::vec3(0.2f)); + objectUBO.model = T * S; + objectUBO.color = glm::vec4(0.0f, 0.0f, 1.0f, 0.5f); + memcpy(objectUniformBufferData, &objectUBO, sizeof(objectUBO)); + objectUniformBufferData += objectUniformBufferSize; + } + } + + void prepareGeometryPass() + { + VkSubpassDescription subpassDescription = {}; + subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + + // Geometry render pass doesn't need any output attachment. + auto renderPassInfo = vks::initializers::renderPassCreateInfo(); + renderPassInfo.attachmentCount = 0; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpassDescription; + + VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &geometryPass.renderPass)); + + // Geometry framebuffer doesn't need any output attachment. + VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo(); + fbufCreateInfo.renderPass = geometryPass.renderPass; + fbufCreateInfo.attachmentCount = 0; + fbufCreateInfo.width = width; + fbufCreateInfo.height = height; + fbufCreateInfo.layers = 1; + + VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &geometryPass.framebuffer)); + + // Create a buffer for GeometrySBO + // Using the device memory will be best but I will use the host visible buffer to make this example simple. + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &geometryPass.geometry, + sizeof(geometrySBO))); + + VK_CHECK_RESULT(geometryPass.geometry.map()); + + // Set up GeometrySBO data. + geometrySBO.count = 0; + geometrySBO.maxNodeCount = NODE_COUNT * width * height; + memcpy(geometryPass.geometry.mapped, &geometrySBO, sizeof(geometrySBO)); + + // Create a texture for HeadIndex. + // This image will track the head index of each fragment. + geometryPass.headIndex.device = vulkanDevice; + + VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo(); + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.format = VK_FORMAT_R32_UINT; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT; + + VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &geometryPass.headIndex.image)); + + geometryPass.headIndex.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, geometryPass.headIndex.image, &memReqs); + + VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); + memAlloc.allocationSize = memReqs.size; + memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &geometryPass.headIndex.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, geometryPass.headIndex.image, geometryPass.headIndex.deviceMemory, 0)); + + VkImageViewCreateInfo imageViewInfo = vks::initializers::imageViewCreateInfo(); + imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + imageViewInfo.format = VK_FORMAT_R32_UINT; + imageViewInfo.flags = 0; + imageViewInfo.image = geometryPass.headIndex.image; + imageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageViewInfo.subresourceRange.baseMipLevel = 0; + imageViewInfo.subresourceRange.levelCount = 1; + imageViewInfo.subresourceRange.baseArrayLayer = 0; + imageViewInfo.subresourceRange.layerCount = 1; + + VK_CHECK_RESULT(vkCreateImageView(device, &imageViewInfo, nullptr, &geometryPass.headIndex.view)); + + geometryPass.headIndex.width = width; + geometryPass.headIndex.height = height; + geometryPass.headIndex.mipLevels = 1; + geometryPass.headIndex.layerCount = 1; + geometryPass.headIndex.descriptor.imageView = geometryPass.headIndex.view; + geometryPass.headIndex.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + geometryPass.headIndex.sampler = VK_NULL_HANDLE; + + // Create a buffer for LinkedListSBO + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &geometryPass.linkedList, + sizeof(Node) * geometrySBO.maxNodeCount)); + + VK_CHECK_RESULT(geometryPass.linkedList.map()); + + // Change HeadInex image's layout from UNDEFINED to GENERAL + auto cmdBufAllocInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); + + VkCommandBuffer cmdBuf; + VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocInfo, &cmdBuf)); + + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuf, &cmdBufInfo)); + + auto barrier = vks::initializers::imageMemoryBarrier(); + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.image = geometryPass.headIndex.image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuf)); + + auto submitInfo = vks::initializers::submitInfo(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &cmdBuf; + + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VK_CHECK_RESULT(vkQueueWaitIdle(queue)); + } + + void setupDescriptorSetLayout() + { + // Create a geometry descriptor set layout. + std::vector setLayoutBindings = { + // RenderPassUBO + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0), + // ObjectUBO + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 1), + // AtomicSBO + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + VK_SHADER_STAGE_FRAGMENT_BIT, + 2), + // headIndexImage + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_SHADER_STAGE_FRAGMENT_BIT, + 3), + // LinkedListSBO + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + VK_SHADER_STAGE_FRAGMENT_BIT, + 4), + }; + + auto descriptorLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCreateInfo, nullptr, &descriptorSetLayouts.geometry)); + + // Create a geometry pipeline layout. + auto pipelineLayoutCreateInfo = + vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.geometry, 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.geometry)); + + // Create a color descriptor set layout. + setLayoutBindings = { + // headIndexImage + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_SHADER_STAGE_FRAGMENT_BIT, + 0), + // LinkedListSBO + vks::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + VK_SHADER_STAGE_FRAGMENT_BIT, + 1), + }; + + descriptorLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCreateInfo, nullptr, &descriptorSetLayouts.color)); + + // Create a color pipeline layout. + pipelineLayoutCreateInfo = + vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.color, 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.color)); + } + + 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_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(0, nullptr); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, 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); + std::array shaderStages; + + // Create a geometry pipeline. + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.geometry, geometryPass.renderPass); + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = shaderStages.size(); + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position }); + + shaderStages[0] = loadShader(getShadersPath() + "oit/geometry.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getShadersPath() + "oit/geometry.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.geometry)); + + // Create a color pipeline. + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.color, renderPass); + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.stageCount = shaderStages.size(); + pipelineCI.pStages = shaderStages.data(); + pipelineCI.pVertexInputState = &vertexInputInfo; + + shaderStages[0] = loadShader(getShadersPath() + "oit/color.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getShadersPath() + "oit/color.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT; + rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.color)); + } + + void setupDescriptorPool() + { + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2), + }; + + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vks::initializers::descriptorPoolCreateInfo( + poolSizes.size(), + poolSizes.data(), + 2); + + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + } + + void setupDescriptorSets() + { + // Update a geometry descriptor set + VkDescriptorSetAllocateInfo allocInfo = + vks::initializers::descriptorSetAllocateInfo( + descriptorPool, + &descriptorSetLayouts.geometry, + 1); + + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.geometry)); + + std::vector writeDescriptorSets = { + // Binding 0: RenderPassUBO + vks::initializers::writeDescriptorSet( + descriptorSets.geometry, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 0, + &uniformBuffers.renderPass.descriptor), + // Binding 1: ObjectUBO + vks::initializers::writeDescriptorSet( + descriptorSets.geometry, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + 1, + &uniformBuffers.objects.descriptor), + // Binding 2: GeometrySBO + vks::initializers::writeDescriptorSet( + descriptorSets.geometry, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 2, + &geometryPass.geometry.descriptor), + // Binding 3: headIndexImage + vks::initializers::writeDescriptorSet( + descriptorSets.geometry, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + 3, + &geometryPass.headIndex.descriptor), + // Binding 4: LinkedListSBO + vks::initializers::writeDescriptorSet( + descriptorSets.geometry, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 4, + &geometryPass.linkedList.descriptor) + }; + + vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + + // Update a color descriptor set. + allocInfo = + vks::initializers::descriptorSetAllocateInfo( + descriptorPool, + &descriptorSetLayouts.color, + 1); + + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.color)); + + writeDescriptorSets = { + // Binding 0: headIndexImage + vks::initializers::writeDescriptorSet( + descriptorSets.color, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + 0, + &geometryPass.headIndex.descriptor), + // Binding 1: LinkedListSBO + vks::initializers::writeDescriptorSet( + descriptorSets.color, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + &geometryPass.linkedList.descriptor) + }; + + vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + } + + void buildCommandBuffers() + { + if (resized) + return; + + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = defaultClearColor; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); + renderPassBeginInfo.renderArea.offset.x = 0; + renderPassBeginInfo.renderArea.offset.y = 0; + renderPassBeginInfo.renderArea.extent.width = width; + renderPassBeginInfo.renderArea.extent.height = height; + + VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) + { + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + // Update dynamic viewport state + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + + // Update dynamic scissor state + vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); + + VkClearColorValue clearColor; + clearColor.uint32[0] = 0xffffffff; + + VkImageSubresourceRange subresRange = {}; + + subresRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresRange.levelCount = 1; + subresRange.layerCount = 1; + + vkCmdClearColorImage(drawCmdBuffers[i], geometryPass.headIndex.image, VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &subresRange); + + // Begin the geometry render pass + renderPassBeginInfo.renderPass = geometryPass.renderPass; + renderPassBeginInfo.framebuffer = geometryPass.framebuffer; + renderPassBeginInfo.clearValueCount = 0; + renderPassBeginInfo.pClearValues = nullptr; + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.geometry); + uint32_t dynamicOffset = 0; + models.sphere.bindBuffers(drawCmdBuffers[i]); + for (auto j = 0; j != SPHERE_COUNT; ++j) + { + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.geometry, 0, 1, &descriptorSets.geometry, 1, &dynamicOffset); + models.sphere.draw(drawCmdBuffers[i]); + dynamicOffset += objectUniformBufferSize; + } + models.cube.bindBuffers(drawCmdBuffers[i]); + for (auto j = 0; j != CUBE_COUNT; ++j) + { + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.geometry, 0, 1, &descriptorSets.geometry, 1, &dynamicOffset); + models.cube.draw(drawCmdBuffers[i]); + dynamicOffset += objectUniformBufferSize; + } + vkCmdEndRenderPass(drawCmdBuffers[i]); + + // Make a pipeline barrier to guarantee the geometry pass is done + vkCmdPipelineBarrier(drawCmdBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr); + + // Begin the color render pass + renderPassBeginInfo.renderPass = renderPass; + renderPassBeginInfo.framebuffer = frameBuffers[i]; + renderPassBeginInfo.clearValueCount = 2; + renderPassBeginInfo.pClearValues = clearValues; + + vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.color); + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.color, 0, 1, &descriptorSets.color, 0, nullptr); + vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); + drawUI(drawCmdBuffers[i]); + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void updateUniformBuffers() + { + renderPassUBO.projection = camera.matrices.perspective; + renderPassUBO.view = camera.matrices.view; + memcpy(uniformBuffers.renderPass.mapped, &renderPassUBO, sizeof(renderPassUBO)); + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + + // Clear previous geometry pass data + memset(geometryPass.geometry.mapped, 0, sizeof(uint32_t)); + + // Command buffer to be submitted to the queue + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + + // Submit to queue + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + VulkanExampleBase::submitFrame(); + } + + void destroyGeometryPass() + { + vkDestroyRenderPass(device, geometryPass.renderPass, nullptr); + vkDestroyFramebuffer(device, geometryPass.framebuffer, nullptr); + geometryPass.geometry.destroy(); + geometryPass.headIndex.destroy(); + geometryPass.linkedList.destroy(); + } + +private: + VkDeviceSize objectUniformBufferSize; +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file