diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 4ea6cb3cbc1537ebd572e9483cc4e20acc3e46ab..d1acdbcbf011aab7de96f060aeb55ede83c516eb 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -4,6 +4,7 @@ add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
 add_subdirectory(first_scene)
 add_subdirectory(particle_simulation)
+add_subdirectory(sph)
 add_subdirectory(voxelization)
 add_subdirectory(mesh_shader)
 add_subdirectory(saf_r)
diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp
index ddbe6195e66e78504d4bccb32b3b09ff680ab414..ae3c6795a66cdc81297986acb224a63055d02c44 100644
--- a/projects/particle_simulation/src/main.cpp
+++ b/projects/particle_simulation/src/main.cpp
@@ -22,7 +22,7 @@ int main(int argc, const char **argv) {
             {vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
 			{ VK_KHR_SWAPCHAIN_EXTENSION_NAME }
     );
-	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, false);
     vkcv::Window& window = core.getWindow(windowHandle);
 	vkcv::camera::CameraManager cameraManager(window);
 
@@ -61,7 +61,7 @@ int main(int argc, const char **argv) {
 			shaderPathFragment = "shaders/shader_space.frag";
     	} else
 		if (strcmp(argv[i], "--water") == 0) {
-			shaderPathCompute = "shaders/shader_water.comp";
+			shaderPathCompute = "shaders/shader_water1.comp";
 			shaderPathFragment = "shaders/shader_water.frag";
 		} else
 		if (strcmp(argv[i], "--gravity") == 0) {
diff --git a/projects/sph/.gitignore b/projects/sph/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4964f89e973f38358aa57f564f56d3d4b0c328a9
--- /dev/null
+++ b/projects/sph/.gitignore
@@ -0,0 +1 @@
+particle_simulation
\ No newline at end of file
diff --git a/projects/sph/CMakeLists.txt b/projects/sph/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..592aa4409ae3e01f4054f430bab7c424d25219d0
--- /dev/null
+++ b/projects/sph/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.16)
+project(sph)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# this should fix the execution path to load local files from the project
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+# adding source files to the project
+add_executable(sph
+		src/main.cpp
+		src/Particle.hpp 
+		src/Particle.cpp
+		src/BloomAndFlares.hpp
+		src/BloomAndFlares.cpp
+		src/PipelineInit.hpp
+		src/PipelineInit.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(sph PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(sph PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+	# in addition to setting the output directory, the working directory has to be set
+	# by default visual studio sets the working directory to the build directory, when using the debugger
+	set_target_properties(sph PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(sph SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(sph vkcv vkcv_testing vkcv_camera vkcv_shader_compiler)
diff --git a/projects/sph/shaders/bloom/composite.comp b/projects/sph/shaders/bloom/composite.comp
new file mode 100644
index 0000000000000000000000000000000000000000..87b5ddb975106232d1cd3b6e5b8dc7e623dd0b59
--- /dev/null
+++ b/projects/sph/shaders/bloom/composite.comp
@@ -0,0 +1,38 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set=0, binding=0) uniform texture2D                          blurImage;
+layout(set=0, binding=1) uniform texture2D                          lensImage;
+layout(set=0, binding=2) uniform sampler                            linearSampler;
+layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D            colorBuffer;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+
+void main()
+{
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){
+        return;
+    }
+
+    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
+    vec2  pixel_size    = vec2(1.0f) / textureSize(sampler2D(blurImage, linearSampler), 0);
+    vec2  UV            = pixel_coord.xy * pixel_size;
+
+    vec4 composite_color = vec4(0.0f);
+
+    vec3 blur_color   = texture(sampler2D(blurImage, linearSampler), UV).rgb;
+    vec3 lens_color   = texture(sampler2D(lensImage, linearSampler), UV).rgb;
+    vec3 main_color   = imageLoad(colorBuffer, pixel_coord).rgb;
+
+    // composite blur and lens features
+    float bloom_weight = 0.01f;
+    float lens_weight  = 0.f;
+    float main_weight = 1 - (bloom_weight + lens_weight);
+
+    composite_color.rgb = blur_color * bloom_weight +
+                          lens_color * lens_weight  +
+                          main_color * main_weight;
+
+    imageStore(colorBuffer, pixel_coord, composite_color);
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/bloom/downsample.comp b/projects/sph/shaders/bloom/downsample.comp
new file mode 100644
index 0000000000000000000000000000000000000000..2ab00c7c92798769153634f3479c5b7f3fb61d94
--- /dev/null
+++ b/projects/sph/shaders/bloom/downsample.comp
@@ -0,0 +1,76 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set=0, binding=0) uniform texture2D                          inBlurImage;
+layout(set=0, binding=1) uniform sampler                            inImageSampler;
+layout(set=0, binding=2, r11f_g11f_b10f) uniform writeonly image2D  outBlurImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+
+void main()
+{
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outBlurImage)))){
+        return;
+    }
+
+    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
+    vec2  pixel_size    = vec2(1.0f) / imageSize(outBlurImage);
+    vec2  UV            = pixel_coord.xy * pixel_size;
+    vec2  UV_offset     = UV + 0.5f * pixel_size;
+
+    vec2 color_fetches[13] = {
+        // center neighbourhood (RED)
+        vec2(-1,  1), // LT
+        vec2(-1, -1), // LB
+        vec2( 1, -1), // RB
+        vec2( 1,  1), // RT
+
+        vec2(-2, 2), // LT
+        vec2( 0, 2), // CT
+        vec2( 2, 2), // RT
+
+        vec2(0 ,-2), // LC
+        vec2(0 , 0), // CC
+        vec2(2,  0), // CR
+
+        vec2(-2, -2), // LB
+        vec2(0 , -2), // CB
+        vec2(2 , -2)  // RB
+    };
+
+    float color_weights[13] = {
+        // 0.5f
+        1.f/8.f,
+        1.f/8.f,
+        1.f/8.f,
+        1.f/8.f,
+
+        // 0.125f
+        1.f/32.f,
+        1.f/16.f,
+        1.f/32.f,
+
+        // 0.25f
+        1.f/16.f,
+        1.f/8.f,
+        1.f/16.f,
+
+        // 0.125f
+        1.f/32.f,
+        1.f/16.f,
+        1.f/32.f
+    };
+
+    vec3 sampled_color = vec3(0.0f);
+
+    for(uint i = 0; i < 13; i++)
+    {
+        vec2 color_fetch = UV_offset + color_fetches[i] * pixel_size;
+        vec3 color = texture(sampler2D(inBlurImage, inImageSampler), color_fetch).rgb;
+        color *= color_weights[i];
+        sampled_color += color;
+    }
+
+    imageStore(outBlurImage, pixel_coord, vec4(sampled_color, 1.f));
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/bloom/lensFlares.comp b/projects/sph/shaders/bloom/lensFlares.comp
new file mode 100644
index 0000000000000000000000000000000000000000..ce27d8850b709f61332d467914ddc944dc63109f
--- /dev/null
+++ b/projects/sph/shaders/bloom/lensFlares.comp
@@ -0,0 +1,109 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set=0, binding=0) uniform texture2D                          blurBuffer;
+layout(set=0, binding=1) uniform sampler                            linearSampler;
+layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D            lensBuffer;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+vec3 sampleColorChromaticAberration(vec2 _uv)
+{
+    vec2 toCenter = (vec2(0.5) - _uv);
+
+    vec3    colorScales     = vec3(-1, 0, 1);
+    float   aberrationScale = 0.1;
+    vec3 scaleFactors = colorScales * aberrationScale;
+
+    float r = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.r).r;
+    float g = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.g).g;
+    float b = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.b).b;
+    return vec3(r, g, b);
+}
+
+// _uv assumed to be flipped UV coordinates!
+vec3 ghost_vectors(vec2 _uv)
+{
+    vec2 ghost_vec = (vec2(0.5f) - _uv);
+
+    const uint c_ghost_count = 64;
+    const float c_ghost_spacing = length(ghost_vec) / c_ghost_count;
+
+    ghost_vec *= c_ghost_spacing;
+
+    vec3 ret_color = vec3(0.0f);
+
+    for (uint i = 0; i < c_ghost_count; ++i)
+    {
+        // sample scene color
+        vec2 s_uv = fract(_uv + ghost_vec * vec2(i));
+        vec3 s = sampleColorChromaticAberration(s_uv);
+
+        // tint/weight
+        float d = distance(s_uv, vec2(0.5));
+        float weight = 1.0f - smoothstep(0.0f, 0.75f, d);
+        s *= weight;
+
+        ret_color += s;
+    }
+
+    ret_color /= c_ghost_count;
+    return ret_color;
+}
+
+vec3 halo(vec2 _uv)
+{
+    const float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y);
+    const float c_radius = 0.6f;
+    const float c_halo_thickness = 0.1f;
+
+    vec2 halo_vec = vec2(0.5) - _uv;
+    //halo_vec.x /= c_aspect_ratio;
+    halo_vec = normalize(halo_vec);
+    //halo_vec.x *= c_aspect_ratio;
+
+
+    //vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0);
+    vec2 w_uv = _uv;
+    float d = distance(w_uv, vec2(0.5)); // distance to center
+
+    float distance_to_halo = abs(d - c_radius);
+
+    float halo_weight = 0.0f;
+    if(abs(d - c_radius) <= c_halo_thickness)
+    {
+        float distance_to_border = c_halo_thickness - distance_to_halo;
+        halo_weight = distance_to_border / c_halo_thickness;
+
+        //halo_weight = clamp((halo_weight / 0.4f), 0.0f, 1.0f);
+        halo_weight = pow(halo_weight, 2.0f);
+
+
+        //halo_weight = 1.0f;
+    }
+
+    return sampleColorChromaticAberration(_uv + halo_vec) * halo_weight;
+}
+
+
+
+void main()
+{
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(lensBuffer)))){
+        return;
+    }
+
+    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
+    vec2  pixel_size    = vec2(1.0f) / imageSize(lensBuffer);
+    vec2  UV            = pixel_coord.xy * pixel_size;
+
+    vec2 flipped_UV = vec2(1.0f) - UV;
+
+    vec3 color = vec3(0.0f);
+
+    color += ghost_vectors(flipped_UV);
+    color += halo(UV);
+    color  *= 0.5f;
+
+    imageStore(lensBuffer, pixel_coord, vec4(color, 0.0f));
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/bloom/upsample.comp b/projects/sph/shaders/bloom/upsample.comp
new file mode 100644
index 0000000000000000000000000000000000000000..0ddeedb5b5af9e476dc19012fed6430544006c0e
--- /dev/null
+++ b/projects/sph/shaders/bloom/upsample.comp
@@ -0,0 +1,45 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set=0, binding=0) uniform texture2D                          inUpsampleImage;
+layout(set=0, binding=1) uniform sampler                            inImageSampler;
+layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D  outUpsampleImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main()
+{
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outUpsampleImage)))){
+        return;
+    }
+
+
+    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
+    vec2  pixel_size    = vec2(1.0f) / imageSize(outUpsampleImage);
+    vec2  UV            = pixel_coord.xy * pixel_size;
+
+    const float gauss_kernel[3] = {1.f, 2.f, 1.f};
+    const float gauss_weight = 16.f;
+
+    vec3 sampled_color = vec3(0.f);
+
+    for(int i = -1; i <= 1; i++)
+    {
+        for(int j = -1; j <= 1; j++)
+        {
+            vec2 sample_location = UV + vec2(j, i) * pixel_size;
+            vec3 color = texture(sampler2D(inUpsampleImage, inImageSampler), sample_location).rgb;
+            color *= gauss_kernel[j+1];
+            color *= gauss_kernel[i+1];
+            color /= gauss_weight;
+
+            sampled_color += color;
+        }
+    }
+
+    //vec3 prev_color = imageLoad(outUpsampleImage, pixel_coord).rgb;
+    //float bloomRimStrength = 0.75f; // adjust this to change strength of bloom
+    //sampled_color = mix(prev_color, sampled_color, bloomRimStrength);
+
+    imageStore(outUpsampleImage, pixel_coord, vec4(sampled_color, 1.f));
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/flip.comp b/projects/sph/shaders/flip.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f5cd7bf3ea5ed8d04de970c816989230421c0b52
--- /dev/null
+++ b/projects/sph/shaders/flip.comp
@@ -0,0 +1,53 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+};
+
+layout(std430, binding = 1) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 0) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+    
+    outParticle[id].force = inParticle[id].force;
+    outParticle[id].density = inParticle[id].density;
+    outParticle[id].pressure = inParticle[id].pressure;
+    outParticle[id].position =  inParticle[id].position;
+    outParticle[id].velocity =  inParticle[id].velocity;
+}
diff --git a/projects/sph/shaders/force.comp b/projects/sph/shaders/force.comp
new file mode 100644
index 0000000000000000000000000000000000000000..ea9b378b48a23fd0208ab18d884dbccda5ab21f4
--- /dev/null
+++ b/projects/sph/shaders/force.comp
@@ -0,0 +1,95 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+const float PI = 3.1415926535897932384626433832795;
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 1) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 0) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+float spiky(float r)    
+{
+    return (15.f / (PI * pow(h, 6)) * pow((h-r), 3)) * int(0<=r && r<=h);
+}
+
+float grad_spiky(float r)
+{
+    return -45.f / (PI * pow(h, 6)) * pow((h-r), 2) * int(0<=r && r<=h);
+}
+
+
+
+float laplacian(float r)
+{
+    return (45.f / (PI * pow(h,6)) * (h - r)) * int(0<=r && r<=h);
+}
+
+vec3 pressureForce = vec3(0, 0, 0);
+vec3 viscosityForce = vec3(0, 0, 0); 
+vec3 externalForce = vec3(0, 0, 0);
+
+void main() {
+
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+
+    externalForce = inParticle[id].density * gravity * vec3(-gravityDir.x,gravityDir.y,gravityDir.z);
+
+    for(uint i = 0; i < int(particleCount); i++)  
+    {
+        if (id != i)
+        {
+            vec3 dir = inParticle[id].position - inParticle[i].position;
+            float dist = length(dir);
+            if(dist != 0) 
+            {
+                pressureForce += mass * -(inParticle[id].pressure + inParticle[i].pressure)/(2.f * inParticle[i].density) * grad_spiky(dist) * normalize(dir);
+                viscosityForce += mass * (inParticle[i].velocity - inParticle[id].velocity)/inParticle[i].density * laplacian(dist);
+            }
+        }
+    }
+    viscosityForce *= viscosity;
+
+    outParticle[id].force = externalForce + pressureForce + viscosityForce;
+    outParticle[id].density = inParticle[id].density;
+    outParticle[id].pressure = inParticle[id].pressure;
+    outParticle[id].position = inParticle[id].position;
+    outParticle[id].velocity = inParticle[id].velocity;
+}
diff --git a/projects/sph/shaders/particleShading.inc b/projects/sph/shaders/particleShading.inc
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1832b9ccd6ba05a585b59bdfdedd4729e80f8
--- /dev/null
+++ b/projects/sph/shaders/particleShading.inc
@@ -0,0 +1,6 @@
+float circleFactor(vec2 triangleCoordinates){
+    // percentage of distance from center to circle edge
+    float p = clamp((0.4 - length(triangleCoordinates)) / 0.4, 0, 1);
+    // remapping for nice falloff
+    return sqrt(p);
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/particle_params.inc b/projects/sph/shaders/particle_params.inc
new file mode 100644
index 0000000000000000000000000000000000000000..e35cce13be1bc84f045da276fe46666d0b852984
--- /dev/null
+++ b/projects/sph/shaders/particle_params.inc
@@ -0,0 +1,8 @@
+#define h 0.4
+#define mass 0.15
+#define gasConstant 3000
+#define offset 1800
+#define gravity -1000
+#define viscosity 1500
+#define ABSORBTION 0.9
+#define dt 0.0005
diff --git a/projects/sph/shaders/pressure.comp b/projects/sph/shaders/pressure.comp
new file mode 100644
index 0000000000000000000000000000000000000000..05b3af3afb490b427cc1297f21a82a779d4c8ecb
--- /dev/null
+++ b/projects/sph/shaders/pressure.comp
@@ -0,0 +1,72 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+const float PI = 3.1415926535897932384626433832795;
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 0) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 1) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+float poly6(float r)    
+{
+    return (315.f * pow((pow(h,2)-pow(r,2)), 3)/(64.f*PI*pow(h, 9))) * int(0<=r && r<=h);
+}
+
+float densitySum = 0.f;
+
+void main() {
+    
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+
+    for(uint i = 0; i < int(particleCount); i++)   
+    {
+        if (id != i)
+        {
+            float dist = distance(inParticle[id].position, inParticle[i].position);
+            densitySum += mass * poly6(dist);
+        }
+    }
+    outParticle[id].density = max(densitySum,0.0000001f);
+    outParticle[id].pressure = max((densitySum - offset), 0.0000001f) * gasConstant;
+    outParticle[id].position = inParticle[id].position;
+    outParticle[id].velocity = inParticle[id].velocity;
+    outParticle[id].force = inParticle[id].force;
+}
diff --git a/projects/sph/shaders/shader.vert b/projects/sph/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f5531ffa4f26d3652e8e35971c16af6dda2e3b45
--- /dev/null
+++ b/projects/sph/shaders/shader.vert
@@ -0,0 +1,49 @@
+#version 460 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 particle;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+};
+
+layout(std430, binding = 2) readonly buffer buffer_inParticle1
+{
+    Particle inParticle1[];
+};
+
+layout(std430, binding = 3) readonly buffer buffer_inParticle2
+{
+    Particle inParticle2[];
+};
+
+layout( push_constant ) uniform constants{
+    mat4 view;
+    mat4 projection;
+};
+
+layout(location = 0) out vec2 passTriangleCoordinates;
+layout(location = 1) out vec3 passVelocity;
+
+void main()
+{
+    int id = gl_InstanceIndex;
+    passVelocity = inParticle1[id].velocity;
+    
+    // particle position in view space
+    vec4 positionView = view * vec4(inParticle1[id].position, 1);
+    // by adding the triangle position in view space the mesh is always camera facing
+    positionView.xyz += particle;
+    // multiply with projection matrix for final position
+	gl_Position = projection * positionView;
+    
+    // 0.01 corresponds to vertex position size in main
+    float normalizationDivider  = 0.012;
+    passTriangleCoordinates     = particle.xy / normalizationDivider;
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/shader_water.frag b/projects/sph/shaders/shader_water.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2aee4ec692a2ada060a77389099b2c279e9c338c
--- /dev/null
+++ b/projects/sph/shaders/shader_water.frag
@@ -0,0 +1,28 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particleShading.inc"
+
+layout(location = 0) in vec2 passTriangleCoordinates;
+layout(location = 1) in vec3 passVelocity;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform uColor {
+	vec4 color;
+} Color;
+
+layout(set=0,binding=1) uniform uPosition{
+	vec2 position;
+} Position;
+
+void main()
+{
+    float p = length(passVelocity)/100.f;
+    outColor = vec3(0.f+p/3.f, 0.05f+p/2.f, 0.4f+p);
+
+    // make the triangle look like a circle
+   outColor *= circleFactor(passTriangleCoordinates);
+
+}
diff --git a/projects/sph/shaders/tonemapping.comp b/projects/sph/shaders/tonemapping.comp
new file mode 100644
index 0000000000000000000000000000000000000000..26f0232d66e3475afdd1266c0cc6288b47ed1c38
--- /dev/null
+++ b/projects/sph/shaders/tonemapping.comp
@@ -0,0 +1,19 @@
+#version 440
+
+layout(set=0, binding=0, rgba16f)   uniform image2D inImage;
+layout(set=0, binding=1, rgba8)     uniform image2D outImage;
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
+        return;
+    }
+    ivec2 uv            = ivec2(gl_GlobalInvocationID.xy);
+    vec3 linearColor    = imageLoad(inImage, uv).rgb;
+    vec3 tonemapped     = linearColor / (dot(linearColor, vec3(0.21, 0.71, 0.08)) + 1); // reinhard tonemapping
+    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
+    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/updateData.comp b/projects/sph/shaders/updateData.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3c935b232aff11388cc3b371e5524fa30486b36f
--- /dev/null
+++ b/projects/sph/shaders/updateData.comp
@@ -0,0 +1,109 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 0) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 1) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+void main() {
+
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+
+    vec3 accel = inParticle[id].force / inParticle[id].density;
+    vec3 vel_new = inParticle[id].velocity + (dt * accel);
+
+    vec3 out_force = inParticle[id].force;
+    float out_density = inParticle[id].density;
+    float out_pressure = inParticle[id].pressure;
+    
+    if (length(vel_new) > 100.f)
+    {
+        vel_new = normalize(vel_new)*50;
+        out_density = 0.01f;
+        out_pressure = 0.01f;
+        out_force = gravity * vec3(-gravityDir.x,gravityDir.y,gravityDir.z);
+    }
+
+    vec3 pos_new = inParticle[id].position + (dt * vel_new);
+
+    // Überprüfe Randbedingungen x
+    if (inParticle[id].position.x < -1.0)
+    {
+        vel_new = reflect(vel_new.xyz, vec3(1.f,0.f,0.f)) * ABSORBTION;
+        pos_new.x = -1.0 + 0.01f;
+    }
+    else if (inParticle[id].position.x > 1.0)
+    {
+        vel_new = reflect(vel_new,vec3(1.f,0.f,0.f)) * ABSORBTION;
+        pos_new.x = 1.0 - 0.01f;
+    }
+
+    // Überprüfe Randbedingungen y
+    if (inParticle[id].position.y < -1.0)
+    {
+        vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION;
+        pos_new.y = -1.0 + 0.01f;
+
+    }
+    else if (inParticle[id].position.y > 1.0)
+    {
+        vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION;
+        pos_new.y = 1.0 - 0.01f;
+    }
+
+    // Überprüfe Randbedingungen z
+    if (inParticle[id].position.z < -1.0 )
+    {
+        vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION;
+        pos_new.z = -1.0 + 0.01f;
+    }
+    else if (inParticle[id].position.z > 1.0 )
+    {
+        vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION;
+        pos_new.z = 1.0 - 0.01f;
+    }
+
+    outParticle[id].force = out_force;
+    outParticle[id].density = out_density;
+    outParticle[id].pressure = out_pressure;
+    outParticle[id].position = pos_new;
+    outParticle[id].velocity = vel_new;
+}
diff --git a/projects/sph/src/BloomAndFlares.cpp b/projects/sph/src/BloomAndFlares.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..09534815afcd8ab238b79da5c6bbceb6672b043a
--- /dev/null
+++ b/projects/sph/src/BloomAndFlares.cpp
@@ -0,0 +1,285 @@
+#include "BloomAndFlares.hpp"
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+BloomAndFlares::BloomAndFlares(
+        vkcv::Core *p_Core,
+        vk::Format colorBufferFormat,
+        uint32_t width,
+        uint32_t height) :
+
+        p_Core(p_Core),
+        m_ColorBufferFormat(colorBufferFormat),
+        m_Width(width),
+        m_Height(height),
+        m_LinearSampler(p_Core->createSampler(vkcv::SamplerFilterType::LINEAR,
+                                              vkcv::SamplerFilterType::LINEAR,
+                                              vkcv::SamplerMipmapMode::LINEAR,
+                                              vkcv::SamplerAddressMode::CLAMP_TO_EDGE)),
+        m_Blur(p_Core->createImage(colorBufferFormat, width, height, 1, true, true, false)),
+        m_LensFeatures(p_Core->createImage(colorBufferFormat, width, height, 1, false, true, false))
+{
+    vkcv::shader::GLSLCompiler compiler;
+
+    // DOWNSAMPLE
+    vkcv::ShaderProgram dsProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "shaders/bloom/downsample.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         dsProg.addShader(shaderStage, path);
+                     });
+    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
+    {
+        m_DownsampleDescSetLayouts.push_back(
+                p_Core->createDescriptorSetLayout(dsProg.getReflectedDescriptors().at(0)));
+
+		m_DownsampleDescSets.push_back(
+		        p_Core->createDescriptorSet(m_DownsampleDescSetLayouts.back()));
+    }
+    m_DownsamplePipe = p_Core->createComputePipeline({
+            dsProg, { p_Core->getDescriptorSetLayout(m_DownsampleDescSetLayouts[0]).vulkanHandle } });
+
+    // UPSAMPLE
+    vkcv::ShaderProgram usProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "shaders/bloom/upsample.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         usProg.addShader(shaderStage, path);
+                     });
+    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
+    {
+        m_UpsampleDescSetLayouts.push_back(
+                p_Core->createDescriptorSetLayout(usProg.getReflectedDescriptors().at(0)));
+        m_UpsampleDescSets.push_back(
+                p_Core->createDescriptorSet(m_UpsampleDescSetLayouts.back()));
+    }
+    m_UpsamplePipe = p_Core->createComputePipeline({
+            usProg, { p_Core->getDescriptorSetLayout(m_UpsampleDescSetLayouts[0]).vulkanHandle } });
+
+    // LENS FEATURES
+    vkcv::ShaderProgram lensProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "shaders/bloom/lensFlares.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         lensProg.addShader(shaderStage, path);
+                     });
+    m_LensFlareDescSetLayout = p_Core->createDescriptorSetLayout(lensProg.getReflectedDescriptors().at(0));
+    m_LensFlareDescSet = p_Core->createDescriptorSet(m_LensFlareDescSetLayout);
+    m_LensFlarePipe = p_Core->createComputePipeline({
+            lensProg, { p_Core->getDescriptorSetLayout(m_LensFlareDescSetLayout).vulkanHandle } });
+
+    // COMPOSITE
+    vkcv::ShaderProgram compProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "shaders/bloom/composite.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         compProg.addShader(shaderStage, path);
+                     });
+    m_CompositeDescSetLayout = p_Core->createDescriptorSetLayout(compProg.getReflectedDescriptors().at(0));
+    m_CompositeDescSet = p_Core->createDescriptorSet(m_CompositeDescSetLayout);
+    m_CompositePipe = p_Core->createComputePipeline({
+            compProg, { p_Core->getDescriptorSetLayout(m_CompositeDescSetLayout).vulkanHandle } });
+}
+
+void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream,
+                                        const vkcv::ImageHandle &colorAttachment)
+{
+    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
+    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
+    // blur dispatch
+    uint32_t initialDispatchCount[3] = {
+            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
+            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
+            1
+    };
+
+    // downsample dispatch of original color attachment
+    p_Core->prepareImageForSampling(cmdStream, colorAttachment);
+    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
+
+    vkcv::DescriptorWrites initialDownsampleWrites;
+    initialDownsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorAttachment)};
+    initialDownsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
+    initialDownsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), 0) };
+    p_Core->writeDescriptorSet(m_DownsampleDescSets[0], initialDownsampleWrites);
+
+    p_Core->recordComputeDispatchToCmdStream(
+            cmdStream,
+            m_DownsamplePipe,
+            initialDispatchCount,
+            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)},
+            vkcv::PushConstants(0));
+
+    // downsample dispatches of blur buffer's mip maps
+    float mipDispatchCountX = dispatchCountX;
+    float mipDispatchCountY = dispatchCountY;
+    for(uint32_t mipLevel = 1; mipLevel < std::min((uint32_t)m_DownsampleDescSets.size(), m_Blur.getMipCount()); mipLevel++)
+    {
+        // mip descriptor writes
+        vkcv::DescriptorWrites mipDescriptorWrites;
+        mipDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel - 1, true)};
+        mipDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
+        mipDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel) };
+        p_Core->writeDescriptorSet(m_DownsampleDescSets[mipLevel], mipDescriptorWrites);
+
+        // mip dispatch calculation
+        mipDispatchCountX  /= 2.0f;
+        mipDispatchCountY /= 2.0f;
+
+        uint32_t mipDispatchCount[3] = {
+                static_cast<uint32_t>(glm::ceil(mipDispatchCountX)),
+                static_cast<uint32_t>(glm::ceil(mipDispatchCountY)),
+                1
+        };
+
+        if(mipDispatchCount[0] == 0)
+            mipDispatchCount[0] = 1;
+        if(mipDispatchCount[1] == 0)
+            mipDispatchCount[1] = 1;
+
+        // mip blur dispatch
+        p_Core->recordComputeDispatchToCmdStream(
+                cmdStream,
+                m_DownsamplePipe,
+                mipDispatchCount,
+                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)},
+                vkcv::PushConstants(0));
+
+        // image barrier between mips
+        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
+    }
+}
+
+void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream)
+{
+    // upsample dispatch
+    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
+
+    const uint32_t upsampleMipLevels = std::min(
+    		static_cast<uint32_t>(m_UpsampleDescSets.size() - 1),
+    		static_cast<uint32_t>(3)
+	);
+
+    // upsample dispatch for each mip map
+    for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--)
+    {
+        // mip descriptor writes
+        vkcv::DescriptorWrites mipUpsampleWrites;
+        mipUpsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel, true)};
+        mipUpsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
+        mipUpsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel - 1) };
+        p_Core->writeDescriptorSet(m_UpsampleDescSets[mipLevel], mipUpsampleWrites);
+
+        auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f);
+
+        auto upsampleDispatchX  = static_cast<float>(m_Width) / mipDivisor;
+        auto upsampleDispatchY = static_cast<float>(m_Height) / mipDivisor;
+        upsampleDispatchX /= 8.0f;
+        upsampleDispatchY /= 8.0f;
+
+        const uint32_t upsampleDispatchCount[3] = {
+                static_cast<uint32_t>(glm::ceil(upsampleDispatchX)),
+                static_cast<uint32_t>(glm::ceil(upsampleDispatchY)),
+                1
+        };
+
+        p_Core->recordComputeDispatchToCmdStream(
+                cmdStream,
+                m_UpsamplePipe,
+                upsampleDispatchCount,
+                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)},
+                vkcv::PushConstants(0)
+        );
+        // image barrier between mips
+        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
+    }
+}
+
+void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream)
+{
+    // lens feature generation descriptor writes
+    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
+    p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle());
+
+    vkcv::DescriptorWrites lensFeatureWrites;
+    lensFeatureWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), 0)};
+    lensFeatureWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
+    lensFeatureWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_LensFeatures.getHandle(), 0)};
+    p_Core->writeDescriptorSet(m_LensFlareDescSet, lensFeatureWrites);
+
+    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
+    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
+    // lens feature generation dispatch
+    uint32_t lensFeatureDispatchCount[3] = {
+            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
+            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
+            1
+    };
+    p_Core->recordComputeDispatchToCmdStream(
+            cmdStream,
+            m_LensFlarePipe,
+            lensFeatureDispatchCount,
+            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)},
+            vkcv::PushConstants(0));
+}
+
+void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream,
+                                       const vkcv::ImageHandle &colorAttachment)
+{
+    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
+    p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle());
+    p_Core->prepareImageForStorage(cmdStream, colorAttachment);
+
+    // bloom composite descriptor write
+    vkcv::DescriptorWrites compositeWrites;
+    compositeWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle()),
+                                          vkcv::SampledImageDescriptorWrite(1, m_LensFeatures.getHandle())};
+    compositeWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(2, m_LinearSampler)};
+    compositeWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(3, colorAttachment)};
+    p_Core->writeDescriptorSet(m_CompositeDescSet, compositeWrites);
+
+    float dispatchCountX = static_cast<float>(m_Width)  / 8.0f;
+    float dispatchCountY = static_cast<float>(m_Height) / 8.0f;
+
+    uint32_t compositeDispatchCount[3] = {
+            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
+            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
+            1
+    };
+
+    // bloom composite dispatch
+    p_Core->recordComputeDispatchToCmdStream(
+            cmdStream,
+            m_CompositePipe,
+            compositeDispatchCount,
+            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)},
+            vkcv::PushConstants(0));
+}
+
+void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream,
+                                       const vkcv::ImageHandle &colorAttachment)
+{
+    execDownsamplePipe(cmdStream, colorAttachment);
+    execUpsamplePipe(cmdStream);
+    execLensFeaturePipe(cmdStream);
+    execCompositePipe(cmdStream, colorAttachment);
+}
+
+void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height)
+{
+    if ((width == m_Width) && (height == m_Height)) {
+        return;
+    }
+    
+    m_Width  = width;
+    m_Height = height;
+
+    p_Core->getContext().getDevice().waitIdle();
+    m_Blur = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, true, true, false);
+    m_LensFeatures = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, false, true, false);
+}
+
+
diff --git a/projects/sph/src/BloomAndFlares.hpp b/projects/sph/src/BloomAndFlares.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1644d38e9c98c7a0bf74d48b173f0e627214f1e1
--- /dev/null
+++ b/projects/sph/src/BloomAndFlares.hpp
@@ -0,0 +1,51 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <glm/glm.hpp>
+
+class BloomAndFlares{
+public:
+    BloomAndFlares(vkcv::Core *p_Core,
+                   vk::Format colorBufferFormat,
+                   uint32_t width,
+                   uint32_t height);
+
+    void execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
+
+    void updateImageDimensions(uint32_t width, uint32_t height);
+
+private:
+    vkcv::Core *p_Core;
+
+    vk::Format m_ColorBufferFormat;
+    uint32_t m_Width;
+    uint32_t m_Height;
+
+    vkcv::SamplerHandle m_LinearSampler;
+    vkcv::Image m_Blur;
+    vkcv::Image m_LensFeatures;
+
+
+    vkcv::ComputePipelineHandle                     m_DownsamplePipe;
+    std::vector<vkcv::DescriptorSetLayoutHandle>    m_DownsampleDescSetLayouts;
+    std::vector<vkcv::DescriptorSetHandle>          m_DownsampleDescSets; // per mip desc set
+
+    vkcv::ComputePipelineHandle                     m_UpsamplePipe;
+    std::vector<vkcv::DescriptorSetLayoutHandle>    m_UpsampleDescSetLayouts;
+    std::vector<vkcv::DescriptorSetHandle>          m_UpsampleDescSets;   // per mip desc set
+
+    vkcv::ComputePipelineHandle                     m_LensFlarePipe;
+    vkcv::DescriptorSetLayoutHandle                 m_LensFlareDescSetLayout;
+    vkcv::DescriptorSetHandle                       m_LensFlareDescSet;
+
+    vkcv::ComputePipelineHandle                     m_CompositePipe;
+    vkcv::DescriptorSetLayoutHandle                 m_CompositeDescSetLayout;
+    vkcv::DescriptorSetHandle                       m_CompositeDescSet;
+
+    void execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
+    void execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream);
+    void execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream);
+    void execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
+};
+
+
+
diff --git a/projects/sph/src/Particle.cpp b/projects/sph/src/Particle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..329236989b7cab502e7a4e1bb5aa27869bed53cb
--- /dev/null
+++ b/projects/sph/src/Particle.cpp
@@ -0,0 +1,39 @@
+
+#include "Particle.hpp"
+
+Particle::Particle(glm::vec3 position, glm::vec3 velocity)
+: m_position(position),
+  m_velocity(velocity)
+{
+    m_density = 0.f;
+    m_force = glm::vec3(0.f);
+    m_pressure = 0.f;
+}
+
+const glm::vec3& Particle::getPosition()const{
+    return m_position;
+}
+
+void Particle::setPosition( const glm::vec3 pos ){
+    m_position = pos;
+}
+
+const glm::vec3& Particle::getVelocity()const{
+    return m_velocity;
+}
+
+void Particle::setVelocity( const glm::vec3 vel ){
+    m_velocity = vel;
+}
+
+const float& Particle::getDensity()const {
+    return m_density;
+}
+
+const glm::vec3& Particle::getForce()const {
+    return m_force;
+}
+
+const float& Particle::getPressure()const {
+    return m_pressure;
+}
\ No newline at end of file
diff --git a/projects/sph/src/Particle.hpp b/projects/sph/src/Particle.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6c4ab50b74cc4544976318c23e36f4b91989ee66
--- /dev/null
+++ b/projects/sph/src/Particle.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+class Particle {
+
+public:
+    Particle(glm::vec3 position, glm::vec3 velocity);
+
+    const glm::vec3& getPosition()const;
+
+    void setPosition( const glm::vec3 pos );
+
+    const glm::vec3& getVelocity()const;
+
+    void setVelocity( const glm::vec3 vel );
+
+    const float& getDensity()const;
+
+    const glm::vec3& getForce()const;
+
+    const float& getPressure()const;
+
+
+
+private:
+    // all properties of the Particle
+    glm::vec3 m_position;
+    float m_padding1;
+    glm::vec3 m_velocity;
+    float m_density;
+    glm::vec3 m_force;
+    float m_pressure;
+};
diff --git a/projects/sph/src/PipelineInit.cpp b/projects/sph/src/PipelineInit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6cf941fa0d8f8716b7d05daf9b6fb618b0fa7d85
--- /dev/null
+++ b/projects/sph/src/PipelineInit.cpp
@@ -0,0 +1,27 @@
+#include "PipelineInit.hpp"
+
+vkcv::DescriptorSetHandle PipelineInit::ComputePipelineInit(vkcv::Core *pCore, vkcv::ShaderStage shaderStage, std::filesystem::path includePath,
+                                vkcv::ComputePipelineHandle &pipeline) {
+    vkcv::ShaderProgram shaderProgram;
+    vkcv::shader::GLSLCompiler compiler;
+    compiler.compile(shaderStage, includePath,
+                     [&](vkcv::ShaderStage shaderStage1, const std::filesystem::path& path1) {shaderProgram.addShader(shaderStage1, path1);
+    });
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = pCore->createDescriptorSetLayout(
+            shaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle descriptorSet = pCore->createDescriptorSet(descriptorSetLayout);
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = shaderProgram.getVertexAttachments();
+
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+    }
+    const vkcv::VertexLayout layout(bindings);
+
+    pipeline = pCore->createComputePipeline({
+            shaderProgram,
+            { pCore->getDescriptorSetLayout(descriptorSetLayout).vulkanHandle } });
+
+    return  descriptorSet;
+}
\ No newline at end of file
diff --git a/projects/sph/src/PipelineInit.hpp b/projects/sph/src/PipelineInit.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e628af0eef9c0558719b405790246946d8720d47
--- /dev/null
+++ b/projects/sph/src/PipelineInit.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <fstream>
+
+class PipelineInit{
+public:
+    static vkcv::DescriptorSetHandle ComputePipelineInit(vkcv::Core *pCore,
+                                                         vkcv::ShaderStage shaderStage,
+                                                         std::filesystem::path includePath,
+                                                         vkcv::ComputePipelineHandle& pipeline);
+};
diff --git a/projects/sph/src/main.cpp b/projects/sph/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..255fb0e73f068ff9e5e8ce892897a1325631b6e1
--- /dev/null
+++ b/projects/sph/src/main.cpp
@@ -0,0 +1,409 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <random>
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_access.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <time.h>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include "BloomAndFlares.hpp"
+#include "PipelineInit.hpp"
+#include "Particle.hpp"
+
+int main(int argc, const char **argv) {
+    const char *applicationName = "SPH";
+
+    vkcv::Core core = vkcv::Core::create(
+        applicationName,
+        VK_MAKE_VERSION(0, 0, 1),
+        { vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+        { VK_KHR_SWAPCHAIN_EXTENSION_NAME }
+    );
+
+    vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 1920, 1080, false);
+    vkcv::Window& window = core.getWindow(windowHandle);
+
+    vkcv::camera::CameraManager cameraManager(window);
+
+    auto particleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3,
+                                                           vkcv::BufferMemoryType::DEVICE_LOCAL);
+    uint16_t indices[3] = {0, 1, 2};
+    particleIndexBuffer.fill(&indices[0], sizeof(indices));
+
+    vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat;
+    // an example attachment for passes that output to the window
+    const vkcv::AttachmentDescription present_color_attachment(
+            vkcv::AttachmentOperation::STORE,
+            vkcv::AttachmentOperation::CLEAR,
+            colorFormat);
+
+
+    vkcv::PassConfig particlePassDefinition({present_color_attachment});
+    vkcv::PassHandle particlePass = core.createPass(particlePassDefinition);
+
+    vkcv::PassConfig computePassDefinition({});
+    vkcv::PassHandle computePass = core.createPass(computePassDefinition);
+
+    //rotation
+    float rotationx = 0;
+    float rotationy = 0;
+
+    // params  
+    float param_h = 0.20;
+    float param_mass = 0.03;
+    float param_gasConstant = 3500;
+    float param_offset = 200;
+    float param_gravity = -5000;
+    float param_viscosity = 10;
+    float param_ABSORBTION = 0.5;
+    float param_dt = 0.0005;
+
+    if (!particlePass || !computePass)
+    {
+        std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+	vkcv::shader::GLSLCompiler compiler;
+
+// comp shader 1
+    vkcv::ComputePipelineHandle computePipeline1;
+    vkcv::DescriptorSetHandle computeDescriptorSet1 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                          "shaders/pressure.comp", computePipeline1);
+// comp shader 2
+    vkcv::ComputePipelineHandle computePipeline2;
+    vkcv::DescriptorSetHandle computeDescriptorSet2 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                          "shaders/force.comp", computePipeline2);
+
+//comp shader 3
+    vkcv::ComputePipelineHandle computePipeline3;
+    vkcv::DescriptorSetHandle computeDescriptorSet3 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                           "shaders/updateData.comp", computePipeline3);
+
+//comp shader 4
+    vkcv::ComputePipelineHandle computePipeline4;
+    vkcv::DescriptorSetHandle computeDescriptorSet4 = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                            "shaders/flip.comp", computePipeline4);
+
+// shader
+    vkcv::ShaderProgram particleShaderProgram{};
+    compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+    compiler.compile(vkcv::ShaderStage::FRAGMENT, "shaders/shader_water.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(
+            particleShaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+
+    vkcv::Buffer<glm::vec3> vertexBuffer = core.createBuffer<glm::vec3>(
+            vkcv::BufferType::VERTEX,
+            3
+    );
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = particleShaderProgram.getVertexAttachments();
+
+    const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+            vkcv::VertexBufferBinding(0, vertexBuffer.getVulkanHandle())};
+
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::VertexBinding(i, {vertexAttachments[i]}));
+    }
+
+    const vkcv::VertexLayout particleLayout(bindings);
+
+    vkcv::GraphicsPipelineConfig particlePipelineDefinition{
+            particleShaderProgram,
+            UINT32_MAX,
+            UINT32_MAX,
+            particlePass,
+            {particleLayout},
+            {core.getDescriptorSetLayout(descriptorSetLayout).vulkanHandle},
+            true};
+    particlePipelineDefinition.m_blendMode = vkcv::BlendMode::Additive;
+
+    const std::vector<glm::vec3> vertices = {glm::vec3(-0.012, 0.012, 0),
+                                             glm::vec3(0.012, 0.012, 0),
+                                             glm::vec3(0, -0.012, 0)};
+
+    vertexBuffer.fill(vertices);
+
+    vkcv::GraphicsPipelineHandle particlePipeline = core.createGraphicsPipeline(particlePipelineDefinition);
+
+    vkcv::Buffer<glm::vec4> color = core.createBuffer<glm::vec4>(
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    vkcv::Buffer<glm::vec2> position = core.createBuffer<glm::vec2>(
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    int numberParticles = 20000;
+    std::vector<Particle> particles;
+    for (int i = 0; i < numberParticles; i++) {
+        const float lo = 0.6;
+        const float hi = 0.9;
+        const float vlo = 0;
+        const float vhi = 70;
+        float x = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float y = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float z = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float vx = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        float vy = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        float vz = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        glm::vec3 pos = glm::vec3(x,y,z);
+        glm::vec3 vel = glm::vec3(vx,vy,vz);
+        //glm::vec3 vel = glm::vec3(0.0,0.0,0.0);
+        particles.push_back(Particle(pos, vel));
+    }
+
+    vkcv::Buffer<Particle> particleBuffer1 = core.createBuffer<Particle>(
+            vkcv::BufferType::STORAGE,
+            numberParticles * sizeof(glm::vec4) * 3
+
+    );
+
+    vkcv::Buffer<Particle> particleBuffer2 = core.createBuffer<Particle>(
+        vkcv::BufferType::STORAGE,
+        numberParticles * sizeof(glm::vec4) * 3
+    );
+
+    particleBuffer1.fill(particles);
+	particleBuffer2.fill(particles);
+
+    vkcv::DescriptorWrites setWrites;
+    setWrites.uniformBufferWrites = {vkcv::BufferDescriptorWrite(0,color.getHandle()),
+                                     vkcv::BufferDescriptorWrite(1,position.getHandle())};
+    setWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(2,particleBuffer1.getHandle()),
+									  vkcv::BufferDescriptorWrite(3,particleBuffer2.getHandle())};
+    core.writeDescriptorSet(descriptorSet, setWrites);
+
+    vkcv::DescriptorWrites computeWrites;
+    computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,particleBuffer1.getHandle()),
+										  vkcv::BufferDescriptorWrite(1,particleBuffer2.getHandle())};
+    
+    core.writeDescriptorSet(computeDescriptorSet1, computeWrites);
+	core.writeDescriptorSet(computeDescriptorSet2, computeWrites);
+    core.writeDescriptorSet(computeDescriptorSet3, computeWrites);
+	core.writeDescriptorSet(computeDescriptorSet4, computeWrites);
+
+    if (!particlePipeline || !computePipeline1 || !computePipeline2 || !computePipeline3 || !computePipeline4)
+    {
+        std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+    const vkcv::Mesh renderMesh({vertexBufferBindings}, particleIndexBuffer.getVulkanHandle(),
+                                particleIndexBuffer.getCount());
+    vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+
+    auto pos = glm::vec2(0.f);
+    auto spawnPosition = glm::vec3(0.f);
+
+    std::vector<vkcv::DrawcallInfo> drawcalls;
+    drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}, numberParticles));
+
+    auto start = std::chrono::system_clock::now();
+
+    glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
+    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+    cameraManager.getCamera(camIndex0).setNearFar(0.1, 30);
+    cameraManager.getCamera(camIndex1).setNearFar(0.1, 30);
+
+    cameraManager.setActiveCamera(1);
+
+    cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2.5));
+    cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+    cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, 0.0f));
+
+	auto swapchainExtent = core.getSwapchain(window.getSwapchainHandle()).getExtent();
+	
+    vkcv::ImageHandle colorBuffer = core.createImage(
+			colorFormat,
+			swapchainExtent.width,
+			swapchainExtent.height,
+			1, false, true, true
+	).getHandle();
+    BloomAndFlares bloomAndFlares(&core, colorFormat, swapchainExtent.width, swapchainExtent.height);
+
+    //tone mapping shader & pipeline
+    vkcv::ComputePipelineHandle tonemappingPipe;
+    vkcv::DescriptorSetHandle tonemappingDescriptor = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                                        "shaders/tonemapping.comp", tonemappingPipe);
+
+
+    while (vkcv::Window::hasOpenWindow()) {
+        vkcv::Window::pollEvents();
+
+        uint32_t swapchainWidth, swapchainHeight;
+        if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
+            continue;
+        }
+
+        color.fill(&colorData);
+        position.fill(&pos);
+
+        auto end = std::chrono::system_clock::now();
+        float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() );
+        start = end;
+
+        cameraManager.update(deltatime);
+
+        // split view and projection to allow for easy billboarding in shader
+        struct {
+			glm::mat4 view;
+			glm::mat4 projection;
+        } renderingMatrices;
+
+        glm::vec3 gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationx), glm::vec3(0.f,0.f,1.f)) * glm::vec4(0.f,1.f,0.f,0.f);
+        gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationy), glm::vec3(0.f,1.f,0.f)) * glm::vec4(gravityDir,0.f);
+
+        renderingMatrices.view = cameraManager.getActiveCamera().getView();
+        renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationx), glm::vec3(0.f, 0.f, 1.f));
+        renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationy), glm::vec3(0.f, 1.f, 0.f));
+        renderingMatrices.projection = cameraManager.getActiveCamera().getProjection();
+
+        // keybindings rotation
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_LEFT) == GLFW_PRESS)
+            rotationx += deltatime * 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_RIGHT) == GLFW_PRESS)
+            rotationx -= deltatime * 50;
+        
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_UP) == GLFW_PRESS)
+            rotationy += deltatime * 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_DOWN) == GLFW_PRESS)
+            rotationy -= deltatime * 50;
+
+        // keybindings params
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_T) == GLFW_PRESS)
+            param_h += deltatime * 0.2;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_G) == GLFW_PRESS)
+            param_h -= deltatime * 0.2;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_Y) == GLFW_PRESS)
+            param_mass += deltatime * 0.2;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_H) == GLFW_PRESS)
+            param_mass -= deltatime * 0.2;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_U) == GLFW_PRESS)
+            param_gasConstant += deltatime * 1500.0;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_J) == GLFW_PRESS)
+            param_gasConstant -= deltatime * 1500.0;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_I) == GLFW_PRESS)
+            param_offset += deltatime * 400.0;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_K) == GLFW_PRESS)
+            param_offset -= deltatime * 400.0;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_O) == GLFW_PRESS)
+            param_viscosity = 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_L) == GLFW_PRESS)
+            param_viscosity = 1200;
+        
+
+        auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+        glm::vec4 pushData[3] = {
+            glm::vec4(param_h,param_mass,param_gasConstant,param_offset),
+            glm::vec4(param_gravity,param_viscosity,param_ABSORBTION,param_dt),
+            glm::vec4(gravityDir.x,gravityDir.y,gravityDir.z,(float)numberParticles)
+        };
+
+        std::cout << "h: " << param_h << " | mass: " << param_mass << " | gasConstant: " << param_gasConstant << " | offset: " << param_offset << " | viscosity: " << param_viscosity << std::endl;
+
+        vkcv::PushConstants pushConstantsCompute (sizeof(pushData));
+        pushConstantsCompute.appendDrawcall(pushData);
+
+        uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil(numberParticles/256.f)),1,1};
+        
+        core.recordComputeDispatchToCmdStream(cmdStream,
+                                              computePipeline1,
+                                              computeDispatchCount,
+                                              {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet1).vulkanHandle)},
+											  pushConstantsCompute);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+		core.recordComputeDispatchToCmdStream(cmdStream,
+											  computePipeline2,
+											  computeDispatchCount,
+											  {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet2).vulkanHandle)},
+											  pushConstantsCompute);
+
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+        core.recordComputeDispatchToCmdStream(cmdStream,
+                                              computePipeline3,
+                                              computeDispatchCount,
+                                              { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet3).vulkanHandle) },
+                                              pushConstantsCompute);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+        core.recordComputeDispatchToCmdStream(cmdStream,
+                                              computePipeline4,
+                                              computeDispatchCount,
+                                              { vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet4).vulkanHandle) },
+                                              pushConstantsCompute);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+
+        // bloomAndFlares & tonemapping
+        vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices));
+        pushConstantsDraw.appendDrawcall(renderingMatrices);
+        
+        core.recordDrawcallsToCmdStream(
+                cmdStream,
+                particlePass,
+                particlePipeline,
+				pushConstantsDraw,
+                {drawcalls},
+                { colorBuffer },
+                windowHandle);
+
+        bloomAndFlares.execWholePipeline(cmdStream, colorBuffer);
+
+        core.prepareImageForStorage(cmdStream, colorBuffer);
+        core.prepareImageForStorage(cmdStream, swapchainInput);
+
+        vkcv::DescriptorWrites tonemappingDescriptorWrites;
+        tonemappingDescriptorWrites.storageImageWrites = {
+            vkcv::StorageImageDescriptorWrite(0, colorBuffer),
+            vkcv::StorageImageDescriptorWrite(1, swapchainInput)
+        };
+        core.writeDescriptorSet(tonemappingDescriptor, tonemappingDescriptorWrites);
+
+        uint32_t tonemappingDispatchCount[3];
+        tonemappingDispatchCount[0] = std::ceil(swapchainExtent.width / 8.f);
+        tonemappingDispatchCount[1] = std::ceil(swapchainExtent.height / 8.f);
+        tonemappingDispatchCount[2] = 1;
+
+        core.recordComputeDispatchToCmdStream(
+            cmdStream, 
+            tonemappingPipe, 
+            tonemappingDispatchCount, 
+            {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptor).vulkanHandle) },
+            vkcv::PushConstants(0));
+
+        core.prepareSwapchainImageForPresent(cmdStream);
+        core.submitCommandStream(cmdStream);
+        core.endFrame(windowHandle);
+    }
+
+    return 0;
+}
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index 99e6df6f2cf605e7fe4081b6aeb02950c9155318..8c8238f25fb159b7cc665013e9fc3de9b2fda15a 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -79,7 +79,7 @@ namespace vkcv
         }
 
         //create the descriptor set's layout from the binding data gathered above
-        vk::DescriptorSetLayout vulkanHandle = nullptr;
+        vk::DescriptorSetLayout vulkanHandle;
         vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindingsVector);
         auto result = m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &vulkanHandle);
         if (result != vk::Result::eSuccess) {
@@ -96,7 +96,7 @@ namespace vkcv
     {
         //create and allocate the set based on the layout provided
         DescriptorSetLayout setLayout = m_DescriptorSetLayouts[setLayoutHandle.getId()];
-        vk::DescriptorSet vulkanHandle = nullptr;
+        vk::DescriptorSet vulkanHandle;
         vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle);
         auto result = m_Device.allocateDescriptorSets(&allocInfo, &vulkanHandle);
         if(result != vk::Result::eSuccess)