diff --git a/CMakeLists.txt b/CMakeLists.txt
index 89fea60eb527a673ada7868412ae389d386d38bf..0a1af49ba57d2c8189f8387d6263cbe884daae70 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -714,6 +714,7 @@ message("=======================================================================
 			${SRC_DIR}/utility/extern_fence.hpp ${SRC_DIR}/utility/extern_fence.cpp
             ${SRC_DIR}/utility/frame_capture.hpp ${SRC_DIR}/utility/frame_capture.cpp
             ${SRC_DIR}/utility/geometry_buffer.hpp ${SRC_DIR}/utility/geometry_buffer.cpp
+			${SRC_DIR}/utility/indirect_cache.hpp ${SRC_DIR}/utility/indirect_cache.cpp
 			${SRC_DIR}/utility/latency.hpp ${SRC_DIR}/utility/latency.cpp
             ${SRC_DIR}/utility/pass_timer.hpp ${SRC_DIR}/utility/pass_timer.cpp
             ${SRC_DIR}/utility/shadow_cache.hpp ${SRC_DIR}/utility/shadow_cache.cpp
@@ -753,13 +754,14 @@ message("=======================================================================
                 utility/deferred_local_light.frag
                 utility/deferred_local_light_with_shadow.frag
 
-				utility/shadow_indirect_downsample.vert
-                utility/shadow_indirect_downsample.geom
-				utility/shadow_indirect_downsample.frag
+				utility/indirect_capture.vert
+				utility/indirect_capture.frag
+				
+				utility/indirect_injection.vert
+				utility/indirect_injection.geom
+				utility/indirect_injection.frag
 
-				utility/shadow_indirect.vert
-                utility/shadow_indirect.geom
-				utility/shadow_indirect.frag
+				utility/indirect_propagation.comp
 
                 utility/shadow.vert
                 utility/shadow.geom
diff --git a/cmake/Shaders.cmake b/cmake/Shaders.cmake
index 06d9d111495a6aa23f0b7398698641bea9bdac6b..f7a56645bdcc65b71c7ecc1574a286094e9dc33b 100644
--- a/cmake/Shaders.cmake
+++ b/cmake/Shaders.cmake
@@ -16,6 +16,8 @@ function(shader_extension_to_name name extension)
         set(${name} "geometry" PARENT_SCOPE)
     elseif (extension STREQUAL ".frag")
         set(${name} "fragment" PARENT_SCOPE)
+	elseif (extension STREQUAL ".comp")
+        set(${name} "compute" PARENT_SCOPE)
     else ()
         message(SEND_ERROR "Invalid extension: ${extension}")
     endif ()
diff --git a/res/dpr/data/indirect_data.inc b/res/dpr/data/indirect_data.inc
new file mode 100644
index 0000000000000000000000000000000000000000..6e4a52462b7e607dccc4046788a867fc7baae772
--- /dev/null
+++ b/res/dpr/data/indirect_data.inc
@@ -0,0 +1,16 @@
+#ifndef SHADER_INCLUDE_INDIRECT_DATA
+#define SHADER_INCLUDE_INDIRECT_DATA
+
+struct IndirectDomain
+{
+    uvec3 resolution;
+    float cell_size;
+
+    vec3 min;
+    uint padding1;
+
+    vec3 max;
+    uint padding2;
+};
+
+#endif
\ No newline at end of file
diff --git a/res/dpr/data/light_data.inc b/res/dpr/data/light_data.inc
index f75a73befd98994634d8f6f81f05cb764559413e..30b641e9c267413a0b3e00df2c4e89aa72356054 100644
--- a/res/dpr/data/light_data.inc
+++ b/res/dpr/data/light_data.inc
@@ -31,6 +31,7 @@ struct LightData
     uint padding;
 
     mat4 shadow_matrix[MAX_LIGHT_SHADOW_MATRIX_COUNT];
+    mat4 inv_shadow_matrix[MAX_LIGHT_SHADOW_MATRIX_COUNT];
 };
 
 #endif
\ No newline at end of file
diff --git a/res/dpr/data/shadow_data.inc b/res/dpr/data/shadow_data.inc
index d6b58e444db91f272ea230638e049f391c3b249e..bf280dafc8636640fc67304f7e18b85fa03cb200 100644
--- a/res/dpr/data/shadow_data.inc
+++ b/res/dpr/data/shadow_data.inc
@@ -1,24 +1,11 @@
 #ifndef SHADER_INCLUDE_SHADOW_DATA
 #define SHADER_INCLUDE_SHADOW_DATA
 
-#define MAX_INDIRECT_SAMPLE_COUNT 128
-
 struct ShadowParameter
 {
-    uint use_directional_shadow;
-    uint use_directional_indirect;
-    uint use_spot_shadow;
-    uint use_point_shadow;
-
-    uint indirect_sample_count;
-};
-
-struct IndirectSample
-{
-    vec2 coord;
-    float weight;
-
-    uint padding;
+    uint use_directional;
+    uint use_spot;
+    uint use_point;
 };
 
 #endif
\ No newline at end of file
diff --git a/res/dpr/utility/shadow_indirect.frag b/res/dpr/utility/indirect_capture.frag
similarity index 56%
rename from res/dpr/utility/shadow_indirect.frag
rename to res/dpr/utility/indirect_capture.frag
index 29f56f1b41f329ec56860a9451430eec45c96cc4..13f91c7c05e81b7f2d15be95d1ea644c3ede23cf 100644
--- a/res/dpr/utility/shadow_indirect.frag
+++ b/res/dpr/utility/indirect_capture.frag
@@ -7,21 +7,20 @@
 #include "math_library.glsl"
 #include "material_library.glsl"
 
-layout(location = 0) out vec4 outPosition;
-layout(location = 1) out vec4 outNormal;
-layout(location = 2) out vec4 outFlux;
+layout(location = 0) out vec4 outFlux;
+layout(location = 1) out vec2 outNormal;
 
 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(set = 0, binding = 0) uniform LightParameterBuffer
+layout(set = 1, binding = 0) uniform LightParameterBuffer
 {
     LightParameter light_parameter;
 };
 
-layout(set = 0, binding = 1, std430) readonly buffer LightDataBuffer
+layout(set = 1, binding = 1, std430) readonly buffer LightDataBuffer
 {
     LightData lights[];
 };
@@ -29,15 +28,15 @@ layout(set = 0, binding = 1, std430) readonly buffer LightDataBuffer
 layout(push_constant) uniform Constants
 {
     uint light_index;
-    uint indirect_resolution;
+    uint capture_resolution;
 };
 
-float comnpute_indirect_area()
+float comnpute_pixel_area()
 {
     vec2 near_size = lights[light_index].near_size;
-    vec2 pixel_size = near_size / float(indirect_resolution);
+    vec2 pixel_size = near_size / float(capture_resolution);
 
-    return pixel_size.x * pixel_size.y; //Where pixel_size is in meters
+    return pixel_size.x * pixel_size.y; //NOTE: Where pixel_size is in meters
 }
 
 void main()
@@ -51,7 +50,6 @@ void main()
 
     vec3 normal = lookup_normal(inUV, inNormal, inTangent);
 
-    outPosition = vec4(inPos, 0.0);
-    outNormal = vec4(normalize(inNormal), 0.0);
-    outFlux = vec4(lights[light_index].color * base_color_opacity.xyz * comnpute_indirect_area(), 1.0); //where color is in watt/meter^2 and where outFlux is in watt
+    outFlux = vec4(lights[light_index].color * base_color_opacity.xyz * comnpute_pixel_area(), 1.0); //NOTE: Where color is in watt/meter^2 and where outFlux is in watt
+    outNormal = encode_normal(normal);
 }
\ No newline at end of file
diff --git a/res/dpr/utility/shadow_indirect.vert b/res/dpr/utility/indirect_capture.vert
similarity index 82%
rename from res/dpr/utility/shadow_indirect.vert
rename to res/dpr/utility/indirect_capture.vert
index 2eccc9c721eae4247718ace4481096620d1087da..56fa702bc96dad85633cfa672e69b79e2f32f096 100644
--- a/res/dpr/utility/shadow_indirect.vert
+++ b/res/dpr/utility/indirect_capture.vert
@@ -4,25 +4,25 @@
 #include "light_data.inc"
 #include "mesh_data.inc"
 
-layout(set = 0, binding = 0) uniform LightParameterBuffer
+layout(set = 0, binding = 0) uniform MeshBuffer
 {
-    LightParameter light_parameter;
+    MeshData mesh;
 };
 
-layout(set = 0, binding = 1, std430) readonly buffer LightDataBuffer
+layout(set = 1, binding = 0) uniform LightParameterBuffer
 {
-    LightData lights[];
+    LightParameter light_parameter;
 };
 
-layout(set = 1, binding = 0) uniform MeshBuffer
+layout(set = 1, binding = 1, std430) readonly buffer LightDataBuffer
 {
-    MeshData mesh;
+    LightData lights[];
 };
 
 layout(push_constant) uniform Constants
 {
     uint light_index;
-    uint indirect_resolution;
+    uint capture_resolution;
 };
 
 layout(location = 0) in vec3 inPos;
diff --git a/res/dpr/utility/indirect_injection.frag b/res/dpr/utility/indirect_injection.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ad92641f3d7778bb3e1df5f6347fbfbf5a9c4343
--- /dev/null
+++ b/res/dpr/utility/indirect_injection.frag
@@ -0,0 +1,22 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "indirect_library.glsl"
+
+layout(location = 0) out vec4 outRedDistribution;
+layout(location = 0) out vec4 outGreenDistribution;
+layout(location = 0) out vec4 outBlueDistribution;
+
+layout(location = 0) in vec3 inFlux;
+layout(location = 0) in vec3 inNormal;
+
+void main()
+{
+    vec3 normal = normalize(inNormal);
+    vec4 distribution = spherical_harmonic_cosine_lobe(normal);
+
+    //NOTE: Where inFlux is in watt
+    outRedDistribution = inFlux.x * distribution;   //NOTE: Additive blending
+    outGreenDistribution = inFlux.y * distribution; //NOTE: Additive blending
+    outBlueDistribution = inFlux.z * distribution;  //NOTE: Additive blending
+}
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_injection.geom b/res/dpr/utility/indirect_injection.geom
new file mode 100644
index 0000000000000000000000000000000000000000..4c27989fc5e2b2a845ed5cc3a9c480454ad95817
--- /dev/null
+++ b/res/dpr/utility/indirect_injection.geom
@@ -0,0 +1,33 @@
+#version 450 core
+
+#include "indirect_data.inc"
+
+layout(points) in;
+layout(points, max_vertices = 1) out;
+
+layout(set = 1, binding = 3) uniform IndirectDomainBuffer
+{
+    IndirectDomain indirect_domain;
+};
+
+layout(location = 0) in vec3 inFlux[];
+layout(location = 1) in vec3 inNormal[];
+
+layout(location = 0) out vec3 outFlux;
+layout(location = 1) out vec3 outNormal;
+
+void main()
+{
+    vec3 position = gl_in[0].gl_Position;
+    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;
+
+    outFlux = inFlux[0];
+    outNormal = inNormal[0];
+    gl_Position = vec3(position.xy, 0.0, 1.0);
+    gl_Layer = int(position.z);
+
+    EmitVertex();
+    EmitPrimitive();
+}
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_injection.vert b/res/dpr/utility/indirect_injection.vert
new file mode 100644
index 0000000000000000000000000000000000000000..bddcb199318fffba755b01ea77b2e6a85c3d021d
--- /dev/null
+++ b/res/dpr/utility/indirect_injection.vert
@@ -0,0 +1,52 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "light_data.inc"
+#include "indirect_data.inc"
+#include "math_library.glsl"
+
+layout(set = 0, binding = 0) uniform LightParameterBuffer
+{
+    LightParameter light_parameter;
+};
+
+layout(set = 0, binding = 1, std430) readonly buffer LightDataBuffer
+{
+    LightData lights[];
+};
+
+layout(set = 1, binding = 0) uniform sampler2D sampler_capture_depth;
+layout(set = 1, binding = 1) uniform sampler2D sampler_capture_flux;
+layout(set = 1, binding = 2) uniform sampler2D sampler_capture_normal;
+
+layout(push_constant) uniform Constants
+{
+    uint light_index;
+    uint capture_resolution;
+    float cell_size;
+};
+
+layout(location = 0) out vec3 outFlux;
+layout(location = 1) out vec3 outNormal;
+
+void main()
+{
+    ivec2 coord = ivec2(gl_VertexId / capture_resolution, gl_VertexId % capture_resolution);
+
+    float depth = texelFetch(sampler_capture_depth, coord, 0).x;
+    vec3 flux = texelFetch(sampler_capture_flux, coord, 0).xyz;
+    vec3 normal = decode_normal(texelFetch(sampler_capture_normal, coord, 0).xy);
+
+    vec3 screen_coord = vec3(vec2(coord + 0.5) / capture_resolution, depth);
+    screen_coord = (screen_coord + 1.0) / 2.0;
+
+    vec4 position = lights[light_index].inv_shadow_matrix[0] * vec4(screen_coord, 1.0);
+    position /= position.w;
+
+    //NOTE: Shift the virtual point light along the normal in order to avoid self lighting and shadowing
+    position += 0.5 * cell_size * normal;
+
+    outFlux = flux;
+    outNormal = normal;
+    gl_Position = position;
+}
diff --git a/res/dpr/utility/indirect_library.glsl b/res/dpr/utility/indirect_library.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..0051b174d56e27c9cdc14587259f206e2756012b
--- /dev/null
+++ b/res/dpr/utility/indirect_library.glsl
@@ -0,0 +1,36 @@
+#ifndef SHADER_INCLUDE_INDIRECT_LIBRARY
+#define SHADER_INCLUDE_INDIRECT_LIBRARY
+
+#include "math_library.glsl"
+
+//NOTE: Standard factors for real valued spherical harmonics making them orhonormal.
+#define SPHERICAL_HARMONIC_BAND0_FACTOR (0.5 * sqrt(1.0 / PI))
+#define SPHERICAL_HARMONIC_BAND1_FACTOR sqrt(3.0 / (4.0 * PI))
+
+//NOTE: The given factors where computed for the function I(theta, phi) = max(cos(theta), 0) / PI.
+//      This function was choosen, since the integral over it is one.
+//      Therefore the function describes a distribution.
+#define SPHERICAL_HARMONIC_COSINE_LOBE_BAND0_FACTOR (0.5 * sqrt(1.0 / PI))
+#define SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR sqrt(1.0 / (3.0 * PI))
+
+//NOTE: Assume direction is allready normalize
+float spherical_harmonic_evaluate(vec3 direction, vec4 spherical_harmonic)
+{
+    return vec4(SPHERICAL_HARMONIC_COSINE_LOBE_BAND0_FACTOR,
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * (-direction.y),
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * direction.z,
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * (-direction.x));
+}
+
+//NOTE: Assume direction is allready normalize
+//      The compute spherical harmonic represents the function I(theta, phi) = max(cos(theta), 0) / PI,
+//      when it is rotated towards "direction".
+vec4 spherical_harmonic_cosine_lobe(vec3 direction)
+{
+    return vec4(SPHERICAL_HARMONIC_COSINE_LOBE_BAND0_FACTOR,
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * (-direction.y),
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * direction.z,
+                SPHERICAL_HARMONIC_COSINE_LOBE_BAND1_FACTOR * (-direction.x));
+}
+
+#endif
\ No newline at end of file
diff --git a/res/dpr/utility/indirect_propagation.comp b/res/dpr/utility/indirect_propagation.comp
new file mode 100644
index 0000000000000000000000000000000000000000..787ce2759ecfc61d2762b5732f19ce89f0391084
--- /dev/null
+++ b/res/dpr/utility/indirect_propagation.comp
@@ -0,0 +1,138 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "indirect_library.glsl"
+
+layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
+
+layout(set = 0, binding = 0, rgba16f) uniform readonly image3D image_src_red_distribution;
+layout(set = 0, binding = 1, rgba16f) uniform readonly image3D image_src_green_distribution;
+layout(set = 0, binding = 2, rgba16f) uniform readonly image3D image_src_blue_distribution;
+
+layout(set = 0, binding = 3, rgba16f) uniform writeonly image3D image_dst_red_distribution;
+layout(set = 0, binding = 4, rgba16f) uniform writeonly image3D image_dst_green_distribution;
+layout(set = 0, binding = 5, rgba16f) uniform writeonly image3D image_dst_blue_distribution;
+
+layout(set = 0, binding = 6, rgba16f) uniform image3D image_red_indirect;
+layout(set = 0, binding = 7, rgba16f) uniform image3D image_green_indirect;
+layout(set = 0, binding = 8, rgba16f) uniform image3D image_blue_indirect;
+
+const ivec3 neighbour_offsets[6] = ivec3[6]
+(
+    ivec3( 1, 0, 0), //+X
+    ivec3(-1, 0, 0), //-X
+    ivec3( 0, 1, 0), //+Y
+    ivec3( 0,-1, 0), //-Y
+    ivec3( 0, 0, 1), //+Z
+    ivec3( 0, 0,-1)  //-Z
+);
+
+const mat3 neighbour_orientations = mat3[6]
+(
+    mat3(vec3(-1.0, 0.0, 0.0), vec3( 0.0, 1.0, 0.0), vec3( 0.0, 0.0, 1.0)), //+X
+    mat3(vec3( 1.0, 0.0, 0.0), vec3( 0.0, 1.0, 0.0), vec3( 0.0, 0.0, 1.0)), //-X
+    mat3(vec3( 0.0, 1.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3( 0.0, 0.0, 1.0)), //+Y
+    mat3(vec3( 0.0, 1.0, 0.0), vec3( 1.0, 0.0, 0.0), vec3( 0.0, 0.0, 1.0)), //-Y
+    mat3(vec3( 0.0, 0.0, 1.0), vec3( 0.0, 1.0, 0.0), vec3(-1.0, 0.0, 0.0)), //+Z
+    mat3(vec3( 0.0, 0.0, 1.0), vec3( 0.0, 1.0, 0.0), vec3( 1.0, 0.0, 0.0))  //-Z
+);
+
+//NOTE: All directions derived for the case -X
+//      A side eval direction correspond to the center directions of that intensity cone.
+const vec3 direct_eval_direction = vec3(1.0, 0.0, 0.0);
+const vec3 side_eval_directions[4] = vec3[4]
+(
+    vec3(0.85065080, 0.52573111, 0.0),
+    vec3(0.85065080,-0.52573111, 0.0),
+    vec3(0.85065080, 0.0, 0.52573111),
+    vec3(0.85065080, 0.0,-0.52573111)
+);
+
+//NOTE: All directions derived for the case -X
+const vec3 direct_emit_direction = vec3(1.0, 0.0, 0.0);
+const vec3 side_emit_directions[4] = vec3[4]
+(
+    vec3(0.0, 1.0, 0.0),
+    vec3(0.0,-1.0, 0.0),
+    vec3(0.0, 0.0, 1.0),
+    vec3(0.0, 0.0,-1.0)
+);
+
+//NOTE: Solid angle for direct and side face.
+const float direct_solid_angle = 0.40066968;
+const float side_solid_angle = 0.42343135;
+
+void propagate_neighbour(ivec3 cell_coord, uint neighbour, inout vec4 red_distribution, inout vec4 green_distribution, inout vec4 blue_distribution)
+{
+    ivec3 neighbour_coord = cell_coord + neighbour_offset[neighbour];
+    mat3 neighbour_orientation = neighbour_orientations[neighbour];
+
+    vec4 neighbour_red_distribution = image_load(image_src_red_distribution, neighbour_coord);
+    vec4 neighbour_green_distribution = image_load(image_src_green_distribution, neighbour_coord);
+    vec4 neighbour_blue_distribution = image_load(image_src_blue_distribution, neighbour_coord);
+
+    //----- Side Faces ----------------------------------------------
+
+    for(uint side = 0; side < 4; side++)
+    {
+        vec3 eval_direction = neighbour_orientation * side_eval_directions[side];
+        vec3 emit_direction = neighbour_orientation * side_emit_directions[side];
+
+        vec3 intensity = vec3(0.0); //NOTE: Where intensity is in watt / str
+        intensity.x = spherical_harmonic_evaluate(eval_direction, neighbour_red_distribution);
+        intensity.y = spherical_harmonic_evaluate(eval_direction, neighbour_green_distribution);
+        intensity.z = spherical_harmonic_evaluate(eval_direction, neighbour_blue_distribution);
+
+        //NOTE: Where side_solid_angle * intensity is in watt
+        red_distribution += (side_solid_angle * intensity.x) * spherical_harmonic_cosine_lobe(emit_direction);
+        green_distribution += (side_solid_angle * intensity.y) * spherical_harmonic_cosine_lobe(emit_direction);
+        bluedistribution += (side_solid_angle * intensity.z) * spherical_harmonic_cosine_lobe(emit_direction);
+    }
+
+    //----- Direct Face -----------------------------------------------
+
+    vec3 eval_direction = neighbour_orientation * direct_eval_direction;
+    vec3 emit_direction = neighbour_orientation * direct_emit_direction;
+
+    vec3 intensity = vec3(0.0); //NOTE: Where intensity is in watt / str
+    intensity.x = spherical_harmonic_evaluate(eval_direction, neighbour_red_distribution);
+    intensity.y = spherical_harmonic_evaluate(eval_direction, neighbour_green_distribution);
+    intensity.z = spherical_harmonic_evaluate(eval_direction, neighbour_blue_distribution);
+
+    //NOTE: Where side_solid_angle * intensity is in watt
+    red_distribution += (direct_solid_angle * intensity.x) * spherical_harmonic_cosine_lobe(emit_direction);
+    green_distribution += (direct_solid_angle * intensity.y) * spherical_harmonic_cosine_lobe(emit_direction);
+    bluedistribution += (direct_solid_angle * intensity.z) * spherical_harmonic_cosine_lobe(emit_direction);
+
+    //-----------------------------------------------------------------
+}
+
+void main()
+{
+    ivec3 cell_coord = ivec3(gl_GlobalInvocationID);
+
+    vec4 red_distribution = vec4(0.0);
+    vec4 green_distribution = vec4(0.0);
+    vec4 blue_distribution = vec4(0.0);
+
+    for(uint neighbour = 0; neighbour < 6; neighbour++)
+    {
+        propagate_neighbour(cell_coord, neighbour, red_distribution, green_distribution, blue_distribution);
+    }
+
+    image_store(image_dst_red_distribution, cell_coord, red_distribution);
+    image_store(image_dst_green_distribution, cell_coord, green_distribution);
+    image_store(image_dst_blue_distribution, cell_coord, blue_distribution);
+
+    vec4 red_indirect = image_load(image_red_indirect, cell_coord);
+    vec4 green_indirect = image_load(image_green_indirect, cell_coord);
+    vec4 blue_indirect = image_load(image_blue_indirect, cell_coord);
+
+    red_indirect += red_distribution;
+    green_indirect += green_distribution;
+    blue_indirect += blue_distribution;
+
+    image_store(image_red_indirect, cell_coord, red_indirect);
+    image_store(image_green_indirect, cell_coord, green_indirect);
+    image_store(image_blue_indirect, cell_coord, blue_indirect);
+}
\ No newline at end of file
diff --git a/res/dpr/utility/light_library.glsl b/res/dpr/utility/light_library.glsl
index 562fb0eee1d175f756833b940c587a2943ed8f06..f6cd270da3e672c20193b37a120cadb2166254cd 100644
--- a/res/dpr/utility/light_library.glsl
+++ b/res/dpr/utility/light_library.glsl
@@ -49,71 +49,29 @@ layout(set = SHADOW_DESCRIPTOR_SET, binding = 0) uniform sampler2DArrayShadow sa
 layout(set = SHADOW_DESCRIPTOR_SET, binding = 1) uniform sampler2DArrayShadow sampler_shadow_spot;
 layout(set = SHADOW_DESCRIPTOR_SET, binding = 2) uniform samplerCubeArrayShadow sampler_shadow_point;
 
-layout(set = SHADOW_DESCRIPTOR_SET, binding = 3) uniform sampler2DArray sampler_indirect_position_directional;
-layout(set = SHADOW_DESCRIPTOR_SET, binding = 4) uniform sampler2DArray sampler_indirect_normal_directional;
-layout(set = SHADOW_DESCRIPTOR_SET, binding = 5) uniform sampler2DArray sampler_indirect_flux_directional;
-
-layout(set = SHADOW_DESCRIPTOR_SET, binding = 6) uniform ShadowParameterBuffer
+layout(set = SHADOW_DESCRIPTOR_SET, binding = 4 uniform ShadowParameterBuffer
 {
     ShadowParameter shadow_parameter;
 };
+#endif
 
-layout(set = SHADOW_DESCRIPTOR_SET, binding = 7) uniform IndirectSampleBuffer
+#ifdef INDIRECT_DESCRIPTOR_SET
+layout(set = INDIRECT_DESCRIPTOR_SET, binding = 0) uniform sampler3D sampler_indirect_red;
+layout(set = INDIRECT_DESCRIPTOR_SET, binding = 1) uniform sampler3D sampler_indirect_green;
+layout(set = INDIRECT_DESCRIPTOR_SET, binding = 2) uniform sampler3D sampler_indirect_blue;
+
+layout(set = INDIRECT_DESCRIPTOR_SET, binding = 3) uniform IndirectDomainBuffer
 {
-    IndirectSample indirect_samples[MAX_INDIRECT_SAMPLE_COUNT];
+    IndirectDomain indirect_domain;
 };
 #endif
 
 //-- Indirect Functions ----------------------------------------------------------------------------
 
-#ifdef SHADOW_DESCRIPTOR_SET
-//This function computes the indirect irradiance arriving at the given surface location.
-//The final unit of this function is therefore (watts/meter^2).
-vec2 compute_indirect_scale(uint light)
+#ifdef INDIRECT_DESCRIPTOR_SET
+vec3 compute_indirect(vec3 surface_position, vec3 surface_normal)
 {
-    vec2 near_size = lights[light].near_size;
     
-    return 1.0 / near_size;
-}
-
-vec3 compute_indirect(uint light, vec3 surface_position, vec3 surface_normal)
-{
-    uint light_type = lights[light].type;
-
-    if (light_type == LIGHT_TYPE_DIRECTIONAL && shadow_parameter.use_directional_indirect != 0)
-    {
-        uint layer = lights[light].type_index;
-        vec2 indirect_scale = compute_indirect_scale(light);
-
-        vec4 center_position = lights[light].shadow_matrix[0] * vec4(surface_position, 1.0);
-        center_position /= center_position.w;
-        center_position.xy = (center_position.xy + 1.0) / 2.0;
-
-        vec3 irradiance = vec3(0.0);
-
-        for(uint index = 0; index < shadow_parameter.indirect_sample_count; index++)
-        {
-            vec2 sample_coord = center_position.xy + indirect_scale * indirect_samples[index].coord;
-            float sample_weight = indirect_samples[index].weight; //Assume that the sum of all weights is one
-            float sample_level = max(0.0, log2(length(indirect_samples[index].coord)) * 2.0);
-
-            vec3 sample_position = textureLod(sampler_indirect_position_directional, vec3(sample_coord, layer), sample_level).xyz;
-            vec3 sample_normal = normalize(textureLod(sampler_indirect_normal_directional, vec3(sample_coord, layer), sample_level).xyz);
-            vec4 sample_flux = textureLod(sampler_indirect_flux_directional, vec3(sample_coord, layer), sample_level);
-
-            float weight = smoothstep(0.95, 1.0, sample_flux.w);
-
-            vec3 sample_direction = sample_position - surface_position;
-            float sample_distance_square = dot(sample_direction, sample_direction);
-
-            float factor1 = 1.0;//max(0.0, dot(sample_normal, -sample_direction));
-            float factor2 = max(0.0, dot(surface_normal, sample_direction));
-
-            irradiance += weight * (sample_flux.xyz * factor1 * factor2 * sample_weight) / pow(sample_distance_square, 2);
-        }
-
-        return irradiance * 10;// * 50;
-    }
 
     return vec3(0.0);
 }
@@ -165,7 +123,7 @@ float get_shadow(uint light, vec3 surface_position)
 {
     uint light_type = lights[light].type;
 
-    if (light_type == LIGHT_TYPE_DIRECTIONAL && shadow_parameter.use_directional_shadow != 0)
+    if (light_type == LIGHT_TYPE_DIRECTIONAL && shadow_parameter.use_directional != 0)
     {
         uint layer = lights[light].type_index;
 
@@ -176,7 +134,7 @@ float get_shadow(uint light, vec3 surface_position)
         return texture(sampler_shadow_directional, vec4(shadow_position.xy, layer, shadow_position.z - 0.008)).x;
     }
 
-    else if (light_type == LIGHT_TYPE_SPOT && shadow_parameter.use_spot_shadow != 0)
+    else if (light_type == LIGHT_TYPE_SPOT && shadow_parameter.use_spot != 0)
     {
         uint layer = lights[light].type_index;
 
@@ -187,7 +145,7 @@ float get_shadow(uint light, vec3 surface_position)
         return texture(sampler_shadow_spot, vec4(shadow_position.xy, layer, shadow_position.z - 0.001)).x;
     }
     
-    else if (light_type == LIGHT_TYPE_POINT && shadow_parameter.use_point_shadow != 0)
+    else if (light_type == LIGHT_TYPE_POINT && shadow_parameter.use_point != 0)
     {
         uint layer = lights[light].type_index;
         uint frustum = get_shadow_frustum(light, surface_position);
@@ -348,9 +306,13 @@ vec3 apply_lighting(vec3 view_position, vec3 surface_position, vec3 surface_norm
 {
     vec3 lighting = material.emissive; //where emissive is in watts/meter^2 *str
     
-    if (dot(lighting, vec3(1.0)) < EPSILON) //apply direct lighting only if the material is not emissive
+    if (dot(lighting, vec3(1.0)) < EPSILON) //apply direct and indirect lighting only if the material is not emissive
     {
-        //lighting += material.base_color * light_parameter.ambient; //where ambient is in watts/meter^2 *str
+        lighting += material.base_color * light_parameter.ambient; //where ambient is in watts/meter^2 *str
+
+#ifdef INDIRECT_DESCRIPTOR_SET
+        lighting += compute_indirect(surface_position, surface_normal);
+#endif
 
         for (uint light = 0; light < light_parameter.light_count; light++)
         {
@@ -358,16 +320,11 @@ vec3 apply_lighting(vec3 view_position, vec3 surface_position, vec3 surface_norm
             vec3 normal_direction = normalize(surface_normal);
             vec3 light_direction = get_light_direction(light, surface_position);
 
-    #ifdef SHADOW_DESCRIPTOR_SET
-            //lighting += compute_brdf(light_direction, normal_direction, view_direction, material) * get_light_radiance(light, surface_position) * get_shadow(light, surface_position) * dot(normal_direction, light_direction);  
-
-            //Eventough the final unit of the function compute_indirect(...) is watts/meters^2, the unit can be changed to radiance.
-            //This is due to the fact that, that the function accumulates multiple virtual point lights.
-            //These virtual point light contribute like dirca impulse during the accumulation.
-            lighting += /*compute_brdf_diffuse(material.base_color) */ compute_indirect(light, surface_position, normal_direction);
-    #else
+#ifdef SHADOW_DESCRIPTOR_SET
+            lighting += compute_brdf(light_direction, normal_direction, view_direction, material) * get_light_radiance(light, surface_position) * get_shadow(light, surface_position) * dot(normal_direction, light_direction);  
+#else
             lighting += compute_brdf(light_direction, normal_direction, view_direction, material) * get_light_radiance(light, surface_position) * dot(normal_direction, light_direction);
-    #endif
+#endif
         }
 
         lighting *= (1.0 - material.occlusion);
diff --git a/res/dpr/utility/shadow_indirect.geom b/res/dpr/utility/shadow_indirect.geom
deleted file mode 100644
index 3e119ff410dd76cf3c0d63d06c79864fdd1d6e4e..0000000000000000000000000000000000000000
--- a/res/dpr/utility/shadow_indirect.geom
+++ /dev/null
@@ -1,38 +0,0 @@
-#version 450 core
-
-layout(triangles) in;
-layout(triangle_strip, max_vertices = 3) out;
-
-layout(push_constant) uniform Constants
-{
-    uint light_index;
-    uint indirect_resolution;
-};
-
-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 outPos;
-layout(location = 1) out vec3 outNormal;
-layout(location = 2) out vec3 outTangent;
-layout(location = 3) out vec2 outUV;
-
-void main()
-{
-    for (int index = 0; index < 3; index++)
-    {
-        gl_Position = gl_in[index].gl_Position;
-        gl_Layer = int(light_index);
-
-        outPos = inPos[index];
-        outNormal = inNormal[index];
-        outTangent = inTangent[index];
-        outUV = inUV[index];
-
-        EmitVertex();
-    }
-
-    EndPrimitive();
-}
\ No newline at end of file
diff --git a/res/dpr/utility/shadow_indirect_downsample.frag b/res/dpr/utility/shadow_indirect_downsample.frag
deleted file mode 100644
index eef9bb708faa478c35e7cc81562aded61c2c5c13..0000000000000000000000000000000000000000
--- a/res/dpr/utility/shadow_indirect_downsample.frag
+++ /dev/null
@@ -1,52 +0,0 @@
-#version 450 core
-
-layout(location = 0) out vec4 outPosition;
-layout(location = 1) out vec4 outNormal;
-layout(location = 2) out vec4 outFlux;
-
-layout(set = 0, binding = 0) uniform sampler2DArray sampler_indirect_position;
-layout(set = 0, binding = 1) uniform sampler2DArray sampler_indirect_normal;
-layout(set = 0, binding = 2) uniform sampler2DArray sampler_indirect_flux;
-
-void main()
-{
-    ivec3 base_coord = ivec3(2 * ivec2(gl_FragCoord.xy), gl_Layer);
-
-    vec3 flux = vec3(0.0);
-    vec3 position = vec3(0.0);
-    vec3 normal = vec3(0.0);
-    uint sample_count = 0;
-
-    for(uint x = 0; x < 2; x++)
-    {
-        for(uint y = 0; y < 2; y++)
-        {
-            ivec3 sample_coord = base_coord + ivec3(x, y, 0);
-            vec3 sample_flux = texelFetch(sampler_indirect_flux, sample_coord, 0).xyz;
-
-            if(dot(sample_flux, sample_flux) > 0.0)
-            {
-                vec3 sample_position = texelFetch(sampler_indirect_position, sample_coord, 0).xyz;
-                vec3 sample_normal = texelFetch(sampler_indirect_normal, sample_coord, 0).xyz;
-
-                flux += sample_flux;
-                position += sample_position;
-                normal += normalize(sample_normal);
-                sample_count++;
-            }
-        }
-    }
-
-    float weight = 0.0;
-
-    if(sample_count > 0)
-    {
-        position /= float(sample_count);
-        normal = normalize(normal);
-        weight = 1.0;
-    }
-
-    outPosition = vec4(position, 0.0);
-    outNormal = vec4(normal, 0.0);
-    outFlux = vec4(flux, weight);
-}
\ No newline at end of file
diff --git a/res/dpr/utility/shadow_indirect_downsample.geom b/res/dpr/utility/shadow_indirect_downsample.geom
deleted file mode 100644
index b27fe35e843b8ce51a3196ebc35d308aa6678346..0000000000000000000000000000000000000000
--- a/res/dpr/utility/shadow_indirect_downsample.geom
+++ /dev/null
@@ -1,19 +0,0 @@
-#version 450 core
-
-layout(triangles) in;
-layout(triangle_strip, max_vertices = 3) out;
-
-layout(location = 0) in flat uint inLayer[];
-
-void main()
-{
-    for (int index = 0; index < 3; index++)
-    {
-        gl_Position = gl_in[index].gl_Position;
-        gl_Layer = int(inLayer[0]);
-
-        EmitVertex();
-    }
-
-    EndPrimitive();
-}
\ No newline at end of file
diff --git a/res/dpr/utility/shadow_indirect_downsample.vert b/res/dpr/utility/shadow_indirect_downsample.vert
deleted file mode 100644
index 85bae227390bcc528c86f874a1d41fe2b402c2c7..0000000000000000000000000000000000000000
--- a/res/dpr/utility/shadow_indirect_downsample.vert
+++ /dev/null
@@ -1,11 +0,0 @@
-#version 450 core
-
-layout(location = 0) out flat uint outLayer;
-
-void main()
-{
-    outLayer = gl_InstanceIndex;
-
-    vec2 coord = vec2(gl_VertexIndex % 2, gl_VertexIndex / 2);
-    gl_Position = vec4(coord * 2.0 - 1.0, 0.0, 1.0);
-}
\ No newline at end of file
diff --git a/src/scene.cpp b/src/scene.cpp
index 0ba440c166903ff699426e34f72e4604bc05ecba..b8c0657b1052cdb8f5a37a9a517d4a2653862be1 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -478,6 +478,16 @@ const std::vector<SceneAnimation>& Scene::get_animations() const
     return this->animations;
 }
 
+const glm::vec3& Scene::get_scene_min() const
+{
+    return this->scene_min;
+}
+
+const glm::vec3& Scene::get_scene_max() const
+{
+    return this->scene_max;
+}
+
 void Scene::set_vertex_input(lava::graphics_pipeline* pipeline)
 {
     pipeline->set_vertex_input_binding({ 0, sizeof(lava::vertex), VK_VERTEX_INPUT_RATE_VERTEX });
@@ -1469,14 +1479,16 @@ void Scene::compute_light_matrices(SceneLight& light)
         for (uint32_t index = 0; index < directions.size(); index++)
         {
             glm::mat4 view_matrix = glm::lookAt(light_data.position, light_data.position + directions[index], sides[index]);
+            glm::mat4 shadow_matrix = projection_matrix * view_matrix;
 
-            light_data.shadow_matrix[index] = projection_matrix * view_matrix;
+            light_data.shadow_matrix[index] = shadow_matrix;
+            light_data.inv_shadow_matrix[index] = glm::inverse(shadow_matrix);
         }
     }
 
     else if (light_data.type == LIGHT_TYPE_DIRECTIONAL)
     {
-        glm::vec3 direction = /*glm::diagonal4x4(glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f)) */ glm::vec4(glm::normalize(light_data.direction), 0.0f);
+        glm::vec3 direction = glm::normalize(light_data.direction);
         glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
 
         if (glm::dot(direction, up) > 0.9)
@@ -1514,9 +1526,11 @@ void Scene::compute_light_matrices(SceneLight& light)
 
         glm::vec2 near_size = light_max - light_min;
         glm::mat4 projection_matrix = glm::ortho(light_min.x, light_max.x, light_min.y, light_max.y, -light_max.z, -light_min.z);
+        glm::mat4 shadow_matrix = projection_matrix * view_matrix;
 
         light_data.near_size = near_size;
-        light_data.shadow_matrix[0] = projection_matrix * view_matrix;
+        light_data.shadow_matrix[0] = shadow_matrix;
+        light_data.inv_shadow_matrix[0] = glm::inverse(shadow_matrix);
     }
 
     else if (light_data.type == LIGHT_TYPE_SPOT)
@@ -1537,8 +1551,10 @@ void Scene::compute_light_matrices(SceneLight& light)
 
         glm::mat4 view_matrix = glm::lookAt(position, position + direction, side);
         glm::mat4 projection_matrix = glm::perspective(2.0f * angle, 1.0f, 10.0f, scene_diagonal);
+        glm::mat4 shadow_matrix = projection_matrix * view_matrix;
 
-        light_data.shadow_matrix[0] = projection_matrix * view_matrix;
+        light_data.shadow_matrix[0] = shadow_matrix;
+        light_data.inv_shadow_matrix[0] = glm::inverse(shadow_matrix);
     }
 }
 
diff --git a/src/scene.hpp b/src/scene.hpp
index e0e3b1756c409e56d21bb905e8fd3cb9e6556299..c746abfd28d7e0aadc403bf6b07eddb5630510ba 100644
--- a/src/scene.hpp
+++ b/src/scene.hpp
@@ -162,6 +162,9 @@ public:
     const std::vector<SceneMaterial>& get_materials() const;
     const std::vector<SceneAnimation>& get_animations() const;
 
+    const glm::vec3& get_scene_min() const;
+    const glm::vec3& get_scene_max() const;
+
     static void set_vertex_input(lava::graphics_pipeline* pipeline);
     static void set_vertex_input_only_position(lava::graphics_pipeline* pipeline);
 
diff --git a/src/utility/indirect_cache.cpp b/src/utility/indirect_cache.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..696d75854d8997324a8d69db5409f526fe97cbc6
--- /dev/null
+++ b/src/utility/indirect_cache.cpp
@@ -0,0 +1,974 @@
+#include "indirect_cache.hpp"
+
+namespace glsl
+{
+    using namespace glm;
+    using uint = glm::uint32;
+
+#include "res/dpr/data/indirect_data.inc"
+}
+
+bool IndirectCache::create(Scene::Ptr scene, const IndirectSettings& settings)
+{
+    lava::device_ptr device = scene->get_light_descriptor()->get_device();
+
+    this->compute_domain(scene, settings);
+
+    if (!this->create_buffers(device, settings))
+    {
+        return false;
+    }
+
+    if (!this->create_images(device, settings))
+    {
+        return false;
+    }
+
+    if (!this->create_samplers(device))
+    {
+        return false;
+    }
+
+    if (!this->create_descriptors(device))
+    {
+        return false;
+    }
+
+    if (!this->create_layouts(device, scene))
+    {
+        return false;
+    }
+
+    if (!this->create_capture_pass(device, settings))
+    {
+        return false;
+    }
+
+    if (!this->create_capture_pipeline(device))
+    {
+        return false;
+    }
+
+    if (!this->create_injection_pass(device))
+    {
+        return false;
+    }
+
+    if (!this->create_injection_pipeline(device))
+    {
+        return false;
+    }
+
+    if (!this->create_propagation_pipeline(device))
+    {
+        return false;
+    }
+
+	this->scene = scene;
+    this->settings = settings;
+
+    return true;
+}
+
+void IndirectCache::destroy()
+{
+    //TODO: ....
+}
+
+void IndirectCache::compute_indirect(VkCommandBuffer command_buffer, lava::index frame)
+{
+    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];
+
+    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());
+
+    VkClearColorValue clear_color;
+    clear_color.float32[0] = 0.0f;
+    clear_color.float32[1] = 0.0f;
+    clear_color.float32[2] = 0.0f;
+    clear_color.float32[3] = 0.0f;
+
+    VkImageSubresourceRange subresource_range;
+    subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    subresource_range.baseMipLevel = 0;
+    subresource_range.levelCount = 1;
+    subresource_range.baseArrayLayer = 0;
+    subresource_range.layerCount = 1;
+
+    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);
+
+    std::array<VkImageMemoryBarrier, 3> clear_end_barriers = IndirectCache::build_barriers(VK_ACCESS_TRANSFER_WRITE_BIT, 0, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+    clear_end_barriers[0].image = this->distribution_red_image[0];
+    clear_end_barriers[1].image = this->distribution_green_image[0];
+    clear_end_barriers[2].image = this->distribution_blue_image[0];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, clear_end_barriers.size(), clear_end_barriers.data());
+
+    for (uint32_t index = 0; index < lights.size(); index++)
+    {
+        const SceneLight& light = lights[index];
+       
+        if (light.data.type != LIGHT_TYPE_DIRECTIONAL)
+        {
+            continue;
+        }
+        
+        this->frame = frame;
+        this->light_index = index;
+        this->capture_pass->process(command_buffer, 0);
+        this->injection_pass->process(command_buffer, 0);
+    }
+
+    //NOTE: Memory barrier for source image provieded by injection render pass
+    //      The final image layout of distribution_..._image[0] has to be VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+
+    std::array<VkImageMemoryBarrier, 3> copy_barriers = IndirectCache::build_barriers(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+    copy_barriers[0].image = this->distribution_red_image[2];
+    copy_barriers[1].image = this->distribution_green_image[2];
+    copy_barriers[2].image = this->distribution_blue_image[2];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, copy_barriers.size(), copy_barriers.data());
+
+    VkImageSubresourceLayers subresource_layers;
+    subresource_layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    subresource_layers.mipLevel = 0;
+    subresource_layers.baseArrayLayer = 0;
+    subresource_layers.layerCount = 1;
+
+    VkOffset3D offset;
+    offset.x = 0;
+    offset.y = 0;
+    offset.z = 0;
+
+    VkExtent3D extend;
+    extend.width = this->domain_resolution.x;
+    extend.height = this->domain_resolution.y;
+    extend.depth = this->domain_resolution.z;
+
+    VkImageCopy image_copy;
+    image_copy.srcSubresource = subresource_layers;
+    image_copy.srcOffset = offset;
+    image_copy.dstSubresource = subresource_layers;
+    image_copy.dstOffset = offset;
+    image_copy.extent = extend;
+
+    vkCmdCopyImage(command_buffer, this->distribution_red_image[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, this->distribution_red_image[2], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+    vkCmdCopyImage(command_buffer, this->distribution_green_image[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, this->distribution_green_image[2], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+    vkCmdCopyImage(command_buffer, this->distribution_blue_image[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, this->distribution_blue_image[2], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
+
+    std::array<VkImageMemoryBarrier, 3> setup_src_barriers = IndirectCache::build_barriers(VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
+    setup_src_barriers[0].image = this->distribution_red_image[0];
+    setup_src_barriers[1].image = this->distribution_green_image[0];
+    setup_src_barriers[2].image = this->distribution_blue_image[0];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, setup_src_barriers.size(), setup_src_barriers.data());
+
+    std::array<VkImageMemoryBarrier, 3> setup_dst_barriers = IndirectCache::build_barriers(0, VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
+    setup_dst_barriers[0].image = this->distribution_red_image[1];
+    setup_dst_barriers[1].image = this->distribution_green_image[1];
+    setup_dst_barriers[2].image = this->distribution_blue_image[1];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, setup_dst_barriers.size(), setup_dst_barriers.data());
+
+    std::array<VkImageMemoryBarrier, 3> setup_indirect_barriers = IndirectCache::build_barriers(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);
+    setup_indirect_barriers[0].image = this->distribution_red_image[2];
+    setup_indirect_barriers[1].image = this->distribution_green_image[2];
+    setup_indirect_barriers[2].image = this->distribution_blue_image[2];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, setup_indirect_barriers.size(), setup_indirect_barriers.data());
+
+    for (uint32_t index = 0; index < this->propagation_iterations; index++)
+    {
+        uint32_t src_index = index % 2;
+        uint32_t dst_index = (index + 1) % 2;
+
+        this->propagation_pipeline->bind(command_buffer);
+        this->propagation_layout->bind(command_buffer, this->propagation_descriptor_set[src_index]);
+
+        vkCmdDispatch(command_buffer, this->domain_work_groups.x, this->domain_work_groups.y, this->domain_work_groups.z);
+        
+        std::array<VkImageMemoryBarrier, 3> compue_src_barriers = IndirectCache::build_barriers(VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
+        compue_src_barriers[0].image = this->distribution_red_image[src_index];
+        compue_src_barriers[1].image = this->distribution_green_image[src_index];
+        compue_src_barriers[2].image = this->distribution_blue_image[src_index];
+
+        vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, compue_src_barriers.size(), compue_src_barriers.data());
+
+        std::array<VkImageMemoryBarrier, 3> compute_dst_barriers = IndirectCache::build_barriers(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
+        compute_dst_barriers[0].image = this->distribution_red_image[1];
+        compute_dst_barriers[1].image = this->distribution_green_image[1];
+        compute_dst_barriers[2].image = this->distribution_blue_image[1];
+
+        vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, compute_dst_barriers.size(), compute_dst_barriers.data());
+
+        std::array<VkImageMemoryBarrier, 3> compute_indirect_barriers = IndirectCache::build_barriers(VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
+        compute_indirect_barriers[0].image = this->distribution_red_image[2];
+        compute_indirect_barriers[1].image = this->distribution_green_image[2];
+        compute_indirect_barriers[2].image = this->distribution_blue_image[2];
+
+        vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, compute_indirect_barriers.size(), compute_indirect_barriers.data());
+    }
+
+    std::array<VkImageMemoryBarrier, 3> final_barriers = IndirectCache::build_barriers(VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+    final_barriers[0].image = this->distribution_red_image[2];
+    final_barriers[1].image = this->distribution_green_image[2];
+    final_barriers[2].image = this->distribution_blue_image[2];
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, final_barriers.size(), final_barriers.data());
+}
+
+lava::descriptor::ptr IndirectCache::get_descriptor() const
+{
+    return this->indirect_descriptor;
+}
+
+VkDescriptorSet IndirectCache::get_descriptor_set() const
+{
+    return this->indirect_descriptor_set;
+}
+
+const IndirectSettings& IndirectCache::get_settings() const
+{
+    return this->settings;
+}
+
+void IndirectCache::compute_domain(Scene::Ptr scene, const IndirectSettings& settings)
+{
+    glm::vec3 scene_min = scene->get_scene_min();
+    glm::vec3 scene_max = scene->get_scene_max();
+
+    glm::vec3 scene_center = (scene_min + scene_max) / 2.0f;
+    glm::vec3 scene_size = scene_max - scene_min;
+
+    const glm::uvec3 work_group_size = glm::uvec3(16, 16, 1);
+
+    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;
+    
+    if ((this->domain_resolution.x % work_group_size.x) > 0)
+    {
+        this->domain_work_groups.x += 1;
+    }
+
+    if ((this->domain_resolution.y % work_group_size.y) > 0)
+    {
+        this->domain_work_groups.y += 1;
+    }
+
+    if ((this->domain_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));
+}
+
+bool IndirectCache::create_buffers(lava::device_ptr device, const IndirectSettings& settings)
+{
+    glsl::IndirectDomain indirect_domain;
+    indirect_domain.resolution = this->domain_resolution;
+    indirect_domain.cell_size = settings.cell_size;
+    indirect_domain.min = this->domain_min;
+    indirect_domain.padding1 = 0;
+    indirect_domain.max = this->domain_max;
+    indirect_domain.padding2 = 0;
+
+    this->domain_buffer = lava::make_buffer();
+
+    if (!this->domain_buffer->create_mapped(device, &indirect_domain, sizeof(indirect_domain), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT))
+    {
+        lava::log()->error("Can't create domain buffer for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_images(lava::device_ptr device, const IndirectSettings& settings)
+{
+    this->capture_depth_image = lava::make_image(VK_FORMAT_D16_UNORM);
+    this->capture_depth_image->set_usage(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+
+    if (!this->capture_depth_image->create(device, glm::uvec2(settings.capture_resolution)))
+    {
+        lava::log()->error("Can't create depth capture image for indirect cache!");
+
+        return false;
+    }
+
+    this->capture_flux_image = lava::make_image(VK_FORMAT_R16G16B16A16_SFLOAT);
+    this->capture_flux_image->set_usage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+
+    if (!this->capture_flux_image->create(device, glm::uvec2(settings.capture_resolution)))
+    {
+        lava::log()->error("Can't create flux capture image for indirect cache!");
+
+        return false;
+    }
+
+    this->capture_normal_image = lava::make_image(VK_FORMAT_R16G16_UNORM);
+    this->capture_normal_image->set_usage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+
+    if (!this->capture_normal_image->create(device, glm::uvec2(settings.capture_resolution)))
+    {
+        lava::log()->error("Can't create normal capture image for indirect cache!");
+
+        return false;
+    }
+
+    VmaAllocationCreateInfo allocation_info;
+    allocation_info.flags = 0;
+    allocation_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+    allocation_info.requiredFlags = 0;
+    allocation_info.preferredFlags = 0;
+    allocation_info.memoryTypeBits = 0;
+    allocation_info.pool = nullptr;
+    allocation_info.pUserData = nullptr;
+    allocation_info.priority = 1.0f;
+
+    VkImageCreateInfo image_create_info;
+    image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+    image_create_info.pNext = nullptr;
+    image_create_info.flags = 0;
+    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.mipLevels = 1;
+    image_create_info.arrayLayers = 1;
+    image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+    image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+    image_create_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+    image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+    image_create_info.queueFamilyIndexCount = 0;
+    image_create_info.pQueueFamilyIndices = nullptr;
+    image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+    VkImageViewCreateInfo view_create_info;
+    view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    view_create_info.pNext = nullptr;
+    view_create_info.flags = 0;
+    view_create_info.image = VK_NULL_HANDLE;
+    view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
+    view_create_info.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+    view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+    view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+    view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+    view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+    view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    view_create_info.subresourceRange.baseMipLevel = 0;
+    view_create_info.subresourceRange.levelCount = 1;
+    view_create_info.subresourceRange.baseArrayLayer = 0;
+    view_create_info.subresourceRange.layerCount = 1;
+
+    for (uint32_t index = 0; index < this->distribution_red_image.size(); index++)
+    {
+        VmaAllocation red_image_memory = VK_NULL_HANDLE;
+        VmaAllocation green_image_memory = VK_NULL_HANDLE;
+        VmaAllocation blue_image_memory = VK_NULL_HANDLE;
+
+        VkImage red_image = VK_NULL_HANDLE;
+        VkImage green_image = VK_NULL_HANDLE;
+        VkImage blue_image = VK_NULL_HANDLE;
+
+        if (vmaCreateImage(device->get_allocator()->get(), &image_create_info, &allocation_info, &red_image, &red_image_memory, nullptr) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create red distribution image for indirect cache!");
+
+            return false;
+        }
+
+        if (vmaCreateImage(device->get_allocator()->get(), &image_create_info, &allocation_info, &green_image, &green_image_memory, nullptr) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create green distribution image for indirect cache!");
+
+            return false;
+        }
+
+        if (vmaCreateImage(device->get_allocator()->get(), &image_create_info, &allocation_info, &blue_image, &blue_image_memory, nullptr) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create blue distribution image for indirect cache!");
+
+            return false;
+        }
+
+        VkImageView red_image_view = VK_NULL_HANDLE;
+        VkImageView green_image_view = VK_NULL_HANDLE;
+        VkImageView blue_image_view = VK_NULL_HANDLE;
+
+        view_create_info.image = red_image;
+
+        if (vkCreateImageView(device->get(), &view_create_info, lava::memory::alloc(), &red_image_view) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create red distribution image view for indirect cache!");
+
+            return false;
+        }
+
+        view_create_info.image = green_image;
+
+        if (vkCreateImageView(device->get(), &view_create_info, lava::memory::alloc(), &green_image_view) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create green distribution image view for indirect cache!");
+
+            return false;
+        }
+
+        view_create_info.image = blue_image;
+
+        if (vkCreateImageView(device->get(), &view_create_info, lava::memory::alloc(), &blue_image_view) != VK_SUCCESS)
+        {
+            lava::log()->error("Can't create blue distribution image view for indirect cache!");
+
+            return false;
+        }
+
+        this->distribution_red_image_memory[index] = red_image_memory;
+        this->distribution_green_image_memory[index] = green_image_memory;
+        this->distribution_blue_image_memory[index] = blue_image_memory;
+        
+        this->distribution_red_image[index] = red_image;
+        this->distribution_green_image[index] = green_image;
+        this->distribution_blue_image[index] = blue_image;
+        
+        this->distribution_red_image_view[index] = red_image_view;
+        this->distribution_green_image_view[index] = green_image_view;
+        this->distribution_blue_image_view[index] = blue_image_view;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_samplers(lava::device_ptr device)
+{
+    VkSamplerCreateInfo sampler_info;
+    sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    sampler_info.pNext = nullptr;
+    sampler_info.flags = 0;
+    sampler_info.magFilter = VK_FILTER_LINEAR;
+    sampler_info.minFilter = VK_FILTER_LINEAR;
+    sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+    sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    sampler_info.mipLodBias = 0.0f;
+    sampler_info.anisotropyEnable = VK_FALSE;
+    sampler_info.maxAnisotropy = 1.0f;
+    sampler_info.compareEnable = VK_FALSE;
+    sampler_info.compareOp = VK_COMPARE_OP_LESS;
+    sampler_info.minLod = 0.0f;
+    sampler_info.maxLod = VK_LOD_CLAMP_NONE;
+    sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+    sampler_info.unnormalizedCoordinates = VK_FALSE;
+
+    if (vkCreateSampler(device->get(), &sampler_info, lava::memory::alloc(), &this->indirect_sampler) != VK_SUCCESS)
+    {
+        lava::log()->error("Can't create indirect sampler for indirect cache!");
+
+        return false;
+    }
+    
+    sampler_info.magFilter = VK_FILTER_NEAREST;
+    sampler_info.minFilter = VK_FILTER_NEAREST;
+
+    if (vkCreateSampler(device->get(), &sampler_info, lava::memory::alloc(), &this->capture_sampler) != VK_SUCCESS)
+    {
+        lava::log()->error("Can't create capture sampler for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_descriptors(lava::device_ptr device)
+{
+    lava::VkDescriptorPoolSizes descriptor_type_count =
+    {
+        { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2 },
+        { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6 },
+        { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 18 }
+    };
+
+    this->descriptor_pool = lava::make_descriptor_pool();
+
+    if (!this->descriptor_pool->create(device, descriptor_type_count, 4))
+    {
+        lava::log()->error("Can't create descriptor pool for indirect cache!");
+
+        return false;
+    }
+
+    this->indirect_descriptor = lava::make_descriptor();
+    this->indirect_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 0: indirect red
+    this->indirect_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 1: indirect green
+    this->indirect_descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 2: indirect blue
+    this->indirect_descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);         //descriptor-binding index 3: indirect domain
+
+    if (!this->indirect_descriptor->create(device))
+    {
+        lava::log()->error("Can't create indirect 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
+    this->injection_descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_VERTEX_BIT); //descriptor-binding index 2: capture normal
+    this->injection_descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT);       //descriptor-binding index 3: indirect domain
+    
+    if (!this->injection_descriptor->create(device))
+    {
+        lava::log()->error("Can't create injection descriptor set layout for indirect cache!");
+
+        return false;
+    }
+
+    this->propagation_descriptor = lava::make_descriptor();
+    this->propagation_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 0: image red source distribution
+    this->propagation_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 1: image green source distribution
+    this->propagation_descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 2: image blue source distribution
+
+    this->propagation_descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 3: image red destination distribution
+    this->propagation_descriptor->add_binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 4: image green destination distribution
+    this->propagation_descriptor->add_binding(5, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 5: image blue destination distribution
+    
+    this->propagation_descriptor->add_binding(6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 6: image red indirect
+    this->propagation_descriptor->add_binding(7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 7: image green indirect
+    this->propagation_descriptor->add_binding(8, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT); //descriptor-binding index 8: image blue indirect
+    
+    if (!this->propagation_descriptor->create(device))
+    {
+        lava::log()->error("Can't create propagation descriptor set layout for indirect cache!");
+
+        return false;
+    }
+
+    this->indirect_descriptor_set = this->indirect_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());
+
+    std::vector<VkWriteDescriptorSet> descriptor_writes;
+
+    //TODO: ........
+
+    vkUpdateDescriptorSets(device->get(), descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
+
+    return true;
+}
+
+bool IndirectCache::create_layouts(lava::device_ptr device, Scene::Ptr scene)
+{
+    VkPushConstantRange capture_range;
+    capture_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
+    capture_range.offset = 0;
+    capture_range.size = sizeof(uint32_t) * 2;
+
+    this->capture_layout = lava::make_pipeline_layout();
+    this->capture_layout->add(scene->get_mesh_descriptor());        //set 0: mesh descriptor
+    this->capture_layout->add(scene->get_light_descriptor());       //set 1: light descriptor
+    this->capture_layout->add(scene->get_material_descriptor());    //set 2: material descriptor
+    this->capture_layout->add(capture_range);
+
+    if (!this->capture_layout->create(device))
+    {
+        lava::log()->error("Can't create capture pipeline layout for indirect cache!");
+
+        return false;
+    }
+
+    VkPushConstantRange injection_range;
+    injection_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT;
+    injection_range.offset = 0;
+    injection_range.size = sizeof(uint32_t) * 2 + sizeof(float);
+
+    this->injection_layout = lava::make_pipeline_layout();
+    this->injection_layout->add(scene->get_light_descriptor()); //set 0: light descriptor
+    this->injection_layout->add(this->injection_descriptor);    //set 1: injection descriptor
+    this->injection_layout->add(injection_range);
+
+    if (!this->injection_layout->create(device))
+    {
+        lava::log()->error("Can't create injection pipeline layout for indirect cache!");
+
+        return false;
+    }
+
+    this->propagation_layout = lava::make_pipeline_layout();
+    this->propagation_layout->add(this->propagation_descriptor); //set 0: propagation descriptor
+
+    if (!this->propagation_layout->create(device))
+    {
+        lava::log()->error("Can't create propagation pipeline layout for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_capture_pass(lava::device_ptr device, const IndirectSettings& settings)
+{
+    VkClearValue clear_depth;
+    clear_depth.depthStencil.depth = 1.0;
+    clear_depth.depthStencil.stencil = 0;
+
+    VkClearValue clear_color;
+    clear_color.color.float32[0] = 0.0f;
+    clear_color.color.float32[1] = 0.0f;
+    clear_color.color.float32[2] = 0.0f;
+    clear_color.color.float32[3] = 0.0f;
+
+    lava::attachment::ptr depth_attachment = lava::make_attachment(VK_FORMAT_D16_UNORM);
+    depth_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
+    depth_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    depth_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+    lava::attachment::ptr flux_attachment = lava::make_attachment(VK_FORMAT_R16G16B16A16_SFLOAT);
+    flux_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
+    flux_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    flux_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+    lava::attachment::ptr normal_attachment = lava::make_attachment(VK_FORMAT_R16G16_UNORM);
+    normal_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
+    normal_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    normal_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+    VkAttachmentReference flux_attachment_reference;
+    flux_attachment_reference.attachment = 1;
+    flux_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    VkAttachmentReference normal_attachment_reference;
+    normal_attachment_reference.attachment = 2;
+    normal_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    lava::subpass::ptr subpass = lava::make_subpass();
+    subpass->set_depth_stencil_attachment(0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+    subpass->set_color_attachments(
+    {
+        flux_attachment_reference,
+        normal_attachment_reference
+    });
+
+    lava::subpass_dependency::ptr subpass_begin_dependency = lava::make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
+    subpass_begin_dependency->set_stage_mask(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT);
+    subpass_begin_dependency->set_access_mask(0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+
+    lava::subpass_dependency::ptr subpass_end_dependency = lava::make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
+    subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+    subpass_end_dependency->set_access_mask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
+
+    this->capture_pass = lava::make_render_pass(device);
+    this->capture_pass->add(subpass);
+    this->capture_pass->add(subpass_begin_dependency);
+    this->capture_pass->add(subpass_end_dependency);
+    this->capture_pass->add(depth_attachment);      //location 0: depth
+    this->capture_pass->add(flux_attachment);       //location 1: flux
+    this->capture_pass->add(normal_attachment);     //location 2: normal
+    this->capture_pass->set_clear_values(
+    { 
+        clear_depth,
+        clear_color,
+        clear_color
+    });
+
+    lava::rect framebuffer_area =
+    {
+        glm::vec2(0.0f),
+        glm::vec2(settings.capture_resolution)
+    };
+
+    lava::VkImageViews framebuffer_views =
+    {
+        this->capture_depth_image->get_view(),
+        this->capture_flux_image->get_view(),
+        this->capture_normal_image->get_view()
+    };
+
+    if (!this->capture_pass->create({ framebuffer_views }, framebuffer_area))
+    {
+        lava::log()->error("Can't create capture pass for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_capture_pipeline(lava::device_ptr device)
+{
+    VkPipelineColorBlendAttachmentState blend_state;
+    blend_state.blendEnable = VK_FALSE;
+    blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.colorBlendOp = VK_BLEND_OP_ADD;
+    blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.alphaBlendOp = VK_BLEND_OP_ADD;
+    blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+    this->capture_pipeline = lava::make_graphics_pipeline(device);
+    this->capture_pipeline->set_layout(this->capture_layout);
+    this->capture_pipeline->set_rasterization_front_face(VK_FRONT_FACE_CLOCKWISE);
+    this->capture_pipeline->set_rasterization_cull_mode(VK_CULL_MODE_BACK_BIT);
+    this->capture_pipeline->set_depth_test_and_write(true, true);
+    this->capture_pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
+    this->capture_pipeline->add_color_blend_attachment(blend_state);
+    this->capture_pipeline->add_color_blend_attachment(blend_state);
+
+    scene->set_vertex_input(this->capture_pipeline.get());
+
+    if (!this->capture_pipeline->add_shader(lava::file_data("dpr/binary/indirect_capture_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
+    {
+        return false;
+    }
+
+    if (!this->capture_pipeline->add_shader(lava::file_data("dpr/binary/indirect_capture_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
+    {
+        return false;
+    }
+
+    this->capture_pipeline->on_process = [this](VkCommandBuffer command_buffer)
+    {
+        this->pipeline_capture(command_buffer);
+    };
+
+    if (!this->capture_pipeline->create(this->capture_pass->get()))
+    {
+        return false;
+    }
+
+    this->capture_pass->add_front(this->capture_pipeline);
+
+    return true;
+}
+
+bool IndirectCache::create_injection_pass(lava::device_ptr device)
+{
+    lava::attachment::ptr attachment_red = lava::make_attachment(VK_FORMAT_R16G16B16A16_SFLOAT);
+    attachment_red->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
+    attachment_red->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    attachment_red->set_layouts(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+    lava::attachment::ptr attachment_green = lava::make_attachment(VK_FORMAT_R16G16B16A16_SFLOAT);
+    attachment_green->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
+    attachment_green->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    attachment_green->set_layouts(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+    lava::attachment::ptr attachment_blue = lava::make_attachment(VK_FORMAT_R16G16B16A16_SFLOAT);
+    attachment_blue->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
+    attachment_blue->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    attachment_blue->set_layouts(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+    VkAttachmentReference attachment_red_reference;
+    attachment_red_reference.attachment = 0;
+    attachment_red_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    VkAttachmentReference attachment_green_reference;
+    attachment_green_reference.attachment = 1;
+    attachment_green_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    VkAttachmentReference attachment_blue_reference;
+    attachment_blue_reference.attachment = 2;
+    attachment_blue_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    lava::subpass::ptr subpass = lava::make_subpass();
+    subpass->set_color_attachments(
+    {
+        attachment_red_reference,
+        attachment_green_reference,
+        attachment_blue_reference
+    });
+
+    lava::subpass_dependency::ptr subpass_begin_dependency = lava::make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
+    subpass_begin_dependency->set_stage_mask(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
+    subpass_begin_dependency->set_access_mask(0, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
+
+    lava::subpass_dependency::ptr subpass_end_dependency = lava::make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
+    subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+    subpass_end_dependency->set_access_mask(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
+
+    this->injection_pass = lava::make_render_pass(device);
+    this->injection_pass->add(subpass);
+    this->injection_pass->add(subpass_begin_dependency);
+    this->injection_pass->add(subpass_end_dependency);
+    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);
+
+    lava::rect framebuffer_area =
+    {
+        glm::vec2(0.0f),
+        glm::vec2(this->domain_resolution.x, this->domain_resolution.y)
+    };
+
+    lava::VkImageViews framebuffer_views =
+    {
+        this->distribution_red_image_view[0],
+        this->distribution_green_image_view[0],
+        this->distribution_blue_image_view[0],
+    };
+
+    if (!this->injection_pass->create({ framebuffer_views }, framebuffer_area))
+    {
+        lava::log()->error("Can't create injection pass for indirect cache!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool IndirectCache::create_injection_pipeline(lava::device_ptr device)
+{
+    VkPipelineColorBlendAttachmentState blend_state;
+    blend_state.blendEnable = VK_TRUE;
+    blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+    blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+    blend_state.colorBlendOp = VK_BLEND_OP_ADD;
+    blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+    blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+    blend_state.alphaBlendOp = VK_BLEND_OP_ADD;
+    blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+    this->injection_pipeline = lava::make_graphics_pipeline(device);
+    this->injection_pipeline->set_layout(this->capture_layout);
+    this->injection_pipeline->set_input_assembly_topology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST);
+    this->injection_pipeline->add_color_blend_attachment(blend_state);
+    this->injection_pipeline->add_color_blend_attachment(blend_state);
+    this->injection_pipeline->add_color_blend_attachment(blend_state);
+
+    if (!this->injection_pipeline->add_shader(lava::file_data("dpr/binary/indirect_injection_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
+    {
+        return false;
+    }
+
+    if (!this->injection_pipeline->add_shader(lava::file_data("dpr/binary/indirect_injection_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
+    {
+        return false;
+    }
+
+    if (!this->injection_pipeline->add_shader(lava::file_data("dpr/binary/indirect_injection_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
+    {
+        return false;
+    }
+
+    this->injection_pipeline->on_process = [this](VkCommandBuffer command_buffer)
+    {
+        this->pipeline_injection(command_buffer);
+    };
+
+    if (!this->injection_pipeline->create(this->injection_pass->get()))
+    {
+        return false;
+    }
+
+    this->injection_pass->add_front(this->injection_pipeline);
+
+    return true;
+}
+
+bool IndirectCache::create_propagation_pipeline(lava::device_ptr device)
+{
+    this->propagation_pipeline = lava::make_compute_pipeline(device);
+    this->propagation_pipeline->set_layout(this->propagation_layout);
+    
+    if (!this->propagation_pipeline->set_shader_stage(lava::file_data("dpr/binary/indirect_propagation_compute.spirv"), VK_SHADER_STAGE_COMPUTE_BIT))
+    {
+        return false;
+    }
+
+    if (!this->propagation_pipeline->create())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void IndirectCache::pipeline_capture(VkCommandBuffer command_buffer)
+{
+    std::array<uint32_t, 2> push_constants;
+    push_constants[0] = this->light_index;
+    push_constants[1] = this->settings.capture_resolution;
+
+    vkCmdPushConstants(command_buffer, this->capture_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(uint32_t) * push_constants.size(), push_constants.data());
+
+    this->capture_layout->bind(command_buffer, this->scene->get_light_descriptor_set(this->frame), 1);
+
+    const std::vector<SceneMaterial>& materials = this->scene->get_materials();
+
+    for (const SceneMesh& mesh : this->scene->get_meshes())
+    {
+        if (!mesh.cast_shadow)
+        {
+            continue;
+        }
+
+        const SceneMaterial& material = materials[mesh.material_index];
+        this->capture_layout->bind(command_buffer, mesh.descriptor_set[this->frame], 0);
+        this->capture_layout->bind(command_buffer, material.descriptor_set, 2);
+
+        mesh.mesh->bind_draw(command_buffer);
+    }
+}
+
+void IndirectCache::pipeline_injection(VkCommandBuffer command_buffer)
+{
+    std::array<uint32_t, 2> push_constants;
+    push_constants[0] = this->light_index;
+    push_constants[1] = this->settings.capture_resolution;
+
+    float cell_size = this->settings.cell_size;
+
+    vkCmdPushConstants(command_buffer, this->capture_layout->get(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(uint32_t) * push_constants.size(), push_constants.data());
+    vkCmdPushConstants(command_buffer, this->capture_layout->get(), VK_SHADER_STAGE_VERTEX_BIT, sizeof(uint32_t) * push_constants.size(), sizeof(cell_size), &cell_size);
+
+    this->injection_layout->bind(command_buffer, this->scene->get_light_descriptor_set(this->frame), 0);
+    this->injection_layout->bind(command_buffer, this->injection_descriptor_set, 1);
+
+    uint32_t point_count = this->settings.capture_resolution * this->settings.capture_resolution;
+    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 barrier;
+    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barrier.pNext = nullptr;
+    barrier.srcAccessMask = src_access;
+    barrier.dstAccessMask = dst_access;
+    barrier.oldLayout = old_layout;
+    barrier.newLayout = new_layout;
+    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barrier.image = VK_NULL_HANDLE;
+    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barrier.subresourceRange.baseMipLevel = 0;
+    barrier.subresourceRange.levelCount = 1;
+    barrier.subresourceRange.baseArrayLayer = 0;
+    barrier.subresourceRange.layerCount = 1;
+
+    std::array<VkImageMemoryBarrier, 3> barriers;
+    barriers.fill(barrier);
+
+    return barriers;
+}
+
+IndirectCache::Ptr make_indirect_cache()
+{
+    return std::make_shared<IndirectCache>();
+}
\ No newline at end of file
diff --git a/src/utility/indirect_cache.hpp b/src/utility/indirect_cache.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..00a50a89e9c1e4002686f743217ebac0962c4917
--- /dev/null
+++ b/src/utility/indirect_cache.hpp
@@ -0,0 +1,111 @@
+#pragma once
+#include <liblava/lava.hpp>
+#include <glm/glm.hpp>
+#include <memory>
+#include <vector>
+#include <array>
+#include <cstdint>
+
+#include "scene.hpp"
+
+struct IndirectSettings
+{
+    uint32_t capture_resolution = 1024;
+    float cell_size = 1.0f; //Where cell size is in meters
+};
+
+class IndirectCache
+{
+public:
+    typedef std::shared_ptr<IndirectCache> Ptr;
+
+public:
+    IndirectCache() = default;
+
+    bool create(Scene::Ptr scene, const IndirectSettings& settings);
+    void destroy();
+
+    void compute_indirect(VkCommandBuffer command_buffer, lava::index frame);
+
+    lava::descriptor::ptr get_descriptor() const;
+    VkDescriptorSet get_descriptor_set() const;
+
+    const IndirectSettings& get_settings() const;
+
+private:
+    void compute_domain(Scene::Ptr scene, const IndirectSettings& settings);
+
+    bool create_buffers(lava::device_ptr device, const IndirectSettings& settings);
+    bool create_images(lava::device_ptr device, const IndirectSettings& 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_capture_pass(lava::device_ptr device, const IndirectSettings& 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_capture(VkCommandBuffer command_buffer);
+    void pipeline_injection(VkCommandBuffer command_buffer);
+
+    static std::array<VkImageMemoryBarrier, 3> build_barriers(VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout);
+
+private:
+    Scene::Ptr scene;
+    IndirectSettings settings;
+
+    uint32_t frame = 0;
+    uint32_t light_index = 0;
+
+    uint32_t propagation_iterations = 0;
+
+    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_work_groups = glm::uvec3(0);
+
+    lava::descriptor::pool::ptr descriptor_pool;
+    lava::descriptor::ptr indirect_descriptor;
+    lava::descriptor::ptr injection_descriptor;
+    lava::descriptor::ptr propagation_descriptor;
+
+    VkDescriptorSet indirect_descriptor_set = VK_NULL_HANDLE;
+    VkDescriptorSet injection_descriptor_set = VK_NULL_HANDLE;
+    std::array<VkDescriptorSet, 2> propagation_descriptor_set;
+    
+    lava::pipeline_layout::ptr capture_layout;
+    lava::pipeline_layout::ptr injection_layout;
+    lava::pipeline_layout::ptr propagation_layout;
+
+    lava::render_pass::ptr capture_pass;
+    lava::render_pass::ptr injection_pass;
+    
+    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 capture_sampler = VK_NULL_HANDLE;
+
+    lava::image::ptr capture_depth_image;
+    lava::image::ptr capture_flux_image;
+    lava::image::ptr capture_normal_image;
+
+    //NOTE: Two images for the propagation and one image for the accumulation
+    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<VkImage, 3> distribution_red_image;
+    std::array<VkImage, 3> distribution_green_image;
+    std::array<VkImage, 3> distribution_blue_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;
+    
+    lava::buffer::ptr domain_buffer;
+};
+
+IndirectCache::Ptr make_indirect_cache();
\ No newline at end of file
diff --git a/src/utility/shadow_cache.cpp b/src/utility/shadow_cache.cpp
index 84f6c7a825200994db18967ba175e708b54092a9..e8b6df8657fa9bf4054537fe566d2b6df5ecae4a 100644
--- a/src/utility/shadow_cache.cpp
+++ b/src/utility/shadow_cache.cpp
@@ -1,5 +1,12 @@
 #include "shadow_cache.hpp"
-#include <random>
+
+namespace glsl
+{
+    using namespace glm;
+    using uint = glm::uint32;
+
+#include "res/dpr/data/shadow_data.inc"
+}
 
 bool ShadowCache::create(Scene::Ptr scene, const ShadowCacheSettings& settings)
 {
@@ -23,44 +30,16 @@ bool ShadowCache::create(Scene::Ptr scene, const ShadowCacheSettings& settings)
         return false;
     }
 
-    if (!this->create_shadow_pass(device, settings))
+    if (!this->create_render_pass(device, settings))
     {
         return false;
     }
 
-    if (!this->create_shadow_pipeline(device, scene))
+    if (!this->create_pipeline(device, scene))
     {
         return false;
     }
 
-    if (settings.use_directional_indirect)
-    {
-        if (!this->create_indirect_pass(device, settings))
-        {
-            return false;
-        }
-
-        if (!this->create_indirect_pipeline(device, scene))
-        {
-            return false;
-        }
-
-        if (!this->create_downsample_pass(device))
-        {
-            return false;
-        }
-
-        if (!this->create_downsample_pipeline(device))
-        {
-            return false;
-        }
-
-        if (!this->create_framebuffers(device, settings))
-        {
-            return false;
-        }
-    }
-
     return true;
 }
 
@@ -73,40 +52,22 @@ void ShadowCache::destroy()
 
     lava::device_ptr device = this->scene->get_light_descriptor()->get_device();
     
-    if (this->indirect_pipeline != nullptr)
-    {
-        this->indirect_pipeline->destroy();
-        this->indirect_pipeline = nullptr;
-    }
-
-    if (this->indirect_pipeline_layout != nullptr)
-    {
-        this->indirect_pipeline_layout->destroy();
-        this->indirect_pipeline_layout = nullptr;
-    }
-
-    if (this->indirect_pass != nullptr)
-    {
-        this->indirect_pass->destroy();
-        this->indirect_pass = nullptr;
-    }
-
-    if (this->shadow_pipeline != nullptr)
+    if (this->pipeline != nullptr)
     {
-        this->shadow_pipeline->destroy();
-        this->shadow_pipeline = nullptr;
+        this->pipeline->destroy();
+        this->pipeline = nullptr;
     }
 
-    if (this->shadow_pipeline_layout != nullptr)
+    if (this->pipeline_layout != nullptr)
     {
-        this->shadow_pipeline_layout->destroy();
-        this->shadow_pipeline_layout = nullptr;
+        this->pipeline_layout->destroy();
+        this->pipeline_layout = nullptr;
     }
 
-    if (this->shadow_pass != nullptr)
+    if (this->render_pass != nullptr)
     {
-        this->shadow_pass->destroy();
-        this->shadow_pass = nullptr;
+        this->render_pass->destroy();
+        this->render_pass = nullptr;
     }
 
     if (this->descriptor_set != nullptr)
@@ -127,112 +88,70 @@ void ShadowCache::destroy()
         this->descriptor_pool = nullptr;
     }
 
-    if (this->shadow_view_directional != nullptr)
-    {
-        vkDestroyImageView(device->get(), this->shadow_view_directional, lava::memory::alloc());
-        this->shadow_view_directional = nullptr;
-    }
-
-    if (this->shadow_view_spot != nullptr)
-    {
-        vkDestroyImageView(device->get(), this->shadow_view_spot, lava::memory::alloc());
-        this->shadow_view_spot = nullptr;
-    }
-
-    if (this->shadow_view_point != nullptr)
-    {
-        vkDestroyImageView(device->get(), this->shadow_view_point, lava::memory::alloc());
-        this->shadow_view_point = nullptr;
-    }
-
-    if (this->shadow_array != nullptr)
-    {
-        this->shadow_array->destroy();
-        this->shadow_array = nullptr;
-    }
-
-    if (this->shadow_default_plane != nullptr)
-    {
-        this->shadow_default_plane->destroy();
-        this->shadow_default_plane = nullptr;
-    }
-
-    if (this->shadow_default_cube != nullptr)
-    {
-        this->shadow_default_cube->destroy();
-        this->shadow_default_cube = nullptr;
-    }
-
-    if (this->sampler_shadow_default != nullptr)
-    {
-        vkDestroySampler(device->get(), this->sampler_shadow_default, lava::memory::alloc());
-        this->sampler_shadow_default = nullptr;
-    }
-
-    if (this->sampler_shadow_directional != nullptr)
+    if (this->image_view_directional != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_shadow_directional, lava::memory::alloc());
-        this->sampler_shadow_directional = nullptr;
+        vkDestroyImageView(device->get(), this->image_view_directional, lava::memory::alloc());
+        this->image_view_directional = nullptr;
     }
 
-    if (this->sampler_shadow_spot != nullptr)
+    if (this->image_view_spot != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_shadow_spot, lava::memory::alloc());
-        this->sampler_shadow_spot = nullptr;
+        vkDestroyImageView(device->get(), this->image_view_spot, lava::memory::alloc());
+        this->image_view_spot = nullptr;
     }
 
-    if (this->sampler_shadow_point != nullptr)
+    if (this->image_view_point != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_shadow_point, lava::memory::alloc());
-        this->sampler_shadow_point = nullptr;
+        vkDestroyImageView(device->get(), this->image_view_point, lava::memory::alloc());
+        this->image_view_point = nullptr;
     }
 
-    if (this->sampler_indirect_position != nullptr)
+    if (this->image_array != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_indirect_position, lava::memory::alloc());
-        this->sampler_indirect_position = nullptr;
+        this->image_array->destroy();
+        this->image_array = nullptr;
     }
 
-    if (this->sampler_indirect_normal != nullptr)
+    if (this->image_default_plane != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_indirect_normal, lava::memory::alloc());
-        this->sampler_indirect_normal = nullptr;
+        this->image_default_plane->destroy();
+        this->image_default_plane = nullptr;
     }
 
-    if (this->sampler_indirect_flux != nullptr)
+    if (this->image_default_cube != nullptr)
     {
-        vkDestroySampler(device->get(), this->sampler_indirect_flux, lava::memory::alloc());
-        this->sampler_indirect_flux = nullptr;
+        this->image_default_cube->destroy();
+        this->image_default_cube = nullptr;
     }
 
-    if (this->indirect_position_array != nullptr)
+    if (this->sampler_default != nullptr)
     {
-        this->indirect_position_array->destroy();
-        this->indirect_position_array = nullptr;
+        vkDestroySampler(device->get(), this->sampler_default, lava::memory::alloc());
+        this->sampler_default = nullptr;
     }
 
-    if (this->indirect_normal_array != nullptr)
+    if (this->sampler_directional != nullptr)
     {
-        this->indirect_normal_array->destroy();
-        this->indirect_normal_array = nullptr;
+        vkDestroySampler(device->get(), this->sampler_directional, lava::memory::alloc());
+        this->sampler_directional = nullptr;
     }
 
-    if (this->indirect_flux_array != nullptr)
+    if (this->sampler_spot != nullptr)
     {
-        this->indirect_flux_array->destroy();
-        this->indirect_flux_array = nullptr;
+        vkDestroySampler(device->get(), this->sampler_spot, lava::memory::alloc());
+        this->sampler_spot = nullptr;
     }
 
-    if (this->shadow_parameter_buffer != nullptr)
+    if (this->sampler_point != nullptr)
     {
-        this->shadow_parameter_buffer->destroy();
-        this->shadow_parameter_buffer = nullptr;
+        vkDestroySampler(device->get(), this->sampler_point, lava::memory::alloc());
+        this->sampler_point = nullptr;
     }
 
-    if (this->indirect_sample_buffer != nullptr)
+    if (this->shadow_buffer != nullptr)
     {
-        this->indirect_sample_buffer->destroy();
-        this->indirect_sample_buffer = nullptr;
+        this->shadow_buffer->destroy();
+        this->shadow_buffer = nullptr;
     }
 
     //Don't touch these resources. Only release the shared pointer to them.
@@ -245,51 +164,24 @@ void ShadowCache::compute_shadow(VkCommandBuffer command_buffer, lava::index ind
     {
         lava::device_ptr device = scene->get_light_descriptor()->get_device();
 
-        lava::insert_image_memory_barrier(device, command_buffer, this->shadow_default_plane->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->shadow_default_plane->get_subresource_range());
-        lava::insert_image_memory_barrier(device, command_buffer, this->shadow_default_cube->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->shadow_default_cube->get_subresource_range());
-
-        VkClearDepthStencilValue clear_depth_value;
-        clear_depth_value.depth = 1.0f;
-        clear_depth_value.stencil = 0;
+        lava::insert_image_memory_barrier(device, command_buffer, this->image_default_plane->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->image_default_plane->get_subresource_range());
+        lava::insert_image_memory_barrier(device, command_buffer, this->image_default_cube->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->image_default_cube->get_subresource_range());
 
-        vkCmdClearDepthStencilImage(command_buffer, this->shadow_default_plane->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_depth_value, 1, &this->shadow_default_plane->get_subresource_range());
-        vkCmdClearDepthStencilImage(command_buffer, this->shadow_default_cube->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_depth_value, 1, &this->shadow_default_plane->get_subresource_range());
+        VkClearDepthStencilValue clear_value;
+        clear_value.depth = 1.0f;
+        clear_value.stencil = 0;
 
-        lava::insert_image_memory_barrier(device, command_buffer, this->shadow_default_plane->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->shadow_default_plane->get_subresource_range());
-        lava::insert_image_memory_barrier(device, command_buffer, this->shadow_default_cube->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->shadow_default_cube->get_subresource_range());
+        vkCmdClearDepthStencilImage(command_buffer, this->image_default_plane->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_value, 1, &this->image_default_plane->get_subresource_range());
+        vkCmdClearDepthStencilImage(command_buffer, this->image_default_cube->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_value, 1, &this->image_default_cube->get_subresource_range());
 
-        if (!this->settings.use_directional_indirect)
-        {
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_position_array->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->indirect_position_array->get_subresource_range());
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_normal_array->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->indirect_normal_array->get_subresource_range());
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_flux_array->get(), 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, this->indirect_flux_array->get_subresource_range());
-
-            VkClearColorValue clear_color_value;
-            clear_color_value.float32[0] = 0.0f;
-            clear_color_value.float32[1] = 0.0f;
-            clear_color_value.float32[2] = 0.0f;
-            clear_color_value.float32[3] = 0.0f;
-
-            vkCmdClearColorImage(command_buffer, this->indirect_position_array->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color_value, 1, &this->indirect_position_array->get_subresource_range());
-            vkCmdClearColorImage(command_buffer, this->indirect_normal_array->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color_value, 1, &this->indirect_normal_array->get_subresource_range());
-            vkCmdClearColorImage(command_buffer, this->indirect_flux_array->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color_value, 1, &this->indirect_flux_array->get_subresource_range());
-
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_position_array->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->indirect_position_array->get_subresource_range());
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_normal_array->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->indirect_normal_array->get_subresource_range());
-            lava::insert_image_memory_barrier(device, command_buffer, this->indirect_flux_array->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->indirect_flux_array->get_subresource_range());
-        }
+        lava::insert_image_memory_barrier(device, command_buffer, this->image_default_plane->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->image_default_plane->get_subresource_range());
+        lava::insert_image_memory_barrier(device, command_buffer, this->image_default_cube->get(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, this->image_default_cube->get_subresource_range());
 
         this->image_default_cleared = true;
     }
 
     this->frame = index;
-    this->shadow_pass->process(command_buffer, 0);
-
-    if (this->settings.use_directional_indirect)
-    {
-        this->indirect_pass->process(command_buffer, 0);
-        this->pipeline_downsample(command_buffer);
-    }
+    this->render_pass->process(command_buffer, 0);
 }
 
 lava::descriptor::ptr ShadowCache::get_descriptor() const
@@ -307,7 +199,7 @@ const ShadowCacheSettings& ShadowCache::get_settings() const
     return this->settings;
 }
 
-bool ShadowCache::create_image_view(lava::device_ptr device, lava::image::ptr image_array, uint32_t layer_offset, uint32_t layer_count, uint32_t level, VkImageAspectFlags aspect, VkImageViewType view_type, VkImageView& view)
+bool ShadowCache::create_image_view(lava::device_ptr device, lava::image::ptr image_array, uint32_t layer_offset, uint32_t layer_count, VkImageViewType view_type, VkImageView& view)
 {
     VkComponentMapping components;
     components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
@@ -316,8 +208,8 @@ bool ShadowCache::create_image_view(lava::device_ptr device, lava::image::ptr im
     components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
 
     VkImageSubresourceRange subresource_range;
-    subresource_range.aspectMask = aspect;
-    subresource_range.baseMipLevel  = level;
+    subresource_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+    subresource_range.baseMipLevel  = 0;
     subresource_range.levelCount = 1;
     subresource_range.baseArrayLayer = layer_offset;
     subresource_range.layerCount = layer_count;
@@ -340,7 +232,7 @@ bool ShadowCache::create_image_view(lava::device_ptr device, lava::image::ptr im
     return true;
 }
 
-bool ShadowCache::create_shadow_sampler(lava::device_ptr device, VkSampler& sampler)
+bool ShadowCache::create_sampler(lava::device_ptr device, VkSampler& sampler)
 {
     VkSamplerCreateInfo create_info;
     create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
@@ -370,58 +262,28 @@ bool ShadowCache::create_shadow_sampler(lava::device_ptr device, VkSampler& samp
     return true;
 }
 
-bool ShadowCache::create_indirect_sampler(lava::device_ptr device, VkSampler& sampler)
-{
-    VkSamplerCreateInfo create_info;
-    create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
-    create_info.pNext = nullptr;
-    create_info.flags = 0;
-    create_info.magFilter = VK_FILTER_LINEAR;
-    create_info.minFilter = VK_FILTER_LINEAR;
-    create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
-    create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
-    create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
-    create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
-    create_info.mipLodBias = 0.0f;
-    create_info.anisotropyEnable = VK_FALSE;
-    create_info.maxAnisotropy = 1.0f;
-    create_info.compareEnable = VK_FALSE;
-    create_info.compareOp = VK_COMPARE_OP_LESS;
-    create_info.minLod = 0;
-    create_info.maxLod = VK_LOD_CLAMP_NONE;
-    create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
-    create_info.unnormalizedCoordinates = VK_FALSE;
-
-    if (vkCreateSampler(device->get(), &create_info, lava::memory::alloc(), &sampler) != VK_SUCCESS)
-    {
-        return false;
-    }
-
-    return true;
-}
-
 bool ShadowCache::create_images(lava::device_ptr device, Scene::Ptr scene, const ShadowCacheSettings& settings)
 {
-    this->shadow_default_plane = std::make_shared<lava::image>(VK_FORMAT_D16_UNORM);
-    this->shadow_default_plane->set_layer_count(1);
-    this->shadow_default_plane->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
+    this->image_default_plane = std::make_shared<lava::image>(VK_FORMAT_D16_UNORM);
+    this->image_default_plane->set_layer_count(1);
+    this->image_default_plane->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
 
-    if (!this->shadow_default_plane->create(device, lava::uv2(1, 1)))
+    if (!this->image_default_plane->create(device, lava::uv2(1, 1)))
     {
         return false;
     }
 
-    this->shadow_default_cube = std::make_shared<lava::image>(VK_FORMAT_D16_UNORM);
-    this->shadow_default_cube->set_layer_count(6);
-    this->shadow_default_cube->set_view_type(VK_IMAGE_VIEW_TYPE_CUBE_ARRAY);
-    this->shadow_default_cube->set_flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
+    this->image_default_cube = std::make_shared<lava::image>(VK_FORMAT_D16_UNORM);
+    this->image_default_cube->set_layer_count(6);
+    this->image_default_cube->set_view_type(VK_IMAGE_VIEW_TYPE_CUBE_ARRAY);
+    this->image_default_cube->set_flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
 
-    if (!this->shadow_default_cube->create(device, lava::uv2(1, 1)))
+    if (!this->image_default_cube->create(device, lava::uv2(1, 1)))
     {
         return false;
     }
 
-    if (!this->create_shadow_sampler(device, this->sampler_shadow_default))
+    if (!this->create_sampler(device, this->sampler_default))
     {
         return false;
     }
@@ -451,29 +313,29 @@ bool ShadowCache::create_images(lava::device_ptr device, Scene::Ptr scene, const
     uint32_t layer_count = directional_count + spot_count + 6 * point_count;
     uint32_t layer_offset = 0;
 
-    this->shadow_array = lava::make_image(VK_FORMAT_D16_UNORM);
-    this->shadow_array->set_layer_count(layer_count);
-    this->shadow_array->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
-    this->shadow_array->set_usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+    this->image_array = std::make_shared<lava::image>(VK_FORMAT_D16_UNORM);
+    this->image_array->set_layer_count(layer_count);
+    this->image_array->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
+    this->image_array->set_usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
 
     if (point_count > 0)
     {
-        this->shadow_array->set_flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
+        this->image_array->set_flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
     }
 
-    if (!this->shadow_array->create(device, lava::uv2(settings.resolution)))
+    if (!this->image_array->create(device, lava::uv2(settings.resolution)))
     {
         return false;
     }
 
     if (directional_count > 0)
     {
-        if (!this->create_image_view(device, this->shadow_array, layer_offset, directional_count, 0, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, this->shadow_view_directional))
+        if (!this->create_image_view(device, this->image_array, layer_offset, directional_count, VK_IMAGE_VIEW_TYPE_2D_ARRAY, this->image_view_directional))
         {
             return false;
         }
 
-        if (!this->create_shadow_sampler(device, this->sampler_shadow_directional))
+        if (!this->create_sampler(device, this->sampler_directional))
         {
             return false;
         }
@@ -484,12 +346,12 @@ bool ShadowCache::create_images(lava::device_ptr device, Scene::Ptr scene, const
 
     if (spot_count > 0)
     {
-        if (!this->create_image_view(device, this->shadow_array, layer_offset, spot_count, 0, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, this->shadow_view_spot))
+        if (!this->create_image_view(device, this->image_array, layer_offset, spot_count, VK_IMAGE_VIEW_TYPE_2D_ARRAY, this->image_view_spot))
         {
             return false;
         }
 
-        if (!this->create_shadow_sampler(device, this->sampler_shadow_spot))
+        if (!this->create_sampler(device, this->sampler_spot))
         {
             return false;
         }
@@ -500,12 +362,12 @@ bool ShadowCache::create_images(lava::device_ptr device, Scene::Ptr scene, const
 
     if (point_count > 0)
     {
-        if (!this->create_image_view(device, this->shadow_array, layer_offset, point_count * 6, 0, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, this->shadow_view_point))
+        if (!this->create_image_view(device, this->image_array, layer_offset, point_count * 6, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, this->image_view_point))
         {
             return false;
         }
 
-        if (!this->create_shadow_sampler(device, this->sampler_shadow_point))
+        if (!this->create_sampler(device, this->sampler_point))
         {
             return false;
         }
@@ -514,128 +376,19 @@ bool ShadowCache::create_images(lava::device_ptr device, Scene::Ptr scene, const
         layer_offset += point_count * 6;
     }
 
-    uint32_t indirect_resolution = 1;
-    uint32_t indirect_layers = 1;
-    uint32_t indirect_levels = 1;
-
-    if (settings.use_directional_indirect)
-    {
-        indirect_resolution = settings.resolution;
-        indirect_layers = glm::max(indirect_layers, directional_count);
-
-        uint32_t mipmap_resolution = indirect_resolution;
-
-        while (mipmap_resolution > 1)
-        {
-            mipmap_resolution >>= 1;
-            indirect_levels++;
-        }
-    }
-
-    this->indirect_position_array = lava::make_image(VK_FORMAT_R16G16B16A16_SFLOAT);
-    this->indirect_position_array->set_layer_count(indirect_layers);
-    this->indirect_position_array->set_level_count(indirect_levels);
-    this->indirect_position_array->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
-    this->indirect_position_array->set_usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
-
-    if (!this->indirect_position_array->create(device, lava::uv2(indirect_resolution)))
-    {
-        return false;
-    }
-
-    if (!this->create_indirect_sampler(device, this->sampler_indirect_position))
-    {
-        return false;
-    }
-
-    this->indirect_normal_array = lava::make_image(VK_FORMAT_R16G16B16A16_SFLOAT);
-    this->indirect_normal_array->set_layer_count(indirect_layers);
-    this->indirect_normal_array->set_level_count(indirect_levels);
-    this->indirect_normal_array->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
-    this->indirect_normal_array->set_usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
-
-    if (!this->indirect_normal_array->create(device, lava::uv2(indirect_resolution)))
-    {
-        return false;
-    }
-
-    if (!this->create_indirect_sampler(device, this->sampler_indirect_normal))
-    {
-        return false;
-    }
-
-    this->indirect_flux_array = lava::make_image(VK_FORMAT_R16G16B16A16_SFLOAT);
-    this->indirect_flux_array->set_layer_count(indirect_layers);
-    this->indirect_flux_array->set_level_count(indirect_levels);
-    this->indirect_flux_array->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
-    this->indirect_flux_array->set_usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
-
-    if (!this->indirect_flux_array->create(device, lava::uv2(indirect_resolution)))
-    {
-        return false;
-    }
-
-    if (!this->create_indirect_sampler(device, this->sampler_indirect_flux))
-    {
-        return false;
-    }
-
-    for (uint32_t level = 0; level < indirect_levels; level++)
-    {
-        VkImageView indirect_position_view = VK_NULL_HANDLE;
-        VkImageView indirect_normal_view = VK_NULL_HANDLE;
-        VkImageView indirect_flux_view = VK_NULL_HANDLE;
-
-        if (!this->create_image_view(device, this->indirect_position_array, 0, indirect_layers, level, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, indirect_position_view))
-        {
-            return false;
-        }
-
-        if (!this->create_image_view(device, this->indirect_normal_array, 0, indirect_layers, level, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, indirect_normal_view))
-        {
-            return false;
-        }
-
-        if (!this->create_image_view(device, this->indirect_flux_array, 0, indirect_layers, level, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_VIEW_TYPE_2D_ARRAY, indirect_flux_view))
-        {
-            return false;
-        }
-
-        indirect_position_views.push_back(indirect_position_view);
-        indirect_normal_views.push_back(indirect_normal_view);
-        indirect_flux_views.push_back(indirect_flux_view);
-    }
-
     return true;
 }
 
 bool ShadowCache::create_buffers(lava::device_ptr device, const ShadowCacheSettings& settings)
 {
-    if (settings.indirect_sample_count > MAX_INDIRECT_SAMPLE_COUNT)
-    {
-        lava::log()->error("Maximal number of indirect samples exceeded!");
-
-        return false;
-    }
-
     glsl::ShadowParameter shadow_parameter;
-    shadow_parameter.use_directional_shadow = settings.use_directional_shadow ? 1 : 0;
-    shadow_parameter.use_directional_indirect = settings.use_directional_indirect ? 1 : 0;
-    shadow_parameter.use_spot_shadow = settings.use_spot_shadow ? 1 : 0;
-    shadow_parameter.use_point_shadow = settings.use_point_shadow ? 1 : 0;
-    shadow_parameter.indirect_sample_count = settings.indirect_sample_count;
-
-    this->shadow_parameter_buffer = lava::make_buffer();
-
-    if (!this->shadow_parameter_buffer->create_mapped(device, &shadow_parameter, sizeof(glsl::ShadowParameter), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT))
-    {
-        return false;
-    }
+    shadow_parameter.use_directional = settings.use_directional ? 1 : 0;
+    shadow_parameter.use_spot = settings.use_spot ? 1 : 0;
+    shadow_parameter.use_point = settings.use_point ? 1 : 0;
 
-    std::vector<glsl::IndirectSample> samples = ShadowCache::compute_indirect_samples(settings);
-    this->indirect_sample_buffer = lava::make_buffer();
+    this->shadow_buffer = lava::make_buffer();
 
-    if(!this->indirect_sample_buffer->create_mapped(device, samples.data(), samples.size() * sizeof(glsl::IndirectSample), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT))
+    if (!this->shadow_buffer->create_mapped(device, &shadow_parameter, sizeof(glsl::ShadowParameter), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT))
     {
         return false;
     }
@@ -645,17 +398,15 @@ bool ShadowCache::create_buffers(lava::device_ptr device, const ShadowCacheSetti
 
 bool ShadowCache::create_descriptors(lava::device_ptr device)
 {
-    uint32_t indirect_levels = this->indirect_position_views.size();
-
     lava::VkDescriptorPoolSizes descriptor_type_count =
     {
-        { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2 },
-        { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6 + indirect_levels * 3}
+        { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 },
+        { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3 }
     };
 
     this->descriptor_pool = lava::make_descriptor_pool();
 
-    if (!this->descriptor_pool->create(device, descriptor_type_count, 1 + indirect_levels))
+    if (!this->descriptor_pool->create(device, descriptor_type_count, 1))
     {
         return false;
     }
@@ -664,11 +415,7 @@ bool ShadowCache::create_descriptors(lava::device_ptr device)
     this->descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 0: directional image array
     this->descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 1: spot image array
     this->descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 2: point image array
-    this->descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 3: indirect position array
-    this->descriptor->add_binding(4, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 4: indirect normal array
-    this->descriptor->add_binding(5, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 5: indirect flux array
-    this->descriptor->add_binding(6, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);         //descriptor-binding index 6: shadow parameter buffer
-    this->descriptor->add_binding(7, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);         //descriptor-binding index 7: indirect sample buffer
+    this->descriptor->add_binding(3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);         //descriptor-binding index 3: shadow buffer
 
     if (!this->descriptor->create(device))
     {
@@ -677,69 +424,43 @@ bool ShadowCache::create_descriptors(lava::device_ptr device)
 
     this->descriptor_set = this->descriptor->allocate(this->descriptor_pool->get());
 
-    std::array<VkDescriptorImageInfo, 6> image_infos;
-    std::array<VkDescriptorBufferInfo, 2> buffer_infos;
+    std::array<VkDescriptorImageInfo, 3> image_infos;
 
     VkDescriptorImageInfo& directional_image_info = image_infos[0];
-    directional_image_info.sampler = this->sampler_shadow_default;
-    directional_image_info.imageView = this->shadow_default_plane->get_view();
+    directional_image_info.sampler = this->sampler_default;
+    directional_image_info.imageView = this->image_default_plane->get_view();
     directional_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
     VkDescriptorImageInfo& spot_image_info = image_infos[1];
-    spot_image_info.sampler = this->sampler_shadow_default;
-    spot_image_info.imageView = this->shadow_default_plane->get_view();
+    spot_image_info.sampler = this->sampler_default;
+    spot_image_info.imageView = this->image_default_plane->get_view();
     spot_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
     VkDescriptorImageInfo& point_image_info = image_infos[2];
-    point_image_info.sampler = this->sampler_shadow_default;
-    point_image_info.imageView = this->shadow_default_cube->get_view();
+    point_image_info.sampler = this->sampler_default;
+    point_image_info.imageView = this->image_default_cube->get_view();
     point_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
-    if (this->shadow_view_directional != nullptr)
+    if (this->image_view_directional != nullptr)
     {
-        directional_image_info.sampler = this->sampler_shadow_directional;
-        directional_image_info.imageView = this->shadow_view_directional;
+        directional_image_info.sampler = this->sampler_directional;
+        directional_image_info.imageView = this->image_view_directional;
     }
 
-    if (this->shadow_view_spot != nullptr)
+    if (this->image_view_spot != nullptr)
     {
-        spot_image_info.sampler = this->sampler_shadow_spot;
-        spot_image_info.imageView = this->shadow_view_spot;
+        spot_image_info.sampler = this->sampler_spot;
+        spot_image_info.imageView = this->image_view_spot;
     }
 
-    if (this->shadow_view_point != nullptr)
+    if (this->image_view_point != nullptr)
     {
-        point_image_info.sampler = this->sampler_shadow_point;
-        point_image_info.imageView = this->shadow_view_point;
+        point_image_info.sampler = this->sampler_point;
+        point_image_info.imageView = this->image_view_point;
     }
 
-    VkDescriptorImageInfo& indirect_position_info = image_infos[3];
-    indirect_position_info.sampler = this->sampler_indirect_position;
-    indirect_position_info.imageView = this->indirect_position_array->get_view();
-    indirect_position_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-    VkDescriptorImageInfo& indirect_normal_info = image_infos[4];
-    indirect_normal_info.sampler = this->sampler_indirect_normal;
-    indirect_normal_info.imageView = this->indirect_normal_array->get_view();
-    indirect_normal_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-    VkDescriptorImageInfo& indirect_flux_info = image_infos[5];
-    indirect_flux_info.sampler = this->sampler_indirect_flux;
-    indirect_flux_info.imageView = this->indirect_flux_array->get_view();
-    indirect_flux_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-    VkDescriptorBufferInfo& shadow_parameter_info = buffer_infos[0];
-    shadow_parameter_info.buffer = this->shadow_parameter_buffer->get();
-    shadow_parameter_info.offset = 0;
-    shadow_parameter_info.range = VK_WHOLE_SIZE;
-
-    VkDescriptorBufferInfo& indirect_sample_info = buffer_infos[1];
-    indirect_sample_info.buffer = this->indirect_sample_buffer->get();
-    indirect_sample_info.offset = 0;
-    indirect_sample_info.range = VK_WHOLE_SIZE;
-
     std::vector<VkWriteDescriptorSet> descriptor_writes;
-    descriptor_writes.resize(image_infos.size() + buffer_infos.size());
+    descriptor_writes.resize(image_infos.size() + 1);
 
     for (uint32_t index = 0; index < image_infos.size(); index++)
     {
@@ -756,125 +477,35 @@ bool ShadowCache::create_descriptors(lava::device_ptr device)
         descriptor_write.pTexelBufferView = nullptr;
     }
 
-    for (uint32_t index = 0; index < buffer_infos.size(); index++)
-    {
-        uint32_t offset = image_infos.size() + index;
+    VkDescriptorBufferInfo buffer_info;
+    buffer_info.buffer = this->shadow_buffer->get();
+    buffer_info.offset = 0;
+    buffer_info.range = VK_WHOLE_SIZE;
 
-        VkWriteDescriptorSet& descriptor_write = descriptor_writes[offset];
-        descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-        descriptor_write.pNext = nullptr;
-        descriptor_write.dstSet = this->descriptor_set;
-        descriptor_write.dstBinding = offset;
-        descriptor_write.dstArrayElement = 0;
-        descriptor_write.descriptorCount = 1;
-        descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-        descriptor_write.pImageInfo = nullptr;
-        descriptor_write.pBufferInfo = &buffer_infos[index];
-        descriptor_write.pTexelBufferView = nullptr;
-    }
+    VkWriteDescriptorSet& descriptor_write = descriptor_writes.back();
+    descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptor_write.pNext = nullptr;
+    descriptor_write.dstSet = this->descriptor_set;
+    descriptor_write.dstBinding = 3;
+    descriptor_write.dstArrayElement = 0;
+    descriptor_write.descriptorCount = 1;
+    descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    descriptor_write.pImageInfo = nullptr;
+    descriptor_write.pBufferInfo = &buffer_info;
+    descriptor_write.pTexelBufferView = nullptr;
 
     vkUpdateDescriptorSets(device->get(), descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
 
-    this->downsample_descriptor = lava::make_descriptor();
-    this->downsample_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 0: indirect position array
-    this->downsample_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 1: indirect normal array
-    this->downsample_descriptor->add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); //descriptor-binding index 2: indirect flux array
-  
-    if (!this->downsample_descriptor->create(device))
-    {
-        return false;
-    }
-
-    for (uint32_t level = 0; level < indirect_levels; level++)
-    {
-        VkDescriptorSet downsample_descriptor_set = this->downsample_descriptor->allocate(this->descriptor_pool->get());
-        this->downsample_descriptor_sets.push_back(downsample_descriptor_set);
-
-        std::array<VkDescriptorImageInfo, 3> image_level_infos;
- 
-        VkDescriptorImageInfo& indirect_position_level_info = image_level_infos[0];
-        indirect_position_level_info.sampler = this->sampler_indirect_position;
-        indirect_position_level_info.imageView = this->indirect_position_views[level];
-        indirect_position_level_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-        VkDescriptorImageInfo& indirect_normal_level_info = image_level_infos[1];
-        indirect_normal_level_info.sampler = this->sampler_indirect_normal;
-        indirect_normal_level_info.imageView = this->indirect_normal_views[level];
-        indirect_normal_level_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-        VkDescriptorImageInfo& indirect_flux_level_info = image_level_infos[2];
-        indirect_flux_level_info.sampler = this->sampler_indirect_flux;
-        indirect_flux_level_info.imageView = this->indirect_flux_views[level];
-        indirect_flux_level_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-
-        std::array<VkWriteDescriptorSet, 3> descriptor_level_writes;
-
-        for (uint32_t index = 0; index < descriptor_level_writes.size(); index++)
-        {
-            VkWriteDescriptorSet& descriptor_write = descriptor_level_writes[index];
-            descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-            descriptor_write.pNext = nullptr;
-            descriptor_write.dstSet = downsample_descriptor_set;
-            descriptor_write.dstBinding = index;
-            descriptor_write.dstArrayElement = 0;
-            descriptor_write.descriptorCount = 1;
-            descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-            descriptor_write.pImageInfo = &image_level_infos[index];
-            descriptor_write.pBufferInfo = nullptr;
-            descriptor_write.pTexelBufferView = nullptr;
-        }
-
-        vkUpdateDescriptorSets(device->get(), descriptor_level_writes.size(), descriptor_level_writes.data(), 0, nullptr);
-    }
-
-    return true;
-}
-
-bool ShadowCache::create_framebuffers(lava::device_ptr device, const ShadowCacheSettings& settings)
-{
-    uint32_t indirect_levels = this->indirect_position_views.size();
-    uint32_t indirect_layers = this->indirect_position_array->get_info().arrayLayers;
-
-    for (uint32_t level = 0; level < indirect_levels; level++)
-    {
-        std::array<VkImageView, 3> attachments =
-        {
-            this->indirect_position_views[level],
-            this->indirect_normal_views[level],
-            this->indirect_flux_views[level]
-        };
-
-        VkFramebufferCreateInfo create_info;
-        create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
-        create_info.pNext = nullptr;
-        create_info.flags = 0;
-        create_info.renderPass = this->downsample_pass->get();
-        create_info.attachmentCount = attachments.size();
-        create_info.pAttachments = attachments.data();
-        create_info.width = settings.resolution >> level;
-        create_info.height = settings.resolution >> level;
-        create_info.layers = indirect_layers;
-
-        VkFramebuffer framebuffer = VK_NULL_HANDLE;
-
-        if (vkCreateFramebuffer(device->get(), &create_info, lava::memory::alloc(), &framebuffer) != VK_SUCCESS)
-        {
-            return false;
-        }
-
-        this->downsample_framebuffers.push_back(framebuffer);
-    }
-
     return true;
 }
 
-bool ShadowCache::create_shadow_pass(lava::device_ptr device, const ShadowCacheSettings& settings)
+bool ShadowCache::create_render_pass(lava::device_ptr device, const ShadowCacheSettings& settings)
 {
     VkClearValue clear_value;
     clear_value.depthStencil.depth = 1.0;
     clear_value.depthStencil.stencil = 0;
     
-    lava::attachment::ptr attachment = lava::make_attachment(this->shadow_array->get_format());
+    lava::attachment::ptr attachment = lava::make_attachment(this->image_array->get_format());
     attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
     attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
     attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
@@ -890,13 +521,13 @@ bool ShadowCache::create_shadow_pass(lava::device_ptr device, const ShadowCacheS
     subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
     subpass_end_dependency->set_access_mask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
 
-    this->shadow_pass = lava::make_render_pass(device);
-    this->shadow_pass->add(subpass);
-    this->shadow_pass->add(subpass_begin_dependency);
-    this->shadow_pass->add(subpass_end_dependency);
-    this->shadow_pass->add(attachment); //location 0: shadow array
-    this->shadow_pass->set_layers(this->shadow_array->get_info().arrayLayers);
-    this->shadow_pass->set_clear_values(
+    this->render_pass = lava::make_render_pass(device);
+    this->render_pass->add(subpass);
+    this->render_pass->add(subpass_begin_dependency);
+    this->render_pass->add(subpass_end_dependency);
+    this->render_pass->add(attachment); //location 0: image array
+    this->render_pass->set_layers(this->image_array->get_info().arrayLayers);
+    this->render_pass->set_clear_values(
     { 
         clear_value
     });
@@ -909,10 +540,10 @@ bool ShadowCache::create_shadow_pass(lava::device_ptr device, const ShadowCacheS
 
     lava::VkImageViews framebuffer_views =
     {
-        this->shadow_array->get_view()
+        this->image_array->get_view()
     };
 
-    if (!this->shadow_pass->create({ framebuffer_views }, framebuffer_area))
+    if (!this->render_pass->create({ framebuffer_views }, framebuffer_area))
     {
         return false;
     }
@@ -920,422 +551,79 @@ bool ShadowCache::create_shadow_pass(lava::device_ptr device, const ShadowCacheS
     return true;
 }
 
-bool ShadowCache::create_shadow_pipeline(lava::device_ptr device, Scene::Ptr scene)
+bool ShadowCache::create_pipeline(lava::device_ptr device, Scene::Ptr scene)
 {
     VkPushConstantRange push_range;
     push_range.offset = 0;
     push_range.size = sizeof(uint32_t) * 3; //layer-index, light-index, frustum-index
     push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT;
 
-    this->shadow_pipeline_layout = lava::make_pipeline_layout();
-    this->shadow_pipeline_layout->add_push_constant_range(push_range);
-    this->shadow_pipeline_layout->add(scene->get_light_descriptor()); //descriptor-set index 0: light-buffer
-    this->shadow_pipeline_layout->add(scene->get_mesh_descriptor());  //descriptor-set index 1: mesh-buffer
-
-    if (!this->shadow_pipeline_layout->create(device))
-    {
-        return false;
-    }
-
-    this->shadow_pipeline = lava::make_graphics_pipeline(device);
-    this->shadow_pipeline->set_layout(this->shadow_pipeline_layout);
-    this->shadow_pipeline->set_rasterization_front_face(VK_FRONT_FACE_CLOCKWISE);
-    this->shadow_pipeline->set_rasterization_cull_mode(VK_CULL_MODE_BACK_BIT);
-    this->shadow_pipeline->set_depth_test_and_write(true, true);
-    this->shadow_pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
-
-    scene->set_vertex_input_only_position(this->shadow_pipeline.get());
-
-    if (!this->shadow_pipeline->add_shader(lava::file_data("dpr/binary/shadow_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
-    {
-        return false;
-    }
-
-    if (!this->shadow_pipeline->add_shader(lava::file_data("dpr/binary/shadow_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
-    {
-        return false;
-    }
-
-    this->shadow_pipeline->on_process = [this](VkCommandBuffer command_buffer)
-    {
-        this->pipeline_shadow(command_buffer);
-    };
-
-    if (!this->shadow_pipeline->create(this->shadow_pass->get()))
-    {
-        return false;
-    }
-
-    this->shadow_pass->add_front(this->shadow_pipeline);
-
-    return true;
-}
-
-bool ShadowCache::create_indirect_pass(lava::device_ptr device, const ShadowCacheSettings& settings)
-{
-    VkClearValue clear_depth;
-    clear_depth.depthStencil.depth = 1.0;
-    clear_depth.depthStencil.stencil = 0;
-    
-    VkClearValue clear_color;
-    clear_color.color.float32[0] = 0.0f;
-    clear_color.color.float32[1] = 0.0f;
-    clear_color.color.float32[2] = 0.0f;
-    clear_color.color.float32[3] = 0.0f;
-
-    lava::attachment::ptr depth_attachment = lava::make_attachment(this->shadow_array->get_format());
-    depth_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
-    depth_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    depth_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    lava::attachment::ptr position_attachment = lava::make_attachment(this->indirect_position_array->get_format());
-    position_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
-    position_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    position_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    lava::attachment::ptr normal_attachment = lava::make_attachment(this->indirect_normal_array->get_format());
-    normal_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
-    normal_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    normal_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    lava::attachment::ptr flux_attachment = lava::make_attachment(this->indirect_flux_array->get_format());
-    flux_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
-    flux_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    flux_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    VkAttachmentReference position_attachment_reference;
-    position_attachment_reference.attachment = 1;
-    position_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    VkAttachmentReference normal_attachment_reference;
-    normal_attachment_reference.attachment = 2;
-    normal_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    VkAttachmentReference flux_attachment_reference;
-    flux_attachment_reference.attachment = 3;
-    flux_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    lava::subpass::ptr subpass = lava::make_subpass();
-    subpass->set_depth_stencil_attachment(0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
-    subpass->set_color_attachments(
-    {
-        position_attachment_reference,
-        normal_attachment_reference,
-        flux_attachment_reference
-    });
-
-    lava::subpass_dependency::ptr subpass_begin_dependency = lava::make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
-    subpass_begin_dependency->set_stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
-    subpass_begin_dependency->set_access_mask(0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
-
-    lava::subpass_dependency::ptr subpass_end_dependency = lava::make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
-    subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
-    subpass_end_dependency->set_access_mask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
-
-    this->indirect_pass = lava::make_render_pass(device);
-    this->indirect_pass->add(subpass);
-    this->indirect_pass->add(subpass_begin_dependency);
-    this->indirect_pass->add(subpass_end_dependency);
-    this->indirect_pass->add(depth_attachment);    //location 0: shadow array
-    this->indirect_pass->add(position_attachment); //location 1: indirect position array
-    this->indirect_pass->add(normal_attachment);   //location 2: indirect normal array
-    this->indirect_pass->add(flux_attachment);     //location 3: indirect flux array
-    this->indirect_pass->set_layers(this->indirect_position_array->get_info().arrayLayers);
-    this->indirect_pass->set_clear_values(
-    { 
-        clear_depth,
-        clear_color,
-        clear_color,
-        clear_color
-    });
-
-    lava::rect framebuffer_area =
-    {
-        glm::vec2(0.0f),
-        glm::vec2(settings.resolution)
-    };
-
-    lava::VkImageViews framebuffer_views =
-    {
-        this->shadow_view_directional,
-        this->indirect_position_views[0],
-        this->indirect_normal_views[0],
-        this->indirect_flux_views[0]
-    };
-
-    if (!this->indirect_pass->create({ framebuffer_views }, framebuffer_area))
-    {
-        return false;
-    }
-
-    return true;
-}
-
-bool ShadowCache::create_indirect_pipeline(lava::device_ptr device, Scene::Ptr scene)
-{
-    VkPushConstantRange push_range;
-    push_range.offset = 0;
-    push_range.size = sizeof(uint32_t) * 2; //light-index, indirect-resolution
-    push_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
-
-    this->indirect_pipeline_layout = lava::make_pipeline_layout();
-    this->indirect_pipeline_layout->add_push_constant_range(push_range);
-    this->indirect_pipeline_layout->add(scene->get_light_descriptor());     //descriptor-set index 0: light-buffer
-    this->indirect_pipeline_layout->add(scene->get_mesh_descriptor());      //descriptor-set index 1: mesh-buffer
-    this->indirect_pipeline_layout->add(scene->get_material_descriptor());  //descriptor-set index 2: material-buffer
-
-    if (!this->indirect_pipeline_layout->create(device))
-    {
-        return false;
-    }
-
-    VkPipelineColorBlendAttachmentState blend_state;
-    blend_state.blendEnable = VK_FALSE;
-    blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.colorBlendOp = VK_BLEND_OP_ADD;
-    blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.alphaBlendOp = VK_BLEND_OP_ADD;
-    blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
-
-    this->indirect_pipeline = lava::make_graphics_pipeline(device);
-    this->indirect_pipeline->set_layout(this->indirect_pipeline_layout);
-    this->indirect_pipeline->set_rasterization_front_face(VK_FRONT_FACE_CLOCKWISE);
-    this->indirect_pipeline->set_rasterization_cull_mode(VK_CULL_MODE_BACK_BIT);
-    this->indirect_pipeline->set_depth_test_and_write(true, true);
-    this->indirect_pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
-    this->indirect_pipeline->add_color_blend_attachment(blend_state);
-    this->indirect_pipeline->add_color_blend_attachment(blend_state);
-    this->indirect_pipeline->add_color_blend_attachment(blend_state);
-
-    scene->set_vertex_input(this->indirect_pipeline.get());
-
-    if (!this->indirect_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
-    {
-        return false;
-    }
-
-    if (!this->indirect_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
-    {
-        return false;
-    }
-
-    if (!this->indirect_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
-    {
-        return false;
-    }
-
-    this->indirect_pipeline->on_process = [this](VkCommandBuffer command_buffer)
-    {
-        this->pipeline_indirect(command_buffer);
-    };
-
-    if (!this->indirect_pipeline->create(this->indirect_pass->get()))
-    {
-        return false;
-    }
-
-    this->indirect_pass->add_front(this->indirect_pipeline);
-
-    return true;
-}
-
-bool ShadowCache::create_downsample_pass(lava::device_ptr device)
-{
-    lava::attachment::ptr position_attachment = lava::make_attachment(this->indirect_position_array->get_format());
-    position_attachment->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
-    position_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    position_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    lava::attachment::ptr normal_attachment = lava::make_attachment(this->indirect_normal_array->get_format());
-    normal_attachment->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
-    normal_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    normal_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    lava::attachment::ptr flux_attachment = lava::make_attachment(this->indirect_flux_array->get_format());
-    flux_attachment->set_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
-    flux_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
-    flux_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
-    VkAttachmentReference position_attachment_reference;
-    position_attachment_reference.attachment = 0;
-    position_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    VkAttachmentReference normal_attachment_reference;
-    normal_attachment_reference.attachment = 1;
-    normal_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    VkAttachmentReference flux_attachment_reference;
-    flux_attachment_reference.attachment = 2;
-    flux_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-    lava::subpass::ptr subpass = lava::make_subpass();
-    subpass->set_color_attachments(
-    {
-        position_attachment_reference,
-        normal_attachment_reference,
-        flux_attachment_reference
-    });
-
-    lava::subpass_dependency::ptr subpass_begin_dependency = lava::make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
-    subpass_begin_dependency->set_stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
-    subpass_begin_dependency->set_access_mask(0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
-
-    lava::subpass_dependency::ptr subpass_end_dependency = lava::make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
-    subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
-    subpass_end_dependency->set_access_mask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
-
-    this->downsample_pass = lava::make_render_pass(device);
-    this->downsample_pass->add(subpass);
-    this->downsample_pass->add(subpass_begin_dependency);
-    this->downsample_pass->add(subpass_end_dependency);
-    this->downsample_pass->add(position_attachment); //location 0: indirect position array
-    this->downsample_pass->add(normal_attachment);   //location 1: indirect normal array
-    this->downsample_pass->add(flux_attachment);     //location 2: indirect flux array
-    this->downsample_pass->set_layers(this->indirect_position_array->get_info().arrayLayers);
-
-    lava::rect framebuffer_area =
-    {
-        glm::vec2(0.0f),
-        glm::vec2(settings.resolution)
-    };
-
-    lava::VkImageViews framebuffer_views =
-    {
-        this->indirect_position_views[0],
-        this->indirect_normal_views[0],
-        this->indirect_flux_views[0]
-    };
+    this->pipeline_layout = lava::make_pipeline_layout();
+    this->pipeline_layout->add_push_constant_range(push_range);
+    this->pipeline_layout->add(scene->get_light_descriptor()); //descriptor-set index 0: light-buffer
+    this->pipeline_layout->add(scene->get_mesh_descriptor());  //descriptor-set index 1: mesh-buffer
 
-    if (!this->downsample_pass->create({ framebuffer_views }, framebuffer_area))
+    if (!this->pipeline_layout->create(device))
     {
         return false;
     }
 
-    return true;
-}
-
-bool ShadowCache::create_downsample_pipeline(lava::device_ptr device)
-{
-    this->downsample_pipeline_layout = lava::make_pipeline_layout();
-    this->downsample_pipeline_layout->add(this->downsample_descriptor);     //descriptor-set index 0: downsample-info
-
-    if (!this->downsample_pipeline_layout->create(device))
-    {
-        return false;
-    }
+    this->pipeline = lava::make_graphics_pipeline(device);
+    this->pipeline->set_layout(this->pipeline_layout);
+    this->pipeline->set_rasterization_front_face(VK_FRONT_FACE_CLOCKWISE);
+    this->pipeline->set_rasterization_cull_mode(VK_CULL_MODE_BACK_BIT);
+    this->pipeline->set_depth_test_and_write(true, true);
+    this->pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
 
-    VkPipelineColorBlendAttachmentState blend_state;
-    blend_state.blendEnable = VK_FALSE;
-    blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.colorBlendOp = VK_BLEND_OP_ADD;
-    blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
-    blend_state.alphaBlendOp = VK_BLEND_OP_ADD;
-    blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
-
-    this->downsample_pipeline = lava::make_graphics_pipeline(device);
-    this->downsample_pipeline->set_layout(this->downsample_pipeline_layout);
-    this->downsample_pipeline->set_input_assembly_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
-    this->downsample_pipeline->add_color_blend_attachment(blend_state);
-    this->downsample_pipeline->add_color_blend_attachment(blend_state);
-    this->downsample_pipeline->add_color_blend_attachment(blend_state);
-
-    if (!this->downsample_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_downsample_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
-    {
-        return false;
-    }
+    scene->set_vertex_input_only_position(this->pipeline.get());
 
-    if (!this->downsample_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_downsample_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
+    if (!this->pipeline->add_shader(lava::file_data("dpr/binary/shadow_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
     {
         return false;
     }
 
-    if (!this->downsample_pipeline->add_shader(lava::file_data("dpr/binary/shadow_indirect_downsample_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
+    if (!this->pipeline->add_shader(lava::file_data("dpr/binary/shadow_geometry.spirv"), VK_SHADER_STAGE_GEOMETRY_BIT))
     {
         return false;
     }
 
-    this->downsample_pipeline->on_process = [this](VkCommandBuffer command_buffer)
+    this->pipeline->on_process = [this](VkCommandBuffer command_buffer)
     {
-        //NOTE: The render pass setup is done by hand in order to use different framebuffers
+        this->pipline_function(command_buffer);
     };
 
-    if (!this->downsample_pipeline->create(this->downsample_pass->get()))
+    if (!this->pipeline->create(this->render_pass->get()))
     {
         return false;
     }
 
-    this->downsample_pass->add_front(this->downsample_pipeline);
+    this->render_pass->add_front(this->pipeline);
 
     return true;
 }
 
-std::vector<glsl::IndirectSample> ShadowCache::compute_indirect_samples(const ShadowCacheSettings& settings)
-{
-    std::default_random_engine random_engine(0);
-    std::uniform_real_distribution<float> distrubution(0.0f, 1.0f);
-
-    uint32_t sample_count = settings.indirect_sample_count;
-    std::vector<glsl::IndirectSample> samples(sample_count);
-    float weight_sum = 0.0f;
-
-    for (uint32_t index = 0; index < sample_count; index++)
-    {
-        float random1 = distrubution(random_engine);
-        float random2 = distrubution(random_engine);
-
-        glm::vec2 coord;
-        coord.x = settings.indirect_radius * random1 * glm::sin(glm::two_pi<float>() * random2);
-        coord.y = settings.indirect_radius * random1 * glm::cos(glm::two_pi<float>() * random2);
-
-        float weight = random1 * random1;
-
-        samples[index].coord = coord;
-        samples[index].weight = weight;
-        samples[index].padding = 0;
-
-        weight_sum += weight;
-    }
-
-    if(weight_sum > 0.0f)
-    {
-        for(glsl::IndirectSample& sample : samples)
-        {
-            sample.weight /= weight_sum;
-        }
-    }
-
-    return samples;
-}
-
-void ShadowCache::pipeline_shadow(VkCommandBuffer command_buffer)
+void ShadowCache::pipline_function(VkCommandBuffer command_buffer)
 {
-    this->shadow_pipeline_layout->bind(command_buffer, this->scene->get_light_descriptor_set(this->frame), 0);
+    this->pipeline_layout->bind(command_buffer, this->scene->get_light_descriptor_set(this->frame), 0);
 
     const std::vector<SceneLight>& lights = this->scene->get_lights();
 
-    if(!this->settings.use_directional_indirect)
+    for (uint32_t light_index = 0; light_index < lights.size(); light_index++)
     {
-        for (uint32_t light_index = 0; light_index < lights.size(); light_index++)
-        {
-            const SceneLight& light = lights[light_index];
+        const SceneLight& light = lights[light_index];
 
-            if (light.data.type == LIGHT_TYPE_DIRECTIONAL)
-            {
-                uint32_t layer_index = this->directional_offset + light.data.type_index;
+        if (light.data.type == LIGHT_TYPE_DIRECTIONAL)
+        {
+            uint32_t layer_index = this->directional_offset + light.data.type_index;
 
-                std::array<uint32_t, 3> constants;
-                constants[0] = layer_index;
-                constants[1] = light_index;
-                constants[2] = 0;
+            std::array<uint32_t, 3> constants;
+            constants[0] = layer_index;
+            constants[1] = light_index;
+            constants[2] = 0;
 
-                vkCmdPushConstants(command_buffer, this->shadow_pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
+            vkCmdPushConstants(command_buffer, this->pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
 
-                this->pipeline_scene_shadow(command_buffer);
-            }
+            this->pipline_scene(command_buffer);
         }
     }
 
@@ -1352,9 +640,9 @@ void ShadowCache::pipeline_shadow(VkCommandBuffer command_buffer)
             constants[1] = light_index;
             constants[2] = 0;
 
-            vkCmdPushConstants(command_buffer, this->shadow_pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
+            vkCmdPushConstants(command_buffer, this->pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
 
-            this->pipeline_scene_shadow(command_buffer);
+            this->pipline_scene(command_buffer);
         }
     }
 
@@ -1373,91 +661,16 @@ void ShadowCache::pipeline_shadow(VkCommandBuffer command_buffer)
                 constants[1] = light_index;
                 constants[2] = frustum_index;
 
-                vkCmdPushConstants(command_buffer, this->shadow_pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
+                vkCmdPushConstants(command_buffer, this->pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
 
-                this->pipeline_scene_shadow(command_buffer);
+                this->pipline_scene(command_buffer);
             }
         }
     }
 }
 
-void ShadowCache::pipeline_indirect(VkCommandBuffer command_buffer)
-{
-    if(!this->settings.use_directional_indirect)
-    {
-        return;
-    }
-
-    this->indirect_pipeline_layout->bind(command_buffer, this->scene->get_light_descriptor_set(this->frame), 0);
-
-    const std::vector<SceneLight>& lights = this->scene->get_lights();
-
-    for (uint32_t light_index = 0; light_index < lights.size(); light_index++)
-    {
-        const SceneLight& light = lights[light_index];
-
-        if (light.data.type == LIGHT_TYPE_DIRECTIONAL)
-        {
-            std::array<uint32_t, 2> constants;
-            constants[0] = light_index;
-            constants[1] = this->settings.resolution;
-
-            vkCmdPushConstants(command_buffer, this->indirect_pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(uint32_t) * constants.size(), constants.data());
-
-            this->pipeline_scene_indirect(command_buffer);
-        }
-    }
-}
-
-void ShadowCache::pipeline_downsample(VkCommandBuffer command_buffer)
-{
-    uint32_t indirect_levels = this->indirect_position_views.size();
-    uint32_t indirect_layers = this->indirect_position_array->get_info().arrayLayers;
-
-    for (uint32_t level = 1; level < indirect_levels; level++)
-    {
-        VkRenderPassBeginInfo begin_info;
-        begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
-        begin_info.pNext = nullptr;
-        begin_info.renderPass = this->downsample_pass->get();
-        begin_info.framebuffer = this->downsample_framebuffers[level];
-        begin_info.renderArea.offset.x = 0;
-        begin_info.renderArea.offset.y = 0;
-        begin_info.renderArea.extent.width = this->settings.resolution >> level;
-        begin_info.renderArea.extent.height = this->settings.resolution >> level;
-        begin_info.clearValueCount = 0;
-        begin_info.pClearValues = nullptr;
-
-        vkCmdBeginRenderPass(command_buffer, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
-
-        vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, this->downsample_pipeline->get());
-        vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, this->downsample_pipeline_layout->get(), 0, 1, &this->downsample_descriptor_sets[level - 1], 0, nullptr);
-        
-        vkCmdDraw(command_buffer, 4, indirect_layers, 0, 0); //draw fullscreen quad
-
-        vkCmdEndRenderPass(command_buffer);
-    }
-}
-
-void ShadowCache::pipeline_scene_shadow(VkCommandBuffer command_buffer)
-{
-    for (const SceneMesh& mesh : this->scene->get_meshes())
-    {
-        if (!mesh.cast_shadow)
-        {
-            continue;
-        }
-
-        this->shadow_pipeline_layout->bind(command_buffer, mesh.descriptor_set[this->frame], 1);
-
-        mesh.mesh->bind_draw(command_buffer);
-    }
-}
-
-void ShadowCache::pipeline_scene_indirect(VkCommandBuffer command_buffer)
+void ShadowCache::pipline_scene(VkCommandBuffer command_buffer)
 {
-    const std::vector<SceneMaterial>& materials = this->scene->get_materials();
-
     for (const SceneMesh& mesh : this->scene->get_meshes())
     {
         if (!mesh.cast_shadow)
@@ -1465,9 +678,7 @@ void ShadowCache::pipeline_scene_indirect(VkCommandBuffer command_buffer)
             continue;
         }
 
-        const SceneMaterial& material = materials[mesh.material_index];
-        this->indirect_pipeline_layout->bind(command_buffer, mesh.descriptor_set[this->frame], 1);
-        this->indirect_pipeline_layout->bind(command_buffer, material.descriptor_set, 2);
+        this->pipeline_layout->bind(command_buffer, mesh.descriptor_set[this->frame], 1);
 
         mesh.mesh->bind_draw(command_buffer);
     }
diff --git a/src/utility/shadow_cache.hpp b/src/utility/shadow_cache.hpp
index 710951d15fc820e55aeec289f7886522dd3289b4..895bbe73fa6e2bbb2220f745b90f156e0ef758d2 100644
--- a/src/utility/shadow_cache.hpp
+++ b/src/utility/shadow_cache.hpp
@@ -38,25 +38,13 @@
 
 #include "scene.hpp"
 
-namespace glsl
-{
-    using namespace glm;
-    using uint = glm::uint32;
-
-#include "res/dpr/data/shadow_data.inc"
-}
-
 struct ShadowCacheSettings
 {
     uint32_t resolution = 1024;
 
-    bool use_directional_shadow = true;
-    bool use_directional_indirect = true;
-    bool use_spot_shadow = true;
-    bool use_point_shadow = true;
-
-    uint32_t indirect_sample_count = 32;
-    float indirect_radius = 5.0;
+    bool use_directional = true;
+    bool use_spot = true;
+    bool use_point = true;
 };
 
 class ShadowCache
@@ -78,28 +66,17 @@ public:
     const ShadowCacheSettings& get_settings() const;
 
 private:
-    bool create_image_view(lava::device_ptr device, lava::image::ptr image_array, uint32_t layer_offset, uint32_t layer_count, uint32_t level, VkImageAspectFlags aspect, VkImageViewType view_type, VkImageView& view);
-    bool create_shadow_sampler(lava::device_ptr device, VkSampler& sampler);
-    bool create_indirect_sampler(lava::device_ptr device, VkSampler& sampler);
+    bool create_image_view(lava::device_ptr device, lava::image::ptr image_array, uint32_t layer_offset, uint32_t layer_count, VkImageViewType view_type, VkImageView& view);
+    bool create_sampler(lava::device_ptr device, VkSampler& sampler);
     bool create_images(lava::device_ptr device, Scene::Ptr scene, const ShadowCacheSettings& settings);
     bool create_buffers(lava::device_ptr device, const ShadowCacheSettings& settings);
     bool create_descriptors(lava::device_ptr device);
-    bool create_framebuffers(lava::device_ptr device, const ShadowCacheSettings& settings);
-
-    bool create_shadow_pass(lava::device_ptr device, const ShadowCacheSettings& settings);
-    bool create_shadow_pipeline(lava::device_ptr device, Scene::Ptr scene);
-    bool create_indirect_pass(lava::device_ptr device, const ShadowCacheSettings& settings);
-    bool create_indirect_pipeline(lava::device_ptr device, Scene::Ptr scene);
-    bool create_downsample_pass(lava::device_ptr device);
-    bool create_downsample_pipeline(lava::device_ptr device);
-
-    void pipeline_shadow(VkCommandBuffer command_buffer);
-    void pipeline_indirect(VkCommandBuffer command_buffer);
-    void pipeline_downsample(VkCommandBuffer command_buffer);
-    void pipeline_scene_shadow(VkCommandBuffer command_buffer);
-    void pipeline_scene_indirect(VkCommandBuffer command_buffer);
 
-    static std::vector<glsl::IndirectSample> compute_indirect_samples(const ShadowCacheSettings& settings);
+    bool create_render_pass(lava::device_ptr device, const ShadowCacheSettings& settings);
+    bool create_pipeline(lava::device_ptr device, Scene::Ptr scene);
+    
+    void pipline_function(VkCommandBuffer command_buffer);
+    void pipline_scene(VkCommandBuffer command_buffer);
 
 private:
     Scene::Ptr scene;
@@ -110,53 +87,29 @@ private:
     uint32_t spot_offset = 0;
     uint32_t point_offset = 0;
 
-    lava::image::ptr shadow_default_plane;
-    lava::image::ptr shadow_default_cube;
+    lava::image::ptr image_default_plane;
+    lava::image::ptr image_default_cube;
     bool image_default_cleared = false;
 
-    lava::image::ptr shadow_array;
-    VkImageView shadow_view_directional = nullptr;
-    VkImageView shadow_view_spot = nullptr;
-    VkImageView shadow_view_point = nullptr;
-    
-    lava::image::ptr indirect_position_array;
-    lava::image::ptr indirect_normal_array;
-    lava::image::ptr indirect_flux_array;
-
-    std::vector<VkImageView> indirect_position_views;
-    std::vector<VkImageView> indirect_normal_views;
-    std::vector<VkImageView> indirect_flux_views;
+    lava::image::ptr image_array;
+    VkImageView image_view_directional = nullptr;
+    VkImageView image_view_spot = nullptr;
+    VkImageView image_view_point = nullptr;
 
-    VkSampler sampler_shadow_default = nullptr;
-    VkSampler sampler_shadow_directional = nullptr;
-    VkSampler sampler_shadow_spot = nullptr;
-    VkSampler sampler_shadow_point = nullptr;
-    VkSampler sampler_indirect_position = nullptr;
-    VkSampler sampler_indirect_normal = nullptr;
-    VkSampler sampler_indirect_flux = nullptr;
+    VkSampler sampler_default = nullptr;
+    VkSampler sampler_directional = nullptr;
+    VkSampler sampler_spot = nullptr;
+    VkSampler sampler_point = nullptr;
 
-    lava::buffer::ptr shadow_parameter_buffer;
-    lava::buffer::ptr indirect_sample_buffer;
+    lava::buffer::ptr shadow_buffer;
 
     lava::descriptor::pool::ptr descriptor_pool;
     lava::descriptor::ptr descriptor;
     VkDescriptorSet descriptor_set = nullptr;
     
-    lava::pipeline_layout::ptr shadow_pipeline_layout;
-    lava::graphics_pipeline::ptr shadow_pipeline;
-    lava::render_pass::ptr shadow_pass;
-
-    lava::pipeline_layout::ptr indirect_pipeline_layout;
-    lava::graphics_pipeline::ptr indirect_pipeline;
-    lava::render_pass::ptr indirect_pass;
-
-    lava::descriptor::ptr downsample_descriptor;
-    std::vector<VkDescriptorSet> downsample_descriptor_sets;
-
-    lava::pipeline_layout::ptr downsample_pipeline_layout;
-    lava::graphics_pipeline::ptr downsample_pipeline;
-    lava::render_pass::ptr downsample_pass;
-    std::vector<VkFramebuffer> downsample_framebuffers;
+    lava::pipeline_layout::ptr pipeline_layout;
+    lava::graphics_pipeline::ptr pipeline;
+    lava::render_pass::ptr render_pass;
 };
 
 ShadowCache::Ptr make_shadow_cache();
\ No newline at end of file