From 670df3d373ed096a4954865bb36a04df40b1faca Mon Sep 17 00:00:00 2001
From: Jens Koenen <koenen@vr.rwth-aachen.de>
Date: Wed, 14 Sep 2022 17:22:32 +0200
Subject: [PATCH] Started implementing the geometry capturing for light
 propagating volumes.

---
 CMakeLists.txt                            |   4 +
 res/dpr/data/indirect_data.inc            |   9 +-
 res/dpr/utility/indirect_geometry.frag    |  58 ++++
 res/dpr/utility/indirect_geometry.geom    |  67 ++++
 res/dpr/utility/indirect_geometry.vert    |  23 ++
 res/dpr/utility/indirect_injection.geom   |   2 +-
 res/dpr/utility/indirect_propagation.comp |   2 +-
 src/utility/indirect_cache.cpp            | 368 ++++++++++++++++++++--
 src/utility/indirect_cache.hpp            |  20 +-
 9 files changed, 512 insertions(+), 41 deletions(-)
 create mode 100644 res/dpr/utility/indirect_geometry.frag
 create mode 100644 res/dpr/utility/indirect_geometry.geom
 create mode 100644 res/dpr/utility/indirect_geometry.vert

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e2418971..5adb0e03 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -759,6 +759,10 @@ message("=======================================================================
 				utility/indirect_capture.vert
 				utility/indirect_capture.frag
 				
+				utility/indirect_geometry.vert
+				utility/indirect_geometry.geom
+				utility/indirect_geometry.frag
+				
 				utility/indirect_injection.vert
 				utility/indirect_injection.geom
 				utility/indirect_injection.frag
diff --git a/res/dpr/data/indirect_data.inc b/res/dpr/data/indirect_data.inc
index 6e4a5246..0b774ae4 100644
--- a/res/dpr/data/indirect_data.inc
+++ b/res/dpr/data/indirect_data.inc
@@ -3,14 +3,17 @@
 
 struct IndirectDomain
 {
-    uvec3 resolution;
+    uvec3 indirect_resolution;
     float cell_size;
 
-    vec3 min;
+    uvec3 geometry_resolution;
     uint padding1;
 
-    vec3 max;
+    vec3 min;
     uint padding2;
+
+    vec3 max;
+    uint padding3;
 };
 
 #endif
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_geometry.frag b/res/dpr/utility/indirect_geometry.frag
new file mode 100644
index 00000000..3aa83d02
--- /dev/null
+++ b/res/dpr/utility/indirect_geometry.frag
@@ -0,0 +1,58 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_shader_atomic_float : require
+
+#include "indirect_data.inc"
+#include "indirect_library.glsl"
+
+layout(set = 1, binding = 0) uniform IndirectDomainBuffer
+{
+    IndirectDomain indirect_domain;
+};
+
+layout(set = 1, binding = 1, r32f) uniform image3D image_geometry_x_distribution;
+layout(set = 1, binding = 2, r32f) uniform image3D image_geometry_y_distribution;
+layout(set = 1, binding = 3, r32f) uniform image3D image_geometry_z_distribution;
+layout(set = 1, binding = 4, r32f) uniform image3D image_geometry_w_distribution;
+
+layout(push_constant) uniform Constants
+{
+    uvec3 voxel_resolution;
+};
+
+layout(location = 0) in vec3 inCoord;
+layout(location = 1) in vec3 inNormal;
+
+void main()
+{
+    ivec3 cell_coord = ivec3(inCoord * indirect_domain.geometry_resolution);
+
+    vec3 normal = normalize(inNormal);
+    vec4 distribution = spherical_harmonic_cosine_lobe(normal);
+
+    vec3 coverage = vec3(indirect_domain.geometry_resolution) / vec3(voxel_resolution);
+    float opacity = 0.0;
+
+    if(gl_ViewportIndex == 0)
+    {
+        opacity = coverage.y * coverage.z;
+    }
+
+    else if(gl_ViewportIndex == 1)
+    {
+        opacity = coverage.x * coverage.z;
+    }
+
+    else
+    {
+        opacity = coverage.x * coverage.y;
+    }
+
+    //NOTE: Where opacity is in percent
+    vec4 geometry_distribution = opacity * distribution;
+
+    imageAtomicAdd(image_geometry_x_distribution, cell_coord, geometry_distribution.x);
+    imageAtomicAdd(image_geometry_y_distribution, cell_coord, geometry_distribution.y);
+    imageAtomicAdd(image_geometry_z_distribution, cell_coord, geometry_distribution.z);
+    imageAtomicAdd(image_geometry_w_distribution, cell_coord, geometry_distribution.w);
+}
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_geometry.geom b/res/dpr/utility/indirect_geometry.geom
new file mode 100644
index 00000000..b63bf132
--- /dev/null
+++ b/res/dpr/utility/indirect_geometry.geom
@@ -0,0 +1,67 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "indirect_data.inc"
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = 3) out;
+
+layout(set = 1, binding = 0) uniform IndirectDomainBuffer
+{
+    IndirectDomain indirect_domain;
+};
+
+layout(location = 0) in vec3 inNormal[];
+
+layout(location = 0) out vec3 outCoord;
+layout(location = 1) out vec3 outNormal;
+
+void main()
+{
+    vec3 position1 = gl_in[0].gl_Position.xyz;
+    vec3 position2 = gl_in[1].gl_Position.xyz;
+    vec3 position3 = gl_in[2].gl_Position.xyz;
+
+    vec3 normal_abs = abs(cross(position2 - position1, position3 - position1));
+    float normal_max = max(normal_abs.x, max(normal_abs.y, normal_abs.z));
+
+    if(normal_abs.x == normal_max)
+    {
+        position1 = position1.yzx;
+        position2 = position2.yzx;
+        position3 = position3.yzx;
+
+        gl_ViewportIndex = 0;
+    }
+
+    else if(normal_abs.y == normal_max)
+    {
+        position1 = position1.xzy;
+        position2 = position2.xzy;
+        position3 = position3.xzy;
+
+        gl_ViewportIndex = 1;
+    }
+
+    else
+    {
+        gl_ViewportIndex = 2;
+    }
+
+    outCoord = (position1 - indirect_domain.min) / (indirect_domain.max - indirect_domain.min);
+    outNormal = inNormal[0];
+    gl_Position = vec4(outCoord.xy * 2.0 - 1.0, 0.5, 1.0);
+    EmitVertex();
+
+    outCoord = (position2 - indirect_domain.min) / (indirect_domain.max - indirect_domain.min);
+    outNormal = inNormal[1];
+    gl_Position = vec4(outCoord.xy * 2.0 - 1.0, 0.5, 1.0);
+    EmitVertex();
+
+    outCoord = (position3 - indirect_domain.min) / (indirect_domain.max - indirect_domain.min);
+    outNormal = inNormal[2];
+    gl_Position = vec4(outCoord.xy * 2.0 - 1.0, 0.5, 1.0);
+    EmitVertex();
+
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_geometry.vert b/res/dpr/utility/indirect_geometry.vert
new file mode 100644
index 00000000..28416734
--- /dev/null
+++ b/res/dpr/utility/indirect_geometry.vert
@@ -0,0 +1,23 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "mesh_data.inc"
+
+layout(set = 0, binding = 0) uniform MeshBuffer
+{
+    MeshData mesh;
+};
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec3 inTangent;
+layout(location = 3) in vec2 inUV;
+
+layout(location = 0) out vec3 outNormal;
+
+void main()
+{
+    outNormal = normalize((mesh.vectorToWorldSpace * vec4(inNormal, 0.0)).xyz);
+
+    gl_Position = mesh.localToWorldSpace * vec4(inPos, 1.0);
+}
diff --git a/res/dpr/utility/indirect_injection.geom b/res/dpr/utility/indirect_injection.geom
index 386e1677..93ca3bbf 100644
--- a/res/dpr/utility/indirect_injection.geom
+++ b/res/dpr/utility/indirect_injection.geom
@@ -22,7 +22,7 @@ void main()
     vec3 position = gl_in[0].gl_Position.xyz;
     position = (position - indirect_domain.min) / (indirect_domain.max - indirect_domain.min);
     position.xy = position.xy * 2.0 - 1.0;
-    position.z = position.z * indirect_domain.resolution.z;
+    position.z = position.z * indirect_domain.indirect_resolution.z;
 
     outFlux = inFlux[0];
     outNormal = inNormal[0];
diff --git a/res/dpr/utility/indirect_propagation.comp b/res/dpr/utility/indirect_propagation.comp
index 5bfc3b82..f77b617b 100644
--- a/res/dpr/utility/indirect_propagation.comp
+++ b/res/dpr/utility/indirect_propagation.comp
@@ -70,7 +70,7 @@ const float side_solid_angle = 0.42343135;
 
 bool check_bounds(ivec3 coord)
 {
-    return all(lessThanEqual(ivec3(0), coord)) && all(lessThan(coord, indirect_domain.resolution));
+    return all(lessThanEqual(ivec3(0), coord)) && all(lessThan(coord, indirect_domain.indirect_resolution));
 }
 
 void propagate_neighbour(ivec3 cell_coord, uint neighbour, inout vec4 red_distribution, inout vec4 green_distribution, inout vec4 blue_distribution)
diff --git a/src/utility/indirect_cache.cpp b/src/utility/indirect_cache.cpp
index 1eb521f8..b13858db 100644
--- a/src/utility/indirect_cache.cpp
+++ b/src/utility/indirect_cache.cpp
@@ -15,7 +15,10 @@ bool IndirectCache::create(Scene::Ptr scene, const IndirectCacheSettings& settin
 	this->scene = scene;
     this->settings = settings;
 
-    this->compute_domain(scene, settings);
+    if(!this->compute_domain(scene, settings))
+    {
+        return false;
+    }
 
     if (!this->create_buffers(device, settings))
     {
@@ -42,6 +45,16 @@ bool IndirectCache::create(Scene::Ptr scene, const IndirectCacheSettings& settin
         return false;
     }
 
+    if (!this->create_geometry_pass(device))
+    {
+        return false;
+    }
+
+    if (!this->create_geometry_pipeline(device))
+    {
+        return false;
+    }
+
     if (!this->create_capture_pass(device, settings))
     {
         return false;
@@ -185,12 +198,10 @@ void IndirectCache::compute_indirect(VkCommandBuffer command_buffer, lava::index
 {
     const std::vector<SceneLight>& lights = this->scene->get_lights();
 
-    std::array<VkImageMemoryBarrier, 3> clear_begin_barriers = IndirectCache::build_barriers(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
-    clear_begin_barriers[0].image = this->distribution_red_image[0];
-    clear_begin_barriers[1].image = this->distribution_green_image[0];
-    clear_begin_barriers[2].image = this->distribution_blue_image[0];
+    VkImageMemoryBarrier clear_geometry_begin_barrier = IndirectCache::build_barrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+    clear_geometry_begin_barrier.image = this->distribution_geometry_image;
 
-    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, clear_begin_barriers.size(), clear_begin_barriers.data());
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &clear_geometry_begin_barrier);
 
     VkClearColorValue clear_color;
     clear_color.float32[0] = 0.0f;
@@ -205,6 +216,25 @@ void IndirectCache::compute_indirect(VkCommandBuffer command_buffer, lava::index
     subresource_range.baseArrayLayer = 0;
     subresource_range.layerCount = 1;
 
+    vkCmdClearColorImage(command_buffer, this->distribution_geometry_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &subresource_range);
+
+    VkImageMemoryBarrier clear_geometry_end_barrier = IndirectCache::build_barrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
+    clear_geometry_end_barrier.image = this->distribution_geometry_image;
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &clear_geometry_end_barrier);
+
+    this->geometry_pass->process(command_buffer, 0);
+
+
+    //TODO: Another layout transition to shader ead only optimal
+
+    std::array<VkImageMemoryBarrier, 3> clear_begin_barriers = IndirectCache::build_barriers(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+    clear_begin_barriers[0].image = this->distribution_red_image[0];
+    clear_begin_barriers[1].image = this->distribution_green_image[0];
+    clear_begin_barriers[2].image = this->distribution_blue_image[0];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, clear_begin_barriers.size(), clear_begin_barriers.data());
+
     vkCmdClearColorImage(command_buffer, this->distribution_red_image[0], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &subresource_range);
     vkCmdClearColorImage(command_buffer, this->distribution_green_image[0], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &subresource_range);
     vkCmdClearColorImage(command_buffer, this->distribution_blue_image[0], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &subresource_range);
@@ -253,9 +283,9 @@ void IndirectCache::compute_indirect(VkCommandBuffer command_buffer, lava::index
     offset.z = 0;
 
     VkExtent3D extend;
-    extend.width = this->domain_resolution.x;
-    extend.height = this->domain_resolution.y;
-    extend.depth = this->domain_resolution.z;
+    extend.width = this->domain_indirect_resolution.x;
+    extend.height = this->domain_indirect_resolution.y;
+    extend.depth = this->domain_indirect_resolution.z;
 
     VkImageCopy image_copy;
     image_copy.srcSubresource = subresource_layers;
@@ -345,8 +375,15 @@ const IndirectCacheSettings& IndirectCache::get_settings() const
     return this->settings;
 }
 
-void IndirectCache::compute_domain(Scene::Ptr scene, const IndirectCacheSettings& settings)
+bool IndirectCache::compute_domain(Scene::Ptr scene, const IndirectCacheSettings& settings)
 {
+    if (settings.voxel_resolution_scale <= 0)
+    {
+        lava::log()->error("Voxel resolution scale needs to be greater or equal to one!");
+    
+        return false;
+    }
+
     glm::vec3 scene_min = scene->get_scene_min();
     glm::vec3 scene_max = scene->get_scene_max();
 
@@ -355,38 +392,44 @@ void IndirectCache::compute_domain(Scene::Ptr scene, const IndirectCacheSettings
 
     const glm::uvec3 work_group_size = glm::uvec3(16, 16, 2);
 
-    this->domain_resolution = glm::uvec3(glm::max(glm::ceil(scene_size / settings.cell_size), glm::vec3(1.0f)));
-    this->domain_min = scene_center - 0.5f * settings.cell_size * glm::vec3(this->domain_resolution);
-    this->domain_max = scene_center + 0.5f * settings.cell_size * glm::vec3(this->domain_resolution);
-    this->domain_work_groups = this->domain_resolution / work_group_size;
+    this->domain_indirect_resolution = glm::uvec3(glm::max(glm::ceil(scene_size / settings.cell_size), glm::vec3(1.0f)));
+    this->domain_geometry_resolution = this->domain_indirect_resolution + glm::uvec3(1);
+    this->domain_voxel_resolution = this->domain_geometry_resolution * settings.voxel_resolution_scale;
+    this->domain_min = scene_center - 0.5f * settings.cell_size * glm::vec3(this->domain_indirect_resolution);
+    this->domain_max = scene_center + 0.5f * settings.cell_size * glm::vec3(this->domain_indirect_resolution);
+    this->domain_work_groups = this->domain_indirect_resolution / work_group_size;
     
-    if ((this->domain_resolution.x % work_group_size.x) > 0)
+    if ((this->domain_indirect_resolution.x % work_group_size.x) > 0)
     {
         this->domain_work_groups.x += 1;
     }
 
-    if ((this->domain_resolution.y % work_group_size.y) > 0)
+    if ((this->domain_indirect_resolution.y % work_group_size.y) > 0)
     {
         this->domain_work_groups.y += 1;
     }
 
-    if ((this->domain_resolution.z % work_group_size.z) > 0)
+    if ((this->domain_indirect_resolution.z % work_group_size.z) > 0)
     {
         this->domain_work_groups.z += 1;
     }
 
-    this->propagation_iterations = 2 * glm::max(this->domain_resolution.x, glm::max(this->domain_resolution.y, this->domain_resolution.z));
+    this->propagation_iterations = 2 * glm::max(this->domain_indirect_resolution.x, glm::max(this->domain_indirect_resolution.y, this->domain_indirect_resolution.z));
+
+    return true;
 }
 
 bool IndirectCache::create_buffers(lava::device_ptr device, const IndirectCacheSettings& settings)
 {
     glsl::IndirectDomain indirect_domain;
-    indirect_domain.resolution = this->domain_resolution;
+    indirect_domain.indirect_resolution = this->domain_indirect_resolution;
     indirect_domain.cell_size = settings.cell_size;
-    indirect_domain.min = this->domain_min;
+    indirect_domain.geometry_resolution = this->domain_geometry_resolution;
     indirect_domain.padding1 = 0;
-    indirect_domain.max = this->domain_max;
+    indirect_domain.min = this->domain_min;
     indirect_domain.padding2 = 0;
+    indirect_domain.max = this->domain_max;
+    indirect_domain.padding3 = 0;
 
     this->domain_buffer = lava::make_buffer();
 
@@ -448,9 +491,9 @@ bool IndirectCache::create_images(lava::device_ptr device, const IndirectCacheSe
     image_create_info.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
     image_create_info.imageType = VK_IMAGE_TYPE_3D;
     image_create_info.format = VK_FORMAT_R16G16B16A16_SFLOAT;
-    image_create_info.extent.width = this->domain_resolution.x;
-    image_create_info.extent.height = this->domain_resolution.y;
-    image_create_info.extent.depth = this->domain_resolution.z;
+    image_create_info.extent.width = this->domain_indirect_resolution.x;
+    image_create_info.extent.height = this->domain_indirect_resolution.y;
+    image_create_info.extent.depth = this->domain_indirect_resolution.z;
     image_create_info.mipLevels = 1;
     image_create_info.arrayLayers = 1;
     image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
@@ -493,7 +536,7 @@ bool IndirectCache::create_images(lava::device_ptr device, const IndirectCacheSe
     array_view_create_info.subresourceRange.baseMipLevel = 0;
     array_view_create_info.subresourceRange.levelCount = 1;
     array_view_create_info.subresourceRange.baseArrayLayer = 0;
-    array_view_create_info.subresourceRange.layerCount = this->domain_resolution.z;
+    array_view_create_info.subresourceRange.layerCount = this->domain_indirect_resolution.z;
 
     for (uint32_t index = 0; index < this->distribution_red_image.size(); index++)
     {
@@ -602,6 +645,73 @@ bool IndirectCache::create_images(lava::device_ptr device, const IndirectCacheSe
         this->distribution_blue_image_array_view[index] = blue_image_array_view;
     }
 
+    image_create_info.format = VK_FORMAT_R32_SFLOAT;
+    image_create_info.extent.width = this->domain_geometry_resolution.x;
+    image_create_info.extent.height = this->domain_geometry_resolution.y;
+    image_create_info.extent.depth = this->domain_geometry_resolution.z;
+    
+    for (uint32_t index = 0; index < this->distribution_geometry_image.size(); index++)
+    {
+        
+    }
+
+    if (vmaCreateImage(device->get_allocator()->get(), &image_create_info, &allocation_info, &this->distribution_geometry_image, &this->distribution_geometry_image_memory, nullptr) != VK_SUCCESS)
+    {
+        lava::log()->error("Can't create geometry distribution image for indirect cache!");
+
+        return false;
+    }
+
+    view_create_info.image = this->distribution_geometry_image;
+ 
+    if (vkCreateImageView(device->get(), &view_create_info, lava::memory::alloc(), &this->distribution_geometry_image_view) != VK_SUCCESS)
+    {
+        lava::log()->error("Can't create geometry distribution image view for indirect cache!");
+
+        return false;
+    }
+
+    VkImageViewCreateInfo component_view_create_info;
+    component_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    component_view_create_info.pNext = nullptr;
+    component_view_create_info.flags = 0;
+    component_view_create_info.image = this->distribution_geometry_image;
+    component_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
+    component_view_create_info.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+    component_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    component_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    component_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    component_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    component_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    component_view_create_info.subresourceRange.baseMipLevel = 0;
+    component_view_create_info.subresourceRange.levelCount = 1;
+    component_view_create_info.subresourceRange.baseArrayLayer = 0;
+    component_view_create_info.subresourceRange.layerCount = 1;
+
+    VkComponentMapping zero_component;
+    zero_component.r = VK_COMPONENT_SWIZZLE_ZERO;
+    zero_component.g = VK_COMPONENT_SWIZZLE_ZERO;
+    zero_component.b = VK_COMPONENT_SWIZZLE_ZERO;
+    zero_component.a = VK_COMPONENT_SWIZZLE_ZERO;
+
+    std::vector<VkComponentMapping> component_list(4, zero_component);
+    component_list[0].r = VK_COMPONENT_SWIZZLE_R;
+    component_list[1].r = VK_COMPONENT_SWIZZLE_G;
+    component_list[2].r = VK_COMPONENT_SWIZZLE_B;
+    component_list[3].r = VK_COMPONENT_SWIZZLE_A;
+    
+    for (uint32_t index = 0; index < this->distribution_geometry_image_component_view.size(); index++)
+    {
+        component_view_create_info.components = component_list[index];
+
+        if (vkCreateImageView(device->get(), &component_view_create_info, lava::memory::alloc(), &this->distribution_geometry_image_component_view[index]) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create geometry distribution image component view for indirect cache!");
+
+            return false;
+        }
+    }
+
     return true;
 }
 
@@ -633,6 +743,13 @@ bool IndirectCache::create_samplers(lava::device_ptr device)
 
         return false;
     }
+
+    if (vkCreateSampler(device->get(), &sampler_info, lava::memory::alloc(), &this->geometry_sampler) != VK_SUCCESS)
+    {
+        lava::log()->error("Can't create geometry sampler for indirect cache!");
+
+        return false;
+    }
     
     sampler_info.magFilter = VK_FILTER_NEAREST;
     sampler_info.minFilter = VK_FILTER_NEAREST;
@@ -652,14 +769,14 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
 {
     lava::VkDescriptorPoolSizes descriptor_type_count =
     {
-        { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4 },
+        { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 5 },
         { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6 },
-        { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 18 }
+        { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 22 }
     };
 
     this->descriptor_pool = lava::make_descriptor_pool();
 
-    if (!this->descriptor_pool->create(device, descriptor_type_count, 4))
+    if (!this->descriptor_pool->create(device, descriptor_type_count, 5))
     {
         lava::log()->error("Can't create descriptor pool for indirect cache!");
 
@@ -679,6 +796,21 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
         return false;
     }
 
+    this->geometry_descriptor = lava::make_descriptor();
+    this->geometry_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 0: indirect domain
+
+    this->geometry_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT);                                 //descriptor-binding index 1: geometry distribution x
+    this->geometry_descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT);                                 //descriptor-binding index 2: geometry distribution y
+    this->geometry_descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT);                                 //descriptor-binding index 3: geometry distribution z
+    this->geometry_descriptor->add_binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT);                                 //descriptor-binding index 4: geometry distribution w
+
+    if (!this->geometry_descriptor->create(device))
+    {
+        lava::log()->error("Can't create geometry descriptor set layout for indirect cache!");
+
+        return false;
+    }
+
     this->injection_descriptor = lava::make_descriptor();
     this->injection_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT); //descriptor-binding index 0: capture depth
     this->injection_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT); //descriptor-binding index 1: capture flux
@@ -715,6 +847,7 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
     }
 
     this->indirect_descriptor_set = this->indirect_descriptor->allocate(this->descriptor_pool->get());
+    this->geometry_descriptor_set = this->geometry_descriptor->allocate(this->descriptor_pool->get());
     this->injection_descriptor_set = this->injection_descriptor->allocate(this->descriptor_pool->get());
     this->propagation_descriptor_set[0] = this->propagation_descriptor->allocate(this->descriptor_pool->get());
     this->propagation_descriptor_set[1] = this->propagation_descriptor->allocate(this->descriptor_pool->get());
@@ -722,8 +855,8 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
     std::vector<VkWriteDescriptorSet> descriptor_writes;
     std::vector<VkDescriptorImageInfo> image_infos;
 
-    descriptor_writes.reserve(28);
-    image_infos.reserve(24);
+    descriptor_writes.reserve(33);
+    image_infos.reserve(28);
 
     std::vector<VkImageView> indirect_images = 
     {
@@ -752,6 +885,26 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
         descriptor_write.pTexelBufferView = nullptr;
     }
 
+    for (uint32_t index = 0; index < this->distribution_geometry_image_component_view.size(); index++)
+    {
+        VkDescriptorImageInfo& image_info = image_infos.emplace_back();
+        image_info.sampler = nullptr;
+        image_info.imageView = this->distribution_geometry_image_component_view[index];
+        image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+        VkWriteDescriptorSet& descriptor_write = descriptor_writes.emplace_back();
+        descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        descriptor_write.pNext = nullptr;
+        descriptor_write.dstSet = this->geometry_descriptor_set;
+        descriptor_write.dstBinding = index + 1;
+        descriptor_write.dstArrayElement = 0;
+        descriptor_write.descriptorCount = 1;
+        descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+        descriptor_write.pImageInfo = &image_info;
+        descriptor_write.pBufferInfo = nullptr;
+        descriptor_write.pTexelBufferView = nullptr;
+    }
+
     std::vector<VkImageView> injection_images = 
     {
         this->capture_depth_image->get_view(),
@@ -846,6 +999,18 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
     indirect_domain_write.pBufferInfo = this->domain_buffer->get_descriptor_info();
     indirect_domain_write.pTexelBufferView = nullptr;
 
+    VkWriteDescriptorSet& geometry_domain_write = descriptor_writes.emplace_back();
+    geometry_domain_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    geometry_domain_write.pNext = nullptr;
+    geometry_domain_write.dstSet = this->geometry_descriptor_set;
+    geometry_domain_write.dstBinding = 0;
+    geometry_domain_write.dstArrayElement = 0;
+    geometry_domain_write.descriptorCount = 1;
+    geometry_domain_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    geometry_domain_write.pImageInfo = nullptr;
+    geometry_domain_write.pBufferInfo = this->domain_buffer->get_descriptor_info();
+    geometry_domain_write.pTexelBufferView = nullptr;
+
     VkWriteDescriptorSet& injection_domain_write = descriptor_writes.emplace_back();
     injection_domain_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
     injection_domain_write.pNext = nullptr;
@@ -865,6 +1030,23 @@ bool IndirectCache::create_descriptors(lava::device_ptr device)
 
 bool IndirectCache::create_layouts(lava::device_ptr device, Scene::Ptr scene)
 {
+    VkPushConstantRange geometry_range;
+    geometry_range.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+    geometry_range.offset = 0;
+    geometry_range.size = sizeof(uint32_t) * 3;
+
+    this->geometry_layout = lava::make_pipeline_layout();
+    this->geometry_layout->add(scene->get_mesh_descriptor()); //set 0: mesh descriptor
+    this->geometry_layout->add(this->geometry_descriptor);    //set 1: geometry descriptor
+    this->geometry_layout->add(geometry_range);
+
+    if (!this->geometry_layout->create(device))
+    {
+        lava::log()->error("Can't create geometry pipeline layout for indirect cache!");
+
+        return false;
+    }
+
     VkPushConstantRange capture_range;
     capture_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
     capture_range.offset = 0;
@@ -913,6 +1095,71 @@ bool IndirectCache::create_layouts(lava::device_ptr device, Scene::Ptr scene)
     return true;
 }
 
+bool IndirectCache::create_geometry_pass(lava::device_ptr device)
+{
+    lava::subpass::ptr subpass = lava::make_subpass();
+
+    this->geometry_pass = lava::make_render_pass(device);
+    this->geometry_pass->add(subpass);
+
+    uint32_t voxel_max_dimension = glm::max(this->domain_voxel_resolution.x, glm::max(this->domain_voxel_resolution.y, this->domain_voxel_resolution.z));
+
+    lava::rect framebuffer_area =
+    {
+        glm::vec2(0.0f),
+        glm::vec2(voxel_max_dimension)
+    };
+
+    lava::VkImageViews framebuffer_views = {};
+
+    if (!this->geometry_pass->create({ framebuffer_views }, framebuffer_area))
+    {
+        lava::log()->error("Can't create geometry pass for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_geometry_pipeline(lava::device_ptr device)
+{
+    this->geometry_pipeline = lava::make_graphics_pipeline(device);
+    this->geometry_pipeline->set_layout(this->geometry_layout);
+    this->geometry_pipeline->set_auto_size(false);
+
+    scene->set_vertex_input(this->geometry_pipeline.get());
+
+    if (!this->geometry_pipeline->add_shader(lava::file_data("dpr/binary/indirect_geometry_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
+    {
+        return false;
+    }
+
+    if (!this->geometry_pipeline->add_shader(lava::file_data("dpr/binary/indirect_geometry_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
+    {
+        return false;
+    }
+
+    if (!this->geometry_pipeline->add_shader(lava::file_data("dpr/binary/indirect_geometry_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
+    {
+        return false;
+    }
+
+    this->geometry_pipeline->on_process = [this](VkCommandBuffer command_buffer)
+    {
+        this->pipeline_geometry(command_buffer);
+    };
+
+    if (!this->geometry_pipeline->create(this->geometry_pass->get()))
+    {
+        return false;
+    }
+
+    this->geometry_pass->add_front(this->geometry_pipeline);
+
+    return true;
+}
+
 bool IndirectCache::create_capture_pass(lava::device_ptr device, const IndirectCacheSettings& settings)
 {
     VkClearValue clear_depth;
@@ -1101,12 +1348,12 @@ bool IndirectCache::create_injection_pass(lava::device_ptr device)
     this->injection_pass->add(attachment_red);      //location 0: distribution red
     this->injection_pass->add(attachment_green);    //location 1: distribution green
     this->injection_pass->add(attachment_blue);     //location 2: distribution blue
-    this->injection_pass->set_layers(this->domain_resolution.z);
+    this->injection_pass->set_layers(this->domain_indirect_resolution.z);
 
     lava::rect framebuffer_area =
     {
         glm::vec2(0.0f),
-        glm::vec2(this->domain_resolution.x, this->domain_resolution.y)
+        glm::vec2(this->domain_indirect_resolution.x, this->domain_indirect_resolution.y)
     };
 
     lava::VkImageViews framebuffer_views =
@@ -1193,6 +1440,54 @@ bool IndirectCache::create_propagation_pipeline(lava::device_ptr device)
     return true;
 }
 
+void IndirectCache::pipeline_geometry(VkCommandBuffer command_buffer)
+{
+    std::array<uint32_t, 3> push_constants;
+    push_constants[0] = this->domain_voxel_resolution.x;
+    push_constants[1] = this->domain_voxel_resolution.y;
+    push_constants[2] = this->domain_voxel_resolution.z;
+
+    vkCmdPushConstants(command_buffer, this->geometry_layout->get(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(uint32_t) * push_constants.size(), push_constants.data());
+
+    std::array<VkViewport, 3> viewports;
+    viewports[0].x = 0.0f;
+    viewports[0].y = 0.0f;
+    viewports[0].width = this->domain_voxel_resolution.y;
+    viewports[0].height = this->domain_voxel_resolution.z;
+    viewports[0].minDepth = 0.0f;
+    viewports[0].maxDepth = 1.0f;
+
+    viewports[1].x = 0.0f;
+    viewports[1].y = 0.0f;
+    viewports[1].width = this->domain_voxel_resolution.x;
+    viewports[1].height = this->domain_voxel_resolution.z;
+    viewports[1].minDepth = 0.0f;
+    viewports[1].maxDepth = 1.0f;
+
+    viewports[2].x = 0.0f;
+    viewports[2].y = 0.0f;
+    viewports[2].width = this->domain_voxel_resolution.x;
+    viewports[2].height = this->domain_voxel_resolution.y;
+    viewports[2].minDepth = 0.0f;
+    viewports[2].maxDepth = 1.0f;
+
+    vkCmdSetViewport(command_buffer, 0, 3, viewports.data());
+
+    this->geometry_layout->bind(command_buffer, this->geometry_descriptor_set, 1);
+
+    for (const SceneMesh& mesh : this->scene->get_meshes())
+    {
+        if (!mesh.cast_shadow)
+        {
+            continue;
+        }
+
+        this->geometry_layout->bind(command_buffer, mesh.descriptor_set[this->frame], 0);
+
+        mesh.mesh->bind_draw(command_buffer, this->domain_geometry_resolution.z);
+    }
+}
+
 void IndirectCache::pipeline_capture(VkCommandBuffer command_buffer)
 {
     std::array<uint32_t, 2> push_constants;
@@ -1238,7 +1533,7 @@ void IndirectCache::pipeline_injection(VkCommandBuffer command_buffer)
     vkCmdDraw(command_buffer, point_count, 1, 0, 0);
 }
 
-std::array<VkImageMemoryBarrier, 3> IndirectCache::build_barriers(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout)
+VkImageMemoryBarrier IndirectCache::build_barrier(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout)
 {
     VkImageMemoryBarrier barrier;
     barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@@ -1256,8 +1551,13 @@ std::array<VkImageMemoryBarrier, 3> IndirectCache::build_barriers(VkAccessFlags
     barrier.subresourceRange.baseArrayLayer = 0;
     barrier.subresourceRange.layerCount = 1;
 
+    return barrier;
+}
+
+std::array<VkImageMemoryBarrier, 3> IndirectCache::build_barriers(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout)
+{
     std::array<VkImageMemoryBarrier, 3> barriers;
-    barriers.fill(barrier);
+    barriers.fill(build_barrier(src_access, dst_access, old_layout, new_layout));
 
     return barriers;
 }
diff --git a/src/utility/indirect_cache.hpp b/src/utility/indirect_cache.hpp
index df471b1a..5cc5ccc9 100644
--- a/src/utility/indirect_cache.hpp
+++ b/src/utility/indirect_cache.hpp
@@ -11,6 +11,7 @@
 struct IndirectCacheSettings
 {
     uint32_t capture_resolution = 1024;
+    uint32_t voxel_resolution_scale = 1; //Needs to be greater or equal to one
     float cell_size = 1.0f; //Where cell size is in meters
 };
 
@@ -33,22 +34,26 @@ public:
     const IndirectCacheSettings& get_settings() const;
 
 private:
-    void compute_domain(Scene::Ptr scene, const IndirectCacheSettings& settings);
+    bool compute_domain(Scene::Ptr scene, const IndirectCacheSettings& settings);
 
     bool create_buffers(lava::device_ptr device, const IndirectCacheSettings& settings);
     bool create_images(lava::device_ptr device, const IndirectCacheSettings& settings);
     bool create_samplers(lava::device_ptr device);
     bool create_descriptors(lava::device_ptr device);
     bool create_layouts(lava::device_ptr device, Scene::Ptr scene);
+    bool create_geometry_pass(lava::device_ptr device);
+    bool create_geometry_pipeline(lava::device_ptr device);
     bool create_capture_pass(lava::device_ptr device, const IndirectCacheSettings& settings);
     bool create_capture_pipeline(lava::device_ptr device);
     bool create_injection_pass(lava::device_ptr device);
     bool create_injection_pipeline(lava::device_ptr device);
     bool create_propagation_pipeline(lava::device_ptr device);
    
+    void pipeline_geometry(VkCommandBuffer command_buffer);
     void pipeline_capture(VkCommandBuffer command_buffer);
     void pipeline_injection(VkCommandBuffer command_buffer);
 
+    static VkImageMemoryBarrier build_barrier(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout);
     static std::array<VkImageMemoryBarrier, 3> build_barriers(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout);
 
 private:
@@ -62,30 +67,38 @@ private:
 
     glm::vec3 domain_min = glm::vec3(0.0f);
     glm::vec3 domain_max = glm::vec3(0.0f);
-    glm::uvec3 domain_resolution = glm::uvec3(0);
+    glm::uvec3 domain_indirect_resolution = glm::uvec3(0);
+    glm::uvec3 domain_geometry_resolution = glm::uvec3(0);
+    glm::uvec3 domain_voxel_resolution = glm::uvec3(0);
     glm::uvec3 domain_work_groups = glm::uvec3(0);
 
     lava::descriptor::pool::ptr descriptor_pool;
     lava::descriptor::ptr indirect_descriptor;
+    lava::descriptor::ptr geometry_descriptor;
     lava::descriptor::ptr injection_descriptor;
     lava::descriptor::ptr propagation_descriptor;
 
     VkDescriptorSet indirect_descriptor_set = VK_NULL_HANDLE;
+    VkDescriptorSet geometry_descriptor_set = VK_NULL_HANDLE;
     VkDescriptorSet injection_descriptor_set = VK_NULL_HANDLE;
     std::array<VkDescriptorSet, 2> propagation_descriptor_set;
     
+    lava::pipeline_layout::ptr geometry_layout;
     lava::pipeline_layout::ptr capture_layout;
     lava::pipeline_layout::ptr injection_layout;
     lava::pipeline_layout::ptr propagation_layout;
 
+    lava::render_pass::ptr geometry_pass;
     lava::render_pass::ptr capture_pass;
     lava::render_pass::ptr injection_pass;
     
+    lava::graphics_pipeline::ptr geometry_pipeline;
     lava::graphics_pipeline::ptr capture_pipeline;
     lava::graphics_pipeline::ptr injection_pipeline;
     lava::compute_pipeline::ptr propagation_pipeline;
 
     VkSampler indirect_sampler = VK_NULL_HANDLE;
+    VkSampler geometry_sampler = VK_NULL_HANDLE;
     VkSampler injection_sampler = VK_NULL_HANDLE;
 
     lava::image::ptr capture_depth_image;
@@ -96,14 +109,17 @@ private:
     std::array<VmaAllocation, 3> distribution_red_image_memory;
     std::array<VmaAllocation, 3> distribution_green_image_memory;
     std::array<VmaAllocation, 3> distribution_blue_image_memory;
+    std::array<VmaAllocation, 4> distribution_geometry_image_memory;
 
     std::array<VkImage, 3> distribution_red_image;
     std::array<VkImage, 3> distribution_green_image;
     std::array<VkImage, 3> distribution_blue_image;
+    std::array<VkImage, 4> distribution_geometry_image;
 
     std::array<VkImageView, 3> distribution_red_image_view;
     std::array<VkImageView, 3> distribution_green_image_view;
     std::array<VkImageView, 3> distribution_blue_image_view;
+    std::array<VkImageView, 4> distribution_geometry_image_view;
 
     std::array<VkImageView, 3> distribution_red_image_array_view;
     std::array<VkImageView, 3> distribution_green_image_array_view;
-- 
GitLab