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