From bb6821daa84991d967ed20c939a698b12fa91ae5 Mon Sep 17 00:00:00 2001
From: Artur Wasmut <awasmut@uni-koblenz.de>
Date: Fri, 25 Jun 2021 19:32:40 +0200
Subject: [PATCH] create separate class for lens flares and bloom.

---
 include/vkcv/Image.hpp                        |   6 +-
 projects/bloom/CMakeLists.txt                 |   4 +
 projects/bloom/resources/shaders/comp.spv     | Bin 0 -> 6192 bytes
 .../bloom/resources/shaders/composite.comp    |  38 +++
 .../resources/shaders/compositeBloom.comp     |  29 --
 .../shaders/{blur.comp => downsample.comp}    |   1 -
 .../bloom/resources/shaders/lensFlares.comp   | 109 +++++++
 projects/bloom/src/BloomAndFlares.cpp         | 271 ++++++++++++++++++
 projects/bloom/src/BloomAndFlares.hpp         |  47 +++
 projects/bloom/src/main.cpp                   | 193 +------------
 10 files changed, 487 insertions(+), 211 deletions(-)
 create mode 100644 projects/bloom/resources/shaders/comp.spv
 create mode 100644 projects/bloom/resources/shaders/composite.comp
 delete mode 100644 projects/bloom/resources/shaders/compositeBloom.comp
 rename projects/bloom/resources/shaders/{blur.comp => downsample.comp} (99%)
 create mode 100644 projects/bloom/resources/shaders/lensFlares.comp
 create mode 100644 projects/bloom/src/BloomAndFlares.cpp
 create mode 100644 projects/bloom/src/BloomAndFlares.hpp

diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index 9e1f9708..eac96975 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -42,8 +42,10 @@ namespace vkcv {
 		void generateMipChainImmediate();
 		void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream);
 	private:
-		ImageManager* const m_manager;
-		const ImageHandle   m_handle;
+	    // TODO: const qualifier removed, very hacky!!!
+	    //  Else you cannot recreate an image. Pls fix.
+		ImageManager*       m_manager;
+		ImageHandle   m_handle;
 
 		Image(ImageManager* manager, const ImageHandle& handle);
 		
diff --git a/projects/bloom/CMakeLists.txt b/projects/bloom/CMakeLists.txt
index cdb30af0..8171938e 100644
--- a/projects/bloom/CMakeLists.txt
+++ b/projects/bloom/CMakeLists.txt
@@ -11,6 +11,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
 # adding source files to the project
 add_executable(bloom src/main.cpp)
 
+target_sources(bloom PRIVATE
+		src/BloomAndFlares.cpp
+		src/BloomAndFlares.hpp)
+
 # this should fix the execution path to load local files from the project (for MSVC)
 if(MSVC)
 	set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
diff --git a/projects/bloom/resources/shaders/comp.spv b/projects/bloom/resources/shaders/comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..85c7e74cfc0a89917bf6dd1a7ec449368274c1d3
GIT binary patch
literal 6192
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

literal 0
HcmV?d00001

diff --git a/projects/bloom/resources/shaders/composite.comp b/projects/bloom/resources/shaders/composite.comp
new file mode 100644
index 00000000..190bed06
--- /dev/null
+++ b/projects/bloom/resources/shaders/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.25f;
+    float lens_weight  = 0.25f;
+    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/bloom/resources/shaders/compositeBloom.comp b/projects/bloom/resources/shaders/compositeBloom.comp
deleted file mode 100644
index 5435a240..00000000
--- a/projects/bloom/resources/shaders/compositeBloom.comp
+++ /dev/null
@@ -1,29 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          blurImage;
-layout(set=0, binding=1) uniform sampler                            linearSampler;
-layout(set=0, binding=2, 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 main_color = imageLoad(colorBuffer, pixel_coord).rgb;
-
-    composite_color.rgb = mix(main_color, blur_color, 0.25f);
-
-    imageStore(colorBuffer, pixel_coord, composite_color);
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/blur.comp b/projects/bloom/resources/shaders/downsample.comp
similarity index 99%
rename from projects/bloom/resources/shaders/blur.comp
rename to projects/bloom/resources/shaders/downsample.comp
index 51834627..2ab00c7c 100644
--- a/projects/bloom/resources/shaders/blur.comp
+++ b/projects/bloom/resources/shaders/downsample.comp
@@ -14,7 +14,6 @@ void main()
         return;
     }
 
-
     ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
     vec2  pixel_size    = vec2(1.0f) / imageSize(outBlurImage);
     vec2  UV            = pixel_coord.xy * pixel_size;
diff --git a/projects/bloom/resources/shaders/lensFlares.comp b/projects/bloom/resources/shaders/lensFlares.comp
new file mode 100644
index 00000000..ce27d885
--- /dev/null
+++ b/projects/bloom/resources/shaders/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/bloom/src/BloomAndFlares.cpp b/projects/bloom/src/BloomAndFlares.cpp
new file mode 100644
index 00000000..e4ca9357
--- /dev/null
+++ b/projects/bloom/src/BloomAndFlares.cpp
@@ -0,0 +1,271 @@
+#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,
+                     "resources/shaders/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_DownsampleDescSets.push_back(
+                p_Core->createDescriptorSet(dsProg.getReflectedDescriptors()[0]));
+    }
+    m_DownsamplePipe = p_Core->createComputePipeline(
+            dsProg, { p_Core->getDescriptorSet(m_DownsampleDescSets[0]).layout });
+
+    // UPSAMPLE
+    vkcv::ShaderProgram usProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "resources/shaders/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_UpsampleDescSets.push_back(
+                p_Core->createDescriptorSet(usProg.getReflectedDescriptors()[0]));
+    }
+    m_UpsamplePipe = p_Core->createComputePipeline(
+            usProg, { p_Core->getDescriptorSet(m_UpsampleDescSets[0]).layout });
+
+    // LENS FEATURES
+    vkcv::ShaderProgram lensProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "resources/shaders/lensFlares.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         lensProg.addShader(shaderStage, path);
+                     });
+    m_LensFlareDescSet = p_Core->createDescriptorSet(lensProg.getReflectedDescriptors()[0]);
+    m_LensFlarePipe = p_Core->createComputePipeline(
+            lensProg, { p_Core->getDescriptorSet(m_LensFlareDescSet).layout });
+
+    // COMPOSITE
+    vkcv::ShaderProgram compProg;
+    compiler.compile(vkcv::ShaderStage::COMPUTE,
+                     "resources/shaders/composite.comp",
+                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
+                     {
+                         compProg.addShader(shaderStage, path);
+                     });
+    m_CompositeDescSet = p_Core->createDescriptorSet(compProg.getReflectedDescriptors()[0]);
+    m_CompositePipe = p_Core->createComputePipeline(
+            compProg, { p_Core->getDescriptorSet(m_CompositeDescSet).layout });
+}
+
+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::PushConstantData(nullptr, 0));
+
+    // downsample dispatches of blur buffer's mip maps
+    float mipDispatchCountX = dispatchCountX;
+    float mipDispatchCountY = dispatchCountY;
+    for(uint32_t mipLevel = 1; mipLevel < 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::PushConstantData(nullptr, 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());
+
+    uint32_t upsampleMipLevels = std::min(m_Blur.getMipCount(), static_cast<uint32_t>(5));
+
+    // 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::PushConstantData(nullptr, 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::PushConstantData(nullptr, 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::PushConstantData(nullptr, 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)
+{
+    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/bloom/src/BloomAndFlares.hpp b/projects/bloom/src/BloomAndFlares.hpp
new file mode 100644
index 00000000..756b1ca1
--- /dev/null
+++ b/projects/bloom/src/BloomAndFlares.hpp
@@ -0,0 +1,47 @@
+#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::PipelineHandle                     m_DownsamplePipe;
+    std::vector<vkcv::DescriptorSetHandle>   m_DownsampleDescSets; // per mip desc set
+
+    vkcv::PipelineHandle                     m_UpsamplePipe;
+    std::vector<vkcv::DescriptorSetHandle>   m_UpsampleDescSets;   // per mip desc set
+
+    vkcv::PipelineHandle                     m_LensFlarePipe;
+    vkcv::DescriptorSetHandle                m_LensFlareDescSet;
+
+    vkcv::PipelineHandle                     m_CompositePipe;
+    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/bloom/src/main.cpp b/projects/bloom/src/main.cpp
index d93cbc28..278f1b8c 100644
--- a/projects/bloom/src/main.cpp
+++ b/projects/bloom/src/main.cpp
@@ -6,6 +6,7 @@
 #include <vkcv/asset/asset_loader.hpp>
 #include <vkcv/shader/GLSLCompiler.hpp>
 #include <vkcv/Logger.hpp>
+#include "BloomAndFlares.hpp"
 #include <glm/glm.hpp>
 
 int main(int argc, const char** argv) {
@@ -224,11 +225,6 @@ int main(int argc, const char** argv) {
 
 	vkcv::ImageHandle depthBuffer       = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
 	vkcv::ImageHandle colorBuffer       = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
-	vkcv::Image blurBuffer              = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, true, true, false);
-	vkcv::SamplerHandle linearSampler   = core.createSampler(vkcv::SamplerFilterType::LINEAR,
-                                                             vkcv::SamplerFilterType::LINEAR,
-                                                             vkcv::SamplerMipmapMode::LINEAR,
-                                                             vkcv::SamplerAddressMode::CLAMP_TO_EDGE);
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
@@ -271,51 +267,8 @@ int main(int argc, const char** argv) {
 	vkcv::PipelineHandle gammaCorrectionPipeline = core.createComputePipeline(gammaCorrectionProgram,
 		{ core.getDescriptorSet(gammaCorrectionDescriptorSet).layout });
 
-	// blur compute shader
-	vkcv::ShaderProgram blurProgram;
-	compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/blur.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         blurProgram.addShader(shaderStage, path);
-                     });
-	// create descriptor sets for each mip level
-	std::vector<vkcv::DescriptorSetHandle> blurDescriptorSets;
-	for(uint32_t mipLevel = 0; mipLevel < blurBuffer.getMipCount(); mipLevel++)
-    {
-	    blurDescriptorSets.push_back(core.createDescriptorSet(blurProgram.getReflectedDescriptors()[0]));
-    }
-	vkcv::PipelineHandle blurPipeline = core.createComputePipeline(blurProgram,
-                                                                   { core.getDescriptorSet(blurDescriptorSets[0]).layout });
-
-	// upsample compute shader
-    vkcv::ShaderProgram upsampleProgram;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/upsample.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         upsampleProgram.addShader(shaderStage, path);
-                     });
-    // create descriptor sets for each mip level
-    std::vector<vkcv::DescriptorSetHandle> upsampleDescriptorSets;
-    for(uint32_t mipLevel = 0; mipLevel < blurBuffer.getMipCount(); mipLevel++)
-    {
-        upsampleDescriptorSets.push_back(core.createDescriptorSet(upsampleProgram.getReflectedDescriptors()[0]));
-    }
-    vkcv::PipelineHandle upsamplePipeline = core.createComputePipeline(upsampleProgram,
-                                                                   { core.getDescriptorSet(upsampleDescriptorSets[0]).layout });
-
-    // bloom composite shader
-    vkcv::ShaderProgram compositeBloomProgram;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/compositeBloom.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         compositeBloomProgram.addShader(shaderStage, path);
-                     });
-    vkcv::DescriptorSetHandle compositeBloomDescriptorSet = core.createDescriptorSet(compositeBloomProgram.getReflectedDescriptors()[0]);
-    vkcv::PipelineHandle compositeBloomPipeline = core.createComputePipeline(compositeBloomProgram,
-                                                                   { core.getDescriptorSet(compositeBloomDescriptorSet).layout });
+    BloomAndFlares baf(&core, colorBufferFormat, windowWidth, windowHeight);
+
 
 	// model matrices per mesh
 	std::vector<glm::mat4> modelMatrices;
@@ -360,7 +313,8 @@ int main(int argc, const char** argv) {
 		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
 			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
 			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, true, true).getHandle();
-            //blurBuffer  = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, true, false).getHandle();
+
+			baf.updateImageDimensions(swapchainWidth, swapchainHeight);
 
 			windowWidth = swapchainWidth;
 			windowHeight = swapchainHeight;
@@ -369,21 +323,6 @@ int main(int argc, const char** argv) {
 		auto end = std::chrono::system_clock::now();
 		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
 
-		// update descriptor sets which use swapchain image
-        // composite bloom
-        vkcv::DescriptorWrites compositeBloomDescriptorWrites;
-        compositeBloomDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, blurBuffer.getHandle())};
-        compositeBloomDescriptorWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, linearSampler)};
-        compositeBloomDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, colorBuffer, 0)};
-        core.writeDescriptorSet(compositeBloomDescriptorSet, compositeBloomDescriptorWrites);
-
-        // gamma correction
-        vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
-        gammaCorrectionDescriptorWrites.storageImageWrites = {
-                vkcv::StorageImageDescriptorWrite(0, colorBuffer),
-                vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
-        core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites);
-
 		start = end;
 		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
 
@@ -446,111 +385,6 @@ int main(int argc, const char** argv) {
 			drawcalls,
 			renderTargets);
 
-
-
-        auto windowWidthByLocalGroup  = static_cast<float>(windowWidth)  / 8.0f;
-        auto windowHeightByLocalGroup = static_cast<float>(windowHeight) / 8.0f;
-
-		uint32_t initialBlurDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(windowWidthByLocalGroup)),
-                static_cast<uint32_t>(glm::ceil(windowHeightByLocalGroup)),
-                1
-		};
-		// blur dispatch
-        core.prepareImageForSampling(cmdStream, colorBuffer);
-        core.prepareImageForStorage(cmdStream, blurBuffer.getHandle());
-        // blur dispatch of original color attachment
-        vkcv::DescriptorWrites firstBlurDescriptorWrites;
-        firstBlurDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorBuffer)};
-        firstBlurDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, linearSampler)};
-        firstBlurDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, blurBuffer.getHandle(), 0) };
-        core.writeDescriptorSet(blurDescriptorSets[0], firstBlurDescriptorWrites);
-        core.recordComputeDispatchToCmdStream(
-                cmdStream,
-                blurPipeline,
-                initialBlurDispatchCount,
-                {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(blurDescriptorSets[0]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0));
-
-        // blur dispatches of blur buffer's mip maps
-		for(uint32_t mipLevel = 1; mipLevel < blurBuffer.getMipCount(); mipLevel++)
-        {
-            // mip descriptor writes
-            vkcv::DescriptorWrites mipBlurDescriptorWrites;
-            mipBlurDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, blurBuffer.getHandle(), mipLevel - 1, true)};
-            mipBlurDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, linearSampler)};
-            mipBlurDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, blurBuffer.getHandle(), mipLevel) };
-            core.writeDescriptorSet(blurDescriptorSets[mipLevel], mipBlurDescriptorWrites);
-
-            // mip dispatch calculation
-            windowWidthByLocalGroup /= 2.0f;
-            windowHeightByLocalGroup /= 2.0f;
-
-            uint32_t mipBlurDispatchCount[3] = {
-                    static_cast<uint32_t>(glm::ceil(windowWidthByLocalGroup)),
-                    static_cast<uint32_t>(glm::ceil(windowHeightByLocalGroup)),
-                    1
-            };
-
-            if(mipBlurDispatchCount[0] == 0)
-                mipBlurDispatchCount[0] = 1;
-            if(mipBlurDispatchCount[1] == 0)
-                mipBlurDispatchCount[1] = 1;
-
-            // mip blur dispatch
-            core.recordComputeDispatchToCmdStream(
-                    cmdStream,
-                    blurPipeline,
-                    mipBlurDispatchCount,
-                    {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(blurDescriptorSets[mipLevel]).vulkanHandle)},
-                    vkcv::PushConstantData(nullptr, 0));
-
-            // image barrier between mips
-            core.recordImageMemoryBarrier(cmdStream, blurBuffer.getHandle());
-        }
-
-		// upsample dispatch
-
-		uint32_t upsampleMipLevels = std::min(blurBuffer.getMipCount(), static_cast<uint32_t>(5));
-
-		// upsample dispatch for each mip map
-		for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--)
-        {
-            // mip descriptor writes
-            vkcv::DescriptorWrites mipUpsampleDescriptorWrites;
-            mipUpsampleDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, blurBuffer.getHandle(), mipLevel, true)};
-            mipUpsampleDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, linearSampler)};
-            mipUpsampleDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, blurBuffer.getHandle(), mipLevel - 1) };
-            core.writeDescriptorSet(upsampleDescriptorSets[mipLevel], mipUpsampleDescriptorWrites);
-
-            auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f);
-
-            auto upsampleDispatchWidth  = static_cast<float>(windowWidth) / mipDivisor;
-            auto upsampleDispatchHeight = static_cast<float>(windowHeight) / mipDivisor;
-
-            upsampleDispatchWidth /= 8.0f;
-            upsampleDispatchHeight /= 8.0f;
-
-            const uint32_t upsampleDispatchCount[3] = {
-                    static_cast<uint32_t>(glm::ceil(upsampleDispatchWidth)),
-                    static_cast<uint32_t>(glm::ceil(upsampleDispatchHeight)),
-                    1
-            };
-
-            core.recordComputeDispatchToCmdStream(
-                    cmdStream,
-                    upsamplePipeline,
-                    upsampleDispatchCount,
-                    {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(upsampleDescriptorSets[mipLevel]).vulkanHandle)},
-                    vkcv::PushConstantData(nullptr, 0)
-                    );
-            // image barrier between mips
-            core.recordImageMemoryBarrier(cmdStream, blurBuffer.getHandle());
-        }
-
-		core.prepareImageForStorage(cmdStream, colorBuffer);
-		core.prepareImageForSampling(cmdStream, blurBuffer.getHandle());
-
         const uint32_t gammaCorrectionLocalGroupSize = 8;
         const uint32_t gammaCorrectionDispatchCount[3] = {
                 static_cast<uint32_t>(glm::ceil(static_cast<float>(windowWidth) / static_cast<float>(gammaCorrectionLocalGroupSize))),
@@ -558,17 +392,18 @@ int main(int argc, const char** argv) {
                 1
         };
 
-		// bloom composite dispatch
-        core.recordComputeDispatchToCmdStream(cmdStream,
-                                              compositeBloomPipeline,
-                                              gammaCorrectionDispatchCount,
-                                              {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(compositeBloomDescriptorSet).vulkanHandle)},
-                                              vkcv::PushConstantData(nullptr, 0));
+        baf.execWholePipeline(cmdStream, colorBuffer);
 
         core.prepareImageForStorage(cmdStream, swapchainInput);
+        // gamma correction descriptor write
+        vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
+        gammaCorrectionDescriptorWrites.storageImageWrites = {
+                vkcv::StorageImageDescriptorWrite(0, colorBuffer),
+                vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
+        core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites);
 
-		// gamma correction dispatch
-		core.recordComputeDispatchToCmdStream(
+        // gamma correction dispatch
+        core.recordComputeDispatchToCmdStream(
 			cmdStream, 
 			gammaCorrectionPipeline, 
 			gammaCorrectionDispatchCount,
-- 
GitLab