diff --git a/.gitignore b/.gitignore
index d2bf98a016f588760241f9dc7f90f6197c458404..7ee4ff1903e902c4715c6e2b0c3e784ed5755aaf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-<<<<<<< HEAD
-=======
-
->>>>>>> develop
 # IDE specific files
 .project
 .cproject
@@ -19,3 +15,6 @@ cmake-build-release/
 *.exe
 *.ilk
 *.pdb
+
+# GUI configuration files
+imgui.ini
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 318f3e931e557421a5e9275735174cdee7947453..33b70018e368ecc3ad019ea33e57485814eb233a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,7 +14,7 @@ build_ubuntu_gcc:
       - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
-    - ubuntu-gcc
+    - ubuntu-gcc-cached
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
   timeout: 10m
@@ -37,11 +37,11 @@ build_win10_msvc:
       - $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
-    - win10-msvc
+    - win10-msvc-cached
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
   timeout: 10m
-  retry: 1
+  retry: 0
   script:
     - cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\'
     - .\Launch-VsDevShell.ps1
diff --git a/.gitmodules b/.gitmodules
index 983b753744e8767da0ec3c959c32a3766ee346f6..e0aaf2d17c340f98ae875f7e0f1238bfe04f7e5d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -15,4 +15,10 @@
 	url = https://github.com/nothings/stb.git
 [submodule "modules/camera/lib/glm"]
 	path = modules/camera/lib/glm
-	url = https://github.com/g-truc/glm.git
\ No newline at end of file
+	url = https://github.com/g-truc/glm.git
+[submodule "modules/shader_compiler/lib/glslang"]
+	path = modules/shader_compiler/lib/glslang
+	url = https://github.com/KhronosGroup/glslang.git
+[submodule "modules/gui/lib/imgui"]
+	path = modules/gui/lib/imgui
+	url = https://github.com/ocornut/imgui.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a7f978f81313ecc3a657dd670d2a16db3cd4e8d..2ae078a428a8e5e640ed8dc7bcc2f4e58e159c6b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,24 +41,24 @@ if (vkcv_build_debug)
 	endif()
 endif()
 
+# configure everything to use the required dependencies
+include(${vkcv_config}/Libraries.cmake)
+
+# set the compile definitions aka preprocessor variables
+add_compile_definitions(${vkcv_definitions})
+
 # add modules as targets
 add_subdirectory(modules)
 
 # add source files for compilation
 include(${vkcv_config}/Sources.cmake)
 
-# configure everything to use the required dependencies
-include(${vkcv_config}/Libraries.cmake)
-
 message("-- Libraries: [ ${vkcv_libraries} ]")
 message("-- Flags: [ ${vkcv_flags} ]")
 
 # set the compiler flags for the framework
 set(CMAKE_CXX_FLAGS ${vkcv_flags})
 
-# set the compile definitions aka preprocessor variables
-add_compile_definitions(${vkcv_definitions})
-
 # create VkCV framework as static library using all source files
 add_library(vkcv STATIC ${vkcv_sources})
 
diff --git a/Doxyfile b/Doxyfile
index be7d96f6cce1ed0ec753dafeaccb6314b9f6e04f..a657ede81c43b3cae1fc5c17f071aa7dc65add04 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -865,7 +865,8 @@ WARN_LOGFILE           =
 # Note: If this tag is empty the current directory is searched.
 
 INPUT                  = src \
-                         include
+                         include \
+                         modules
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -953,7 +954,7 @@ RECURSIVE              = YES
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                =
+EXCLUDE                = lib
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
@@ -969,7 +970,7 @@ EXCLUDE_SYMLINKS       = NO
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories for example use the pattern */test/*
 
-EXCLUDE_PATTERNS       =
+EXCLUDE_PATTERNS       = */lib/*
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
 # (namespaces, classes, functions, etc.) that should be excluded from the
diff --git a/config/Libraries.cmake b/config/Libraries.cmake
index e04aa3575a34632eb75c929bf4640305cd93e298..ec014f84c820abf4988b070d5b733be08c377319 100644
--- a/config/Libraries.cmake
+++ b/config/Libraries.cmake
@@ -10,6 +10,8 @@ if(NOT WIN32)
 	list(APPEND vkcv_flags -fopenmp)
 endif()
 
+list(APPEND vkcv_definitions _USE_MATH_DEFINES)
+
 # some formatted printing
 set(vkcv_config_msg " - Library: ")
 
diff --git a/config/Sources.cmake b/config/Sources.cmake
index 8fc0aa9a2605a629596e26d5eeb0772164e6ec7a..4397e4978eb022d267571d185a1f122d053a5ea1 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -29,15 +29,18 @@ set(vkcv_sources
 
 		${vkcv_source}/vkcv/ImageManager.hpp
 		${vkcv_source}/vkcv/ImageManager.cpp
+		
+		${vkcv_include}/vkcv/Logger.hpp
 
-		${vkcv_include}/vkcv/SwapChain.hpp
-		${vkcv_source}/vkcv/SwapChain.cpp
-
+		${vkcv_include}/vkcv/Swapchain.hpp
+		${vkcv_source}/vkcv/Swapchain.cpp
+		
+		${vkcv_include}/vkcv/ShaderStage.hpp
+		
 		${vkcv_include}/vkcv/ShaderProgram.hpp
 		${vkcv_source}/vkcv/ShaderProgram.cpp
 
 		${vkcv_include}/vkcv/PipelineConfig.hpp
-		${vkcv_source}/vkcv/PipelineConfig.cpp
 
 		${vkcv_source}/vkcv/PipelineManager.hpp
 		${vkcv_source}/vkcv/PipelineManager.cpp
@@ -50,10 +53,7 @@ set(vkcv_sources
         
         ${vkcv_include}/vkcv/QueueManager.hpp
         ${vkcv_source}/vkcv/QueueManager.cpp
-        
-        ${vkcv_source}/vkcv/Surface.hpp
-        ${vkcv_source}/vkcv/Surface.cpp
-        
+
         ${vkcv_source}/vkcv/ImageLayoutTransitions.hpp
         ${vkcv_source}/vkcv/ImageLayoutTransitions.cpp
 
@@ -72,4 +72,12 @@ set(vkcv_sources
 		${vkcv_source}/vkcv/SamplerManager.cpp
         
         ${vkcv_include}/vkcv/DescriptorWrites.hpp
+        
+        ${vkcv_include}/vkcv/DrawcallRecording.hpp
+        ${vkcv_source}/vkcv/DrawcallRecording.cpp
+        
+        ${vkcv_include}/vkcv/CommandStreamManager.hpp
+        ${vkcv_source}/vkcv/CommandStreamManager.cpp
+        
+        ${vkcv_include}/vkcv/CommandRecordingFunctionTypes.hpp
 )
diff --git a/config/lib/SPIRV_Cross.cmake b/config/lib/SPIRV_Cross.cmake
index 751ee883c47e0eab081a13e5805ced6f2daa7e30..2e705d7d5a006e3851d14d22a57fd667c61c79f5 100644
--- a/config/lib/SPIRV_Cross.cmake
+++ b/config/lib/SPIRV_Cross.cmake
@@ -6,9 +6,20 @@ if (spirv-cross_FOUND)
     message(${vkcv_config_msg} " SPIRV Cross    - " ${SPIRV_CROSS_VERSION})
 else()
     if (EXISTS "${vkcv_lib_path}/SPIRV-Cross")
+        set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_SHARED OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_STATIC ON CACHE INTERNAL "")
         set(SPIRV_CROSS_CLI OFF CACHE INTERNAL "")
         set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE INTERNAL "")
+        
+        set(SPIRV_CROSS_ENABLE_GLSL ON CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_HLSL OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_MSL OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_CPP ON CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_REFLECT OFF CACHE INTERNAL "")
         set(SPIRV_CROSS_ENABLE_C_API OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_UTIL OFF CACHE INTERNAL "")
+        
         set(SPIRV_CROSS_SKIP_INSTALL ON CACHE INTERNAL "")
     
         add_subdirectory(${vkcv_lib}/SPIRV-Cross)
diff --git a/include/vkcv/Buffer.hpp b/include/vkcv/Buffer.hpp
index d327067ce409e845bcac5e4c9f56e7de59df89c2..ae935ba9501d4d7776cad7e3ba190a2dd02e5e38 100644
--- a/include/vkcv/Buffer.hpp
+++ b/include/vkcv/Buffer.hpp
@@ -37,6 +37,11 @@ namespace vkcv {
 		size_t getSize() const {
 			return m_count * sizeof(T);
 		}
+
+        [[nodiscard]]
+        const vk::Buffer getVulkanHandle() const {
+            return m_manager->getBuffer(m_handle);
+        }
 		
 		void fill(const T* data, size_t count = 0, size_t offset = 0) {
 			 m_manager->fillBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp
index 322873b348dab7c45d446b5053d61379dfde0b6b..9eb80d70862a79a01593e6fe4c3aabe98d253ac8 100644
--- a/include/vkcv/BufferManager.hpp
+++ b/include/vkcv/BufferManager.hpp
@@ -44,6 +44,14 @@ namespace vkcv
 		
 		void init();
 		
+		/**
+		 * Destroys and deallocates buffer represented by a given
+		 * buffer handle id.
+		 *
+		 * @param id Buffer handle id
+		 */
+		void destroyBufferById(uint64_t id);
+		
 	public:
 		~BufferManager() noexcept;
 		
@@ -123,15 +131,10 @@ namespace vkcv
 		 * @param handle Buffer handle
 		 */
 		void unmapBuffer(const BufferHandle& handle);
-	
-		/**
-		 * Destroys and deallocates buffer represented by a given
-		 * buffer handle.
-		 *
-		 * @param handle Buffer handle
-		 */
-		void destroyBuffer(const BufferHandle& handle);
 		
+		void recordBufferMemoryBarrier(
+			const BufferHandle& handle,
+			vk::CommandBuffer cmdBuffer);
 	};
 	
 }
diff --git a/include/vkcv/CommandRecordingFunctionTypes.hpp b/include/vkcv/CommandRecordingFunctionTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c236fb2c717afd2a3bafc4b3a22708cdac942ffe
--- /dev/null
+++ b/include/vkcv/CommandRecordingFunctionTypes.hpp
@@ -0,0 +1,8 @@
+#pragma once
+#include "vkcv/Event.hpp"
+#include <vulkan/vulkan.hpp>
+
+namespace vkcv {
+	typedef typename event_function<const vk::CommandBuffer&>::type RecordCommandFunction;
+	typedef typename event_function<>::type FinishCommandFunction;
+}
\ No newline at end of file
diff --git a/include/vkcv/CommandStreamManager.hpp b/include/vkcv/CommandStreamManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4af2127ccf6271f1076e3dde05304b8f9c556139
--- /dev/null
+++ b/include/vkcv/CommandStreamManager.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include <vulkan/vulkan.hpp>
+#include <vector>
+#include "vkcv/Event.hpp"
+#include "vkcv/Handles.hpp"
+#include "vkcv/CommandRecordingFunctionTypes.hpp"
+
+namespace vkcv {
+	
+	class Core;
+
+	class CommandStreamManager
+	{
+		friend class Core;
+	private:
+		struct CommandStream {
+			inline CommandStream(vk::CommandBuffer cmdBuffer, vk::Queue queue, vk::CommandPool cmdPool) 
+				: cmdBuffer(cmdBuffer), cmdPool(cmdPool), queue(queue) {};
+			vk::CommandBuffer                   cmdBuffer;
+			vk::CommandPool                     cmdPool;
+			vk::Queue                           queue;
+			std::vector<FinishCommandFunction>  callbacks;
+		};
+
+		Core* m_core;
+		std::vector<CommandStream> m_commandStreams;
+
+		CommandStreamManager() noexcept;
+
+		void init(Core* core);
+
+	public:
+		~CommandStreamManager() noexcept;
+
+		CommandStreamManager(CommandStreamManager&& other) = delete;
+		CommandStreamManager(const CommandStreamManager& other) = delete;
+
+		CommandStreamManager& operator=(CommandStreamManager&& other) = delete;
+		CommandStreamManager& operator=(const CommandStreamManager& other) = delete;
+
+		CommandStreamHandle createCommandStream(
+			const vk::Queue queue,
+			vk::CommandPool cmdPool);
+
+		void recordCommandsToStream(const CommandStreamHandle handle, const RecordCommandFunction record);
+		void addFinishCallbackToStream(const CommandStreamHandle handle, const FinishCommandFunction finish);
+		void submitCommandStreamSynchronous(
+			const CommandStreamHandle   handle,
+			std::vector<vk::Semaphore>  &waitSemaphores,
+			std::vector<vk::Semaphore>  &signalSemaphores);
+
+		vk::CommandBuffer getStreamCommandBuffer(const CommandStreamHandle handle);
+	};
+
+}
\ No newline at end of file
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index cf75b8e754513da774ec1841c8c4cc700b99ee2d..493534f77a0f930cbd0d292a504300f51ebc0f76 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -8,7 +8,7 @@
 #include <vulkan/vulkan.hpp>
 
 #include "vkcv/Context.hpp"
-#include "vkcv/SwapChain.hpp"
+#include "vkcv/Swapchain.hpp"
 #include "vkcv/Window.hpp"
 #include "vkcv/PassConfig.hpp"
 #include "vkcv/Handles.hpp"
@@ -21,13 +21,12 @@
 #include "vkcv/DescriptorConfig.hpp"
 #include "Sampler.hpp"
 #include "DescriptorWrites.hpp"
+#include "Event.hpp"
+#include "DrawcallRecording.hpp"
+#include "CommandRecordingFunctionTypes.hpp"
 
 namespace vkcv
 {
-	struct VertexBufferBinding {
-		vk::DeviceSize	offset;
-		BufferHandle	buffer;
-	};
 
     // forward declarations
     class PassManager;
@@ -36,15 +35,13 @@ namespace vkcv
     class BufferManager;
     class SamplerManager;
     class ImageManager;
+	class CommandStreamManager;
 
 	struct SubmitInfo {
 		QueueType queueType;
 		std::vector<vk::Semaphore> waitSemaphores;
 		std::vector<vk::Semaphore> signalSemaphores;
 	};
-	
-	typedef std::function<void(const vk::CommandBuffer& cmdBuffer)> RecordCommandFunction;
-	typedef std::function<void(void)> FinishCommandFunction;
 
     class Core final
     {
@@ -55,38 +52,33 @@ namespace vkcv
          *
          * @param context encapsulates various Vulkan objects
          */
-        Core(Context &&context, Window &window, SwapChain swapChain,  std::vector<vk::ImageView> imageViews,
+        Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> imageViews,
 			const CommandResources& commandResources, const SyncResources& syncResources) noexcept;
         // explicit destruction of default constructor
         Core() = delete;
 
 		Result acquireSwapchainImage();
-		void destroyTemporaryFramebuffers();
 
         Context m_Context;
 
-        SwapChain					m_swapchain;
-        std::vector<vk::ImageView>	m_swapchainImageViews;
-        const Window&				m_window;
+        Swapchain                       m_swapchain;
+        Window&                   		m_window;
 
-        std::unique_ptr<PassManager>		m_PassManager;
-        std::unique_ptr<PipelineManager>	m_PipelineManager;
-        std::unique_ptr<DescriptorManager>	m_DescriptorManager;
-        std::unique_ptr<BufferManager>		m_BufferManager;
-        std::unique_ptr<SamplerManager>		m_SamplerManager;
-        std::unique_ptr<ImageManager>		m_ImageManager;
+        std::unique_ptr<PassManager>            m_PassManager;
+        std::unique_ptr<PipelineManager>        m_PipelineManager;
+        std::unique_ptr<DescriptorManager>      m_DescriptorManager;
+        std::unique_ptr<BufferManager>          m_BufferManager;
+        std::unique_ptr<SamplerManager>         m_SamplerManager;
+        std::unique_ptr<ImageManager>           m_ImageManager;
+        std::unique_ptr<CommandStreamManager>   m_CommandStreamManager;
 
-		CommandResources				m_CommandResources;
-		SyncResources					m_SyncResources;
-		uint32_t						m_currentSwapchainImageIndex;
-		std::vector<vk::Framebuffer>	m_TemporaryFramebuffers;
+		CommandResources    m_CommandResources;
+		SyncResources       m_SyncResources;
+		uint32_t            m_currentSwapchainImageIndex;
 
-        /**
-         * recreates the swapchain
-         * @param[in] width new window width
-         * @param[in] height new window hight
-         */
-        static void recreateSwapchain(int width, int height);
+		event_handle<int,int> e_resizeHandle;
+
+        static std::vector<vk::ImageView> createSwapchainImageViews( Context &context, Swapchain& swapChain);
 
     public:
         /**
@@ -126,6 +118,9 @@ namespace vkcv
 
         [[nodiscard]]
         const Context &getContext() const;
+        
+        [[nodiscard]]
+        const Swapchain& getSwapchain() const;
 
         /**
              * Creates a #Core with given @p applicationName and @p applicationVersion for your application.
@@ -160,6 +155,19 @@ namespace vkcv
         [[nodiscard]]
         PipelineHandle createGraphicsPipeline(const PipelineConfig &config);
 
+        /**
+         * Creates a basic vulkan compute pipeline using @p shader program and returns it using the @p handle.
+         * Fixed Functions for pipeline are set with standard values.
+         *
+         * @param shader program that hold the compiles compute shader
+         * @param handle a handle to return the created vulkan handle
+         * @return True if pipeline creation was successful, False if not
+         */
+        [[nodiscard]]
+        PipelineHandle createComputePipeline(
+            const ShaderProgram &config, 
+            const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);
+
         /**
          * Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle.
          * Fixed Functions for pipeline are set with standard values.
@@ -206,46 +214,55 @@ namespace vkcv
          * @return Image-Object
          */
         [[nodiscard]]
-        Image createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1);
+        Image createImage(
+			vk::Format  format,
+			uint32_t    width,
+			uint32_t    height,
+			uint32_t    depth = 1,
+			bool        createMipChain = false,
+			bool        supportStorage = false,
+			bool        supportColorAttachment = false);
+
+        [[nodiscard]]
+        const uint32_t getImageWidth(ImageHandle imageHandle);
+        [[nodiscard]]
+        const uint32_t getImageHeight(ImageHandle imageHandle);
 
         /** TODO:
          *   @param setDescriptions
          *   @return
          */
         [[nodiscard]]
-        ResourcesHandle createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets);
-		void writeResourceDescription(ResourcesHandle handle, size_t setIndex, const DescriptorWrites& writes);
+        DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &bindings);
+		void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites& writes);
 
-		vk::DescriptorSetLayout getDescritorSetLayout(ResourcesHandle handle, size_t setIndex);
+		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
 
 		/**
 		 * @brief start recording command buffers and increment frame index
 		*/
-		void beginFrame();
+		bool beginFrame(uint32_t& width, uint32_t& height);
 
-		/**
-		 * @brief render a beautiful triangle
-		*/
-		void renderMesh(
-			const PassHandle						renderpassHandle, 
-			const PipelineHandle					pipelineHandle,
-			const int								width, 
-			const int								height, 
-			const size_t							pushConstantSize, 
-			const void*								pushConstantData, 
-			const std::vector<VertexBufferBinding>	&vertexBufferBindings, 
-			const BufferHandle						indexBuffer, 
-			const size_t							indexCount,
-			const vkcv::ResourcesHandle				resourceHandle,
-			const size_t							resourceDescriptorSetIndex);
+		void recordDrawcallsToCmdStream(
+            const CommandStreamHandle       cmdStreamHandle,
+			const PassHandle                renderpassHandle, 
+			const PipelineHandle            pipelineHandle,
+			const PushConstantData          &pushConstantData,
+			const std::vector<DrawcallInfo> &drawcalls,
+			const std::vector<ImageHandle>  &renderTargets);
+
+		void recordComputeDispatchToCmdStream(
+			CommandStreamHandle cmdStream,
+			PipelineHandle computePipeline,
+			const uint32_t dispatchCount[3],
+			const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+			const PushConstantData& pushConstantData);
 
 		/**
 		 * @brief end recording and present image
 		*/
 		void endFrame();
 
-		vk::Format getSwapchainImageFormat();
-
 		/**
 		 * Submit a command buffer to any queue of selected type. The recording can be customized by a
 		 * custom record-command-function. If the command submission has finished, an optional finish-function
@@ -255,6 +272,26 @@ namespace vkcv
 		 * @param record Record-command-function
 		 * @param finish Finish-command-function or nullptr
 		 */
-		void submitCommands(const SubmitInfo &submitInfo, const RecordCommandFunction& record, const FinishCommandFunction& finish);
+		void recordAndSubmitCommandsImmediate(
+			const SubmitInfo            &submitInfo, 
+			const RecordCommandFunction &record, 
+			const FinishCommandFunction &finish);
+
+		CommandStreamHandle createCommandStream(QueueType queueType);
+
+		void recordCommandsToStream(
+			const CommandStreamHandle   cmdStreamHandle,
+			const RecordCommandFunction &record,
+			const FinishCommandFunction &finish);
+
+		void submitCommandStream(const CommandStreamHandle handle);
+		void prepareSwapchainImageForPresent(const CommandStreamHandle handle);
+		void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer);
+		
+		vk::ImageView getSwapchainImageView() const;
+		
     };
 }
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
index 161273db1ec3bd0be290ecdd1042d12d181d303e..c6d0dfd1bc60988afb8b6a9326a8d50d8a4ea32e 100644
--- a/include/vkcv/DescriptorConfig.hpp
+++ b/include/vkcv/DescriptorConfig.hpp
@@ -1,8 +1,18 @@
 #pragma once
-#include <vkcv/ShaderProgram.hpp>
+
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/Handles.hpp"
+#include "vkcv/ShaderStage.hpp"
 
 namespace vkcv
 {
+    struct DescriptorSet
+    {
+        vk::DescriptorSet       vulkanHandle;
+        vk::DescriptorSetLayout layout;
+    };
+
     /*
     * All the types of descriptors (resources) that can be retrieved by the shaders
     */
@@ -24,27 +34,16 @@ namespace vkcv
     */
     struct DescriptorBinding
     {
-        DescriptorBinding() = delete;
         DescriptorBinding(
+            uint32_t bindingID,
             DescriptorType descriptorType,
             uint32_t descriptorCount,
             ShaderStage shaderStage
         ) noexcept;
-
+        
+        uint32_t bindingID;
         DescriptorType descriptorType;
         uint32_t descriptorCount;
         ShaderStage shaderStage;
     };
-
-    /*
-    * One descriptor set struct that contains all the necessary information for the actual creation.
-    * @param[in] a number of bindings that were created beforehand
-    * @param[in] the number of (identical) sets that should be created from the attached bindings
-    */
-    struct DescriptorSetConfig
-    {
-        explicit DescriptorSetConfig(std::vector<DescriptorBinding> bindings) noexcept;
-
-        std::vector<DescriptorBinding> bindings;
-    };
 }
diff --git a/include/vkcv/DescriptorWrites.hpp b/include/vkcv/DescriptorWrites.hpp
index d67e8e3233e184b207d109e652adeca43407d7e0..f28a6c91e189b13413ffefec0f05e5a0a358ee26 100644
--- a/include/vkcv/DescriptorWrites.hpp
+++ b/include/vkcv/DescriptorWrites.hpp
@@ -4,15 +4,20 @@
 
 namespace vkcv {
 	struct SampledImageDescriptorWrite {
-		inline SampledImageDescriptorWrite(uint32_t binding, ImageHandle image) : binding(binding), image(image) {};
+		inline SampledImageDescriptorWrite(uint32_t binding, ImageHandle image, uint32_t mipLevel = 0, bool useGeneralLayout = false)
+		    : binding(binding), image(image), mipLevel(mipLevel), useGeneralLayout(useGeneralLayout) {};
 		uint32_t	binding;
 		ImageHandle	image;
+		uint32_t    mipLevel;
+		bool        useGeneralLayout;
 	};
 
 	struct StorageImageDescriptorWrite {
-		inline StorageImageDescriptorWrite(uint32_t binding, ImageHandle image) : binding(binding), image(image) {};
+		inline StorageImageDescriptorWrite(uint32_t binding, ImageHandle image, uint32_t mipLevel = 0) 
+			: binding(binding), image(image), mipLevel(mipLevel) {};
 		uint32_t	binding;
 		ImageHandle	image;
+		uint32_t	mipLevel;
 	};
 
 	struct UniformBufferDescriptorWrite {
@@ -36,8 +41,8 @@ namespace vkcv {
 	struct DescriptorWrites {
 		std::vector<SampledImageDescriptorWrite>		sampledImageWrites;
 		std::vector<StorageImageDescriptorWrite>		storageImageWrites;
-		std::vector<UniformBufferDescriptorWrite>	uniformBufferWrites;
-		std::vector<StorageBufferDescriptorWrite>	storageBufferWrites;
-		std::vector<SamplerDescriptorWrite>			samplerWrites;
+		std::vector<UniformBufferDescriptorWrite>	    uniformBufferWrites;
+		std::vector<StorageBufferDescriptorWrite>	    storageBufferWrites;
+		std::vector<SamplerDescriptorWrite>			    samplerWrites;
 	};
 }
\ No newline at end of file
diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0929ad038fb95ec1573e7c76e5ce13adb84ab760
--- /dev/null
+++ b/include/vkcv/DrawcallRecording.hpp
@@ -0,0 +1,54 @@
+#pragma once
+#include <vulkan/vulkan.hpp>
+#include <vkcv/Handles.hpp>
+#include <vkcv/DescriptorConfig.hpp>
+
+namespace vkcv {
+    struct VertexBufferBinding {
+        inline VertexBufferBinding(vk::DeviceSize offset, vk::Buffer buffer) noexcept
+            : offset(offset), buffer(buffer) {}
+
+        vk::DeviceSize  offset;
+        vk::Buffer      buffer;
+    };
+
+    struct DescriptorSetUsage {
+        inline DescriptorSetUsage(uint32_t setLocation, vk::DescriptorSet vulkanHandle) noexcept
+            : setLocation(setLocation), vulkanHandle(vulkanHandle) {}
+
+        const uint32_t          setLocation;
+        const vk::DescriptorSet vulkanHandle;
+    };
+
+    struct Mesh {
+        inline Mesh(std::vector<VertexBufferBinding> vertexBufferBindings, vk::Buffer indexBuffer, size_t indexCount) noexcept
+            : vertexBufferBindings(vertexBufferBindings), indexBuffer(indexBuffer), indexCount(indexCount){}
+
+        std::vector<VertexBufferBinding>    vertexBufferBindings;
+        vk::Buffer                          indexBuffer;
+        size_t                              indexCount;
+    };
+
+    struct PushConstantData {
+        inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall) {}
+
+        void* data;
+        size_t  sizePerDrawcall;
+    };
+
+    struct DrawcallInfo {
+        inline DrawcallInfo(const Mesh& mesh, const std::vector<DescriptorSetUsage>& descriptorSets)
+            : mesh(mesh), descriptorSets(descriptorSets) {}
+
+        Mesh                            mesh;
+        std::vector<DescriptorSetUsage> descriptorSets;
+    };
+
+    void recordDrawcall(
+        const DrawcallInfo      &drawcall,
+        vk::CommandBuffer       cmdBuffer,
+        vk::PipelineLayout      pipelineLayout,
+        const PushConstantData  &pushConstantData,
+        const size_t            drawcallIndex);
+
+}
\ No newline at end of file
diff --git a/include/vkcv/Event.hpp b/include/vkcv/Event.hpp
index 0836e836e84ff7dfc4931a7cedd65497bf9a89cf..da5cbc72fbb3eee3a71a35c1da6fe32dff06b057 100644
--- a/include/vkcv/Event.hpp
+++ b/include/vkcv/Event.hpp
@@ -1,12 +1,21 @@
 #pragma once
 
 #include <functional>
+#include <mutex>
 
 namespace vkcv {
+	
+	template<typename... T>
+	struct event_handle {
+		uint32_t id;
+	};
 
     template<typename... T>
     struct event_function {
         typedef std::function<void(T...)> type;
+	
+		event_handle<T...> handle;
+        type callback;
     };
 
     /**
@@ -16,7 +25,9 @@ namespace vkcv {
     template<typename... T>
     struct event {
     private:
-        std::vector<typename event_function<T...>::type> m_handles;
+        std::vector< event_function<T...> > m_functions;
+        uint32_t m_id_counter;
+		std::mutex m_mutex;
 
     public:
 
@@ -25,32 +36,60 @@ namespace vkcv {
          * @param arguments of the given function
          */
         void operator()(T... arguments) {
-            for (auto &handle : this->m_handles) {
-                handle(arguments...);
-            }
+			lock();
+
+            for (auto &function : this->m_functions) {
+				function.callback(arguments...);
+			}
+            
+            unlock();
         }
 
         /**
          * adds a function handle to the event to be called
-         * @param handle of the function
+         * @param callback of the function
+         * @return handle of the function
          */
-        typename event_function<T...>::type add(typename event_function<T...>::type handle) {
-            this->m_handles.push_back(handle);
-            return handle;
+		event_handle<T...> add(typename event_function<T...>::type callback) {
+			event_function<T...> function;
+			function.handle = { m_id_counter++ };
+			function.callback = callback;
+            this->m_functions.push_back(function);
+            return function.handle;
         }
 
         /**
          * removes a function handle of the event
          * @param handle of the function
          */
-        void remove(typename event_function<T...>::type handle) {
-            this->m_handles.erase(
-                    remove(this->m_handles.begin(), this->m_handles.end(), handle),
-                    this->m_handles.end()
+        void remove(event_handle<T...> handle) {
+            this->m_functions.erase(
+					std::remove_if(this->m_functions.begin(), this->m_functions.end(), [&handle](auto function){
+						return (handle.id == function.handle.id);
+					}),
+                    this->m_functions.end()
             );
         }
+        
+        /**
+         * locks the event so its function handles won't be called
+         */
+        void lock() {
+			m_mutex.lock();
+        }
+	
+		/**
+		* unlocks the event so its function handles can be called after locking
+		*/
+        void unlock() {
+			m_mutex.unlock();
+        }
 
-        event() = default;
+        explicit event(bool locked = false) {
+        	if (locked) {
+        		lock();
+        	}
+        }
 
         event(const event &other) = delete;
 
diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp
index 58f795f0c99a0cd1b05045f9f401a26c0aac1b88..ea05bd212dd9314957e4771070bedb3963b1b2dc 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -7,31 +7,51 @@
 
 #include <iostream>
 
+#include "Event.hpp"
+
 namespace vkcv
 {
 	
+	typedef typename event_function<uint64_t>::type HandleDestroyFunction;
+	
 	class Handle {
 		friend std::ostream& operator << (std::ostream& out, const Handle& handle);
 		
 	private:
 		uint64_t m_id;
+		uint64_t* m_rc;
+		
+		HandleDestroyFunction m_destroy;
 	
 	protected:
 		Handle();
 		
-		explicit Handle(uint64_t id);
+		explicit Handle(uint64_t id, const HandleDestroyFunction& destroy = nullptr);
 		
+		/**
+		 * Returns the actual handle id of a handle.
+		 *
+		 * @return Handle id
+		 */
 		[[nodiscard]]
 		uint64_t getId() const;
+		
+		/**
+		 * Returns the reference counter of a handle
+		 *
+		 * @return Reference counter
+		 */
+		[[nodiscard]]
+		uint64_t getRC() const;
 	
 	public:
-		virtual ~Handle() = default;
+		virtual ~Handle();
 		
-		Handle(const Handle& other) = default;
-		Handle(Handle&& other) = default;
+		Handle(const Handle& other);
+		Handle(Handle&& other) noexcept;
 		
-		Handle& operator=(const Handle& other) = default;
-		Handle& operator=(Handle&& other) = default;
+		Handle& operator=(const Handle& other);
+		Handle& operator=(Handle&& other) noexcept;
 		
 		explicit operator bool() const;
 		bool operator!() const;
@@ -59,7 +79,7 @@ namespace vkcv
 		using Handle::Handle;
 	};
 	
-	class ResourcesHandle : public Handle {
+	class DescriptorSetHandle : public Handle {
 		friend class DescriptorManager;
 	private:
 		using Handle::Handle;
@@ -73,8 +93,19 @@ namespace vkcv
 
 	class ImageHandle : public Handle {
 		friend class ImageManager;
-	private:
 		using Handle::Handle;
+	public:
+		[[nodiscard]]
+		bool isSwapchainImage() const;
+		
+		static ImageHandle createSwapchainImageHandle(const HandleDestroyFunction& destroy = nullptr);
+		
 	};
+
+    class CommandStreamHandle : public Handle {
+        friend class CommandStreamManager;
+    private:
+        using Handle::Handle;
+    };
 	
 }
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index d56fac9c0e442faeff1898443d737df365c9ed45..eac96975886a92e0043bbb97cbe64f76943efa7e 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -9,8 +9,12 @@
 #include "Handles.hpp"
 
 namespace vkcv {
-	
-	class ImageManager;
+
+    // forward declares
+    class ImageManager;
+
+	bool isDepthFormat(const vk::Format format);
+
 	class Image {
 		friend class Core;
 	public:
@@ -25,28 +29,35 @@ namespace vkcv {
 		
 		[[nodiscard]]
 		uint32_t getDepth() const;
-		
-		[[nodiscard]]
-		vk::ImageLayout getLayout() const;
 
 		[[nodiscard]]
 		vkcv::ImageHandle getHandle() const;
-		
+
+		[[nodiscard]]
+		uint32_t getMipCount() const;
+
 		void switchLayout(vk::ImageLayout newLayout);
 		
 		void fill(void* data, size_t size = SIZE_MAX);
+		void generateMipChainImmediate();
+		void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream);
 	private:
-		ImageManager* const m_manager;
-		const ImageHandle m_handle;
-		const vk::Format m_format;
-		const uint32_t m_width;
-		const uint32_t m_height;
-		const uint32_t m_depth;
-		vk::ImageLayout m_layout;
-
-		Image(ImageManager* manager, const ImageHandle& handle, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
+	    // 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);
 		
-		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
+		static Image create(
+			ImageManager* manager,
+			vk::Format format,
+			uint32_t width,
+			uint32_t height,
+			uint32_t depth,
+			uint32_t mipCount,
+			bool supportStorage,
+			bool supportColorAttachment);
 		
 	};
 	
diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d484711f642506926b1281a830fb2c9caf8240a2
--- /dev/null
+++ b/include/vkcv/Logger.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <stdio.h>
+
+namespace vkcv {
+	
+	enum class LogLevel {
+		INFO,
+		WARNING,
+		ERROR
+	};
+	
+	constexpr auto getLogOutput(LogLevel level) {
+		switch (level) {
+			case LogLevel::INFO:
+				return stdout;
+			default:
+				return stderr;
+		}
+	}
+	
+	constexpr const char* getLogName(LogLevel level) {
+		switch (level) {
+			case LogLevel::INFO:
+				return "INFO";
+			case LogLevel::WARNING:
+				return "WARNING";
+			case LogLevel::ERROR:
+				return "ERROR";
+			default:
+				return "UNKNOWN";
+		}
+	}
+	
+#ifndef NDEBUG
+#ifndef VKCV_DEBUG_MESSAGE_LEN
+#define VKCV_DEBUG_MESSAGE_LEN 1024
+#endif
+
+#ifdef _MSC_VER
+#define __PRETTY_FUNCTION__ __FUNCSIG__
+#endif
+
+#define vkcv_log(level, ...) {      \
+  char output_message [             \
+    VKCV_DEBUG_MESSAGE_LEN          \
+  ];                                \
+  snprintf(                         \
+    output_message,                 \
+    VKCV_DEBUG_MESSAGE_LEN,         \
+    __VA_ARGS__                     \
+  );                                \
+  fprintf(                          \
+    getLogOutput(level),            \
+    "[%s]: %s [%s, line %d: %s]\n", \
+  	vkcv::getLogName(level),        \
+    output_message,                 \
+    __FILE__,                       \
+    __LINE__,                       \
+    __PRETTY_FUNCTION__             \
+  );                                \
+}
+
+#else
+#define vkcv_log(level, ...) {}
+#endif
+
+}
diff --git a/include/vkcv/PassConfig.hpp b/include/vkcv/PassConfig.hpp
index d9a5bcd83acca5f5ba86b4e6ce6973acbed89de6..8f3b516d4b4451c513366fbd8469908bccde6a5f 100644
--- a/include/vkcv/PassConfig.hpp
+++ b/include/vkcv/PassConfig.hpp
@@ -32,23 +32,15 @@ namespace vkcv
 
     struct AttachmentDescription
     {
-        AttachmentDescription() = delete;
         AttachmentDescription(
-			AttachmentLayout initial,
-			AttachmentLayout in_pass,
-			AttachmentLayout final,
-			AttachmentOperation store_op,
-			AttachmentOperation load_op,
-			vk::Format format) noexcept;
-
-        AttachmentLayout layout_initial;
-        AttachmentLayout layout_in_pass;
-        AttachmentLayout layout_final;
+            AttachmentOperation store_op,
+            AttachmentOperation load_op,
+            vk::Format format) noexcept;
 
         AttachmentOperation store_operation;
         AttachmentOperation load_operation;
 
-		vk::Format format;
+        vk::Format format;
     };
 
     struct PassConfig
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index a8e1334f3ee5d6774b9fed35edd685f19747814d..1e00c5209118469a7dc6ff1eb1e3c98a3c6903a7 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -7,36 +7,24 @@
 
 #include <vector>
 #include <cstdint>
-#include "vkcv/Handles.hpp"
+#include "Handles.hpp"
 #include "ShaderProgram.hpp"
-#include <vkcv/VertexLayout.hpp>
+#include "VertexLayout.hpp"
 
 namespace vkcv {
 
-    struct PipelineConfig {
-        /**
-         *  Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the
-         *  dimensions of the application window @p width and @p height. A handle for the Render Pass is also needed, @p passHandle.
-         *
-         * @param shaderProgram shaders of the pipeline
-         * @param height height of the application window
-         * @param width width of the application window
-         * @param passHandle handle for Render Pass
-         */
-        PipelineConfig(
-			const ShaderProgram&						shaderProgram, 
-			uint32_t									width, 
-			uint32_t									height, 
-			PassHandle									&passHandle,
-			const std::vector<VertexAttribute>			&vertexAttributes,
-			const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts);
+    enum class PrimitiveTopology{PointList, LineList, TriangleList };
 
-		ShaderProgram							m_ShaderProgram;
-        uint32_t								m_Height;
-        uint32_t								m_Width;
-        PassHandle								m_PassHandle;
-		std::vector<VertexAttribute>			m_vertexAttributes;
-		std::vector<vk::DescriptorSetLayout>	m_descriptorLayouts;
+    struct PipelineConfig {
+        ShaderProgram                         m_ShaderProgram;
+        uint32_t                              m_Width;
+		uint32_t                              m_Height;
+        PassHandle                            m_PassHandle;
+        VertexLayout                          m_VertexLayout;
+        std::vector<vk::DescriptorSetLayout>  m_DescriptorLayouts;
+        bool                                  m_UseDynamicViewport;
+        bool                                  m_UseConservativeRasterization = false;
+        PrimitiveTopology                     m_PrimitiveTopology = PrimitiveTopology::TriangleList;
     };
 
 }
\ No newline at end of file
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index ef5d1f00ea3eeb97d97d8824439ded1ed326f33c..78b1f02169fe630427b9f66150e32078d42b7b3f 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -8,23 +8,16 @@
 #include <unordered_map>
 #include <fstream>
 #include <iostream>
+#include <algorithm>
 #include <filesystem>
 #include <vulkan/vulkan.hpp>
 #include <spirv_cross.hpp>
-#include "vkcv/VertexLayout.hpp"
+#include "VertexLayout.hpp"
+#include "ShaderStage.hpp"
+#include "DescriptorConfig.hpp"
 
 namespace vkcv {
 
-    enum class ShaderStage
-    {
-        VERTEX,
-        TESS_CONTROL,
-        TESS_EVAL,
-        GEOMETRY,
-        FRAGMENT,
-        COMPUTE
-    };
-
     struct Shader
     {
         std::vector<char> shaderCode;
@@ -55,13 +48,24 @@ namespace vkcv {
 
         bool existsShader(ShaderStage shaderStage) const;
 
-        void reflectShader(ShaderStage shaderStage);
+        const std::vector<VertexAttachment> &getVertexAttachments() const;
+		size_t getPushConstantSize() const;
 
-        const VertexLayout &getVertexLayout() const;
+        const std::vector<std::vector<DescriptorBinding>>& getReflectedDescriptors() const;
 
 	private:
+	    /**
+	     * Called after successfully adding a shader to the program.
+	     * Fills vertex input attachments and descriptor sets (if present).
+	     * @param shaderStage the stage to reflect data from
+	     */
+        void reflectShader(ShaderStage shaderStage);
+
         std::unordered_map<ShaderStage, Shader> m_Shaders;
 
-        VertexLayout m_VertexLayout;
+        // contains all vertex input attachments used in the vertex buffer
+        std::vector<VertexAttachment> m_VertexAttachments;
+        std::vector<std::vector<DescriptorBinding>> m_DescriptorSets;
+		size_t m_pushConstantSize = 0;
 	};
 }
diff --git a/include/vkcv/ShaderStage.hpp b/include/vkcv/ShaderStage.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..dca395bdba82a2f1cb38bb0a25196cfd3dab8019
--- /dev/null
+++ b/include/vkcv/ShaderStage.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+namespace vkcv {
+	
+	enum class ShaderStage
+	{
+		VERTEX,
+		TESS_CONTROL,
+		TESS_EVAL,
+		GEOMETRY,
+		FRAGMENT,
+		COMPUTE
+	};
+
+}
diff --git a/include/vkcv/SwapChain.hpp b/include/vkcv/SwapChain.hpp
deleted file mode 100644
index 1087d6364f6f811b741904d4e2b31fcfeb450901..0000000000000000000000000000000000000000
--- a/include/vkcv/SwapChain.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#pragma once
-#include "vulkan/vulkan.hpp"
-#include "Context.hpp"
-#include "vkcv/Window.hpp"
-
-namespace vkcv {
-    class SwapChain final {
-    private:
-
-        vk::SurfaceKHR m_surface;
-        vk::SwapchainKHR m_swapchain;
-        vk::SurfaceFormatKHR m_format;
-
-		uint32_t m_ImageCount;
-
-        /**
-         * Constructor of a SwapChain object
-         * glfw is not initialized in this class because ist must be sure that there exists a context first
-         * glfw is already initialized by the window class
-         * @param surface used by the swapchain
-         * @param swapchain to show images in the window
-         * @param format
-         */
-        SwapChain(vk::SurfaceKHR surface, vk::SwapchainKHR swapchain, vk::SurfaceFormatKHR format, uint32_t imageCount);
-
-    public:
-        SwapChain(const SwapChain &other) = default;
-        SwapChain(SwapChain &&other) = default;
-
-        /**
-         * @return The swapchain linked with the #SwapChain class
-         * @note The reference to our Swapchain variable is needed for the recreation step
-         */
-        [[nodiscard]]
-        const vk::SwapchainKHR& getSwapchain() const;
-
-        /**
-         * gets the current surface object
-         * @return current surface
-         */
-        [[nodiscard]]
-        vk::SurfaceKHR getSurface();
-        /**
-         * gets the current surface format
-         * @return gets the surface format
-         */
-        [[nodiscard]]
-        vk::SurfaceFormatKHR getSurfaceFormat();
-
-        /**
-         * creates a swap chain object out of the given window and the given context
-         * @param window a wrapper that represents a glfw window
-         * @param context of the application
-         * @return returns an object of swapChain
-         */
-        static SwapChain create(const Window &window, const Context &context, const vk::SurfaceKHR surface);
-
-        /**
-         * Destructor of SwapChain
-         */
-        virtual ~SwapChain();
-
-		/**
-		 * @return number of images in swapchain
-		*/
-		uint32_t getImageCount();
-    };
-
-}
diff --git a/include/vkcv/Swapchain.hpp b/include/vkcv/Swapchain.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b75fc5a87156ea56061e41b4b0974928c83ffa28
--- /dev/null
+++ b/include/vkcv/Swapchain.hpp
@@ -0,0 +1,122 @@
+#pragma once
+#include "vulkan/vulkan.hpp"
+#include "Context.hpp"
+#include "vkcv/Window.hpp"
+
+#include <atomic>
+
+namespace vkcv
+{
+    class Swapchain final {
+    private:
+    	friend class Core;
+
+        struct Surface
+        {
+            vk::SurfaceKHR handle;
+            std::vector<vk::SurfaceFormatKHR> formats;
+            vk::SurfaceCapabilitiesKHR capabilities;
+            std::vector<vk::PresentModeKHR> presentModes;
+        };
+        
+        Surface m_Surface;
+
+        vk::SwapchainKHR m_Swapchain;
+        vk::Format m_Format;
+        vk::ColorSpaceKHR m_ColorSpace;
+        vk::PresentModeKHR m_PresentMode;
+		uint32_t m_ImageCount;
+	
+		vk::Extent2D m_Extent;
+	
+		std::atomic<bool> m_RecreationRequired;
+
+        /**
+         * Constructor of a SwapChain object
+         * glfw is not initialized in this class because ist must be sure that there exists a context first
+         * glfw is already initialized by the window class
+         * @param surface used by the swapchain
+         * @param swapchain to show images in the window
+         * @param format
+         */
+         // TODO:
+        Swapchain(const Surface &surface,
+                  vk::SwapchainKHR swapchain,
+                  vk::Format format,
+                  vk::ColorSpaceKHR colorSpace,
+                  vk::PresentModeKHR presentMode,
+                  uint32_t imageCount,
+				  vk::Extent2D extent) noexcept;
+	
+		/**
+		 * TODO
+		 *
+		 * @return
+		 */
+		bool shouldUpdateSwapchain() const;
+	
+		/**
+		 * TODO
+		 *
+		 * context
+		 * window
+		 */
+		void updateSwapchain(const Context &context, const Window &window);
+	
+		/**
+		 *
+		 */
+		void signalSwapchainRecreation();
+
+    public:
+    	Swapchain(const Swapchain& other);
+
+        /**
+         * @return The swapchain linked with the #SwapChain class
+         * @note The reference to our Swapchain variable is needed for the recreation step
+         */
+        [[nodiscard]]
+        const vk::SwapchainKHR& getSwapchain() const;
+
+        /**
+         * gets the current surface object
+         * @return current surface
+         */
+        [[nodiscard]]
+        vk::SurfaceKHR getSurface() const;
+
+        /**
+         * gets the chosen swapchain format
+         * @return gets the chosen swapchain format
+         */
+        [[nodiscard]]
+        vk::Format getFormat() const;
+
+        /**
+         * creates a swap chain object out of the given window and the given context
+         * @param window a wrapper that represents a glfw window
+         * @param context of the application
+         * @return returns an object of swapChain
+         */
+        static Swapchain create(const Window &window, const Context &context);
+
+        /**
+         * Destructor of SwapChain
+         */
+        virtual ~Swapchain();
+
+		/**
+		 * @return number of images in swapchain
+		*/
+		uint32_t getImageCount() const;
+	
+        /**
+         * TODO
+         *
+         * @return
+         */
+        [[nodiscard]]
+		const vk::Extent2D& getExtent() const;
+    
+    };
+}
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index ee0ad8ef56d5284af2be4c81b7ea2f0d052d5a6f..0600b99a24a327605e89b2e8ec304c20dbf7ad2e 100644
--- a/include/vkcv/VertexLayout.hpp
+++ b/include/vkcv/VertexLayout.hpp
@@ -1,29 +1,11 @@
 #pragma once
 
-#include <unordered_map>
 #include <vector>
 #include <iostream>
+#include <string>
 
 namespace vkcv{
-
-	/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
-	enum class PrimitiveType : uint32_t {
-		UNDEFINED = 0,
-		POSITION = 1,
-		NORMAL = 2,
-		TEXCOORD_0 = 3
-	};
-	/* This struct describes one vertex attribute of a vertex buffer. */
-	typedef struct {
-		PrimitiveType type;			// POSITION, NORMAL, ...
-		uint32_t offset;			// offset in bytes
-		uint32_t length;			// length of ... in bytes
-		uint32_t stride;			// stride in bytes
-		uint16_t componentType;		// eg. 5126 for float
-		uint8_t  componentCount;	// eg. 3 for vec3
-	} VertexAttribute;
-
-    enum class VertexFormat{
+    enum class VertexAttachmentFormat{
         FLOAT,
         FLOAT2,
         FLOAT3,
@@ -34,22 +16,51 @@ namespace vkcv{
         INT4
     };
 
-	uint32_t getFormatSize(VertexFormat format);
+	uint32_t getFormatSize(VertexAttachmentFormat format);
 
-    struct VertexInputAttachment{
-        VertexInputAttachment() = delete;
-        VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept;
+    struct VertexAttachment{
+        friend struct VertexBinding;
+        /**
+         * Describes an individual vertex input attribute/attachment.
+         * @param inputLocation its location in the vertex shader.
+         * @param name the name referred to in the shader.
+         * @param format the format (and therefore, the size) this attachment is in.
+         * The offset is calculated when a collection of attachments forms a binding, hence the friend declaration.
+         */
+        VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept;
+        VertexAttachment() = delete;
 
-        uint32_t location;
-        uint32_t binding;
-        VertexFormat format;
-        uint32_t offset;
+        uint32_t                inputLocation;
+        std::string             name;
+        VertexAttachmentFormat  format;
+        uint32_t                offset;
+    };
+
+    struct VertexBinding{
+        /**
+         * Describes all vertex input attachments _one_ buffer contains to create a vertex buffer binding.
+         * NOTE: multiple vertex layouts may contain various (mutually exclusive) vertex input attachments
+         * to form one complete vertex buffer binding!
+         * @param bindingLocation its entry in the buffers that make up the whole vertex buffer.
+         * @param attachments the vertex input attachments this specific buffer layout contains.
+         */
+        VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept;
+        VertexBinding() = delete;
+
+        uint32_t                        bindingLocation;
+        uint32_t                        stride;
+        std::vector<VertexAttachment>   vertexAttachments;
     };
 
     struct VertexLayout{
+        /**
+         * Describes the complete layout of one vertex, e.g. all of the vertex input attachments used,
+         * and all of the buffer bindings that refer to the attachments (for when multiple buffers are used).
+         * @param bindings bindings the complete vertex buffer is comprised of.
+         */
         VertexLayout() noexcept;
-        VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept;
-        std::unordered_map<uint32_t, VertexInputAttachment> attachmentMap;
-        uint32_t stride;
+        VertexLayout(const std::vector<VertexBinding> &bindings) noexcept;
+
+        std::vector<VertexBinding> vertexBindings;
     };
-}
\ No newline at end of file
+}
diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp
index 7428c7c73eb481f7352821faed36257211dfd5bf..7dc6c1b7dc8fef4d5de7de5b0a9976bf714e6ac2 100644
--- a/include/vkcv/Window.hpp
+++ b/include/vkcv/Window.hpp
@@ -7,23 +7,24 @@
 
 #define NOMINMAX
 #include <algorithm>
+
 #include "Event.hpp"
 
 struct GLFWwindow;
 
 namespace vkcv {
 
-    class Window final {
-    private:
-        GLFWwindow *m_window;
-
-
-        /**
+    class Window {
+	protected:
+		GLFWwindow *m_window;
+	
+		/**
          *
          * @param GLFWwindow of the class
          */
-        explicit Window(GLFWwindow *window);
-
+		explicit Window(GLFWwindow *window);
+		
+    private:
         /**
          * mouse callback for moving the mouse on the screen
          * @param[in] window The window that received the event.
@@ -40,6 +41,12 @@ namespace vkcv {
          */
         static void onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods);
 
+        /**
+         * @brief A callback function for handling mouse scrolling events.
+         * @param[in] callbackWindow The window that received the event.
+         * @param[in] xoffset The extent of horizontal scrolling.
+         * @param[in] yoffset The extent of vertical scrolling.
+         */
         static void onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset);
 
         /**
@@ -59,6 +66,19 @@ namespace vkcv {
          * @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
          */
         static void onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods);
+	
+        /**
+         * char callback for any typed character
+         * @param[in] window The window that received the event
+         * @param[in] c The character that got typed
+         */
+		static void onCharEvent(GLFWwindow *callbackWindow, unsigned int c);
+
+        /**
+         * @brief A callback function for gamepad input events.
+         * @param gamepadIndex The gamepad index.
+         */
+        static void onGamepadEvent(int gamepadIndex);
 
     public:
         /**
@@ -78,11 +98,6 @@ namespace vkcv {
         [[nodiscard]]
         bool isWindowOpen() const;
 
-        /**
-         * binds windowEvents to lambda events
-         */
-        void initEvents();
-
         /**
          * polls all events on the GLFWwindow
          */
@@ -96,6 +111,8 @@ namespace vkcv {
         event< double, double > e_mouseScroll;
         event< int, int > e_resize;
         event< int, int, int, int > e_key;
+        event< unsigned int > e_char;
+        event< int > e_gamepad;
 
         /**
          * returns the current window
@@ -142,4 +159,4 @@ namespace vkcv {
         virtual ~Window();
     };
 
-}
\ No newline at end of file
+}
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index d6c372d07f52ed25acaaae60787e258afe4be085..802200ad5deb76decbb75e30e1fbd14bff3b7e3b 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -2,5 +2,8 @@
 # Add new modules here:
 add_subdirectory(asset_loader)
 add_subdirectory(camera)
+add_subdirectory(gui)
+add_subdirectory(material)
 add_subdirectory(scene)
+add_subdirectory(shader_compiler)
 add_subdirectory(testing)
diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt
index d2a9b817ea68c7851fd2123f76b378d8a4d85ac0..c5a1fd0eb9620d3a95af1c52a9e7456337047c7b 100644
--- a/modules/asset_loader/CMakeLists.txt
+++ b/modules/asset_loader/CMakeLists.txt
@@ -38,3 +38,5 @@ target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_
 
 # add the own include directory for public headers
 target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include})
+
+target_compile_definitions(vkcv_asset_loader PUBLIC ${vkcv_asset_loader_definitions})
diff --git a/modules/asset_loader/config/STB.cmake b/modules/asset_loader/config/STB.cmake
index da20d3ec07f98c865b4c6e38518f668b226cbfb9..1287d0a9ddda559e061ddd680bc815e24b3e2075 100644
--- a/modules/asset_loader/config/STB.cmake
+++ b/modules/asset_loader/config/STB.cmake
@@ -1,6 +1,10 @@
 
 if (EXISTS "${vkcv_asset_loader_lib_path}/stb")
 	list(APPEND vkcv_asset_loader_includes ${vkcv_asset_loader_lib}/stb)
+	
+	list(APPEND vkcv_asset_loader_definitions STB_IMAGE_IMPLEMENTATION)
+	list(APPEND vkcv_asset_loader_definitions STBI_ONLY_JPEG)
+	list(APPEND vkcv_asset_loader_definitions STBI_ONLY_PNG)
 else()
 	message(WARNING "STB is required..! Update the submodules!")
 endif ()
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 24687e846ff9eae3275de357331a825f0b4ed2c3..4107d57ee97a6efe0475c6d9dbd80d2603e0afe8 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -1,16 +1,16 @@
 #pragma once
 /**
- * @authors Trevor Hollmann
+ * @authors Trevor Hollmann, Mara Vogt, Susanne Dötsch
  * @file include/vkcv/asset/asset_loader.h
  * @brief Interface of the asset loader module for the vkcv framework.
  */
 
 #include <string>
 #include <vector>
+#include <array>
 #include <cstdint>
-#include <vkcv/VertexLayout.hpp>
 
-/* These macros define limits of the following structs. Implementations can
+/** These macros define limits of the following structs. Implementations can
  * test against these limits when performing sanity checks. The main constraint
  * expressed is that of the data type: Material indices are identified by a
  * uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
@@ -20,17 +20,18 @@
 #define MAX_MATERIALS_PER_MESH UINT8_MAX
 #define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
 
-/* LOADING MESHES
+/** LOADING MESHES
  * The description of meshes is a hierarchy of structures with the Mesh at the
  * top.
  *
  * Each Mesh has an array of one or more vertex groups (called "primitives" in
- * glTF parlance) and an array of zero or more Materials.
+ * glTF parlance). Specifically, it has an array of indices into an array of
+ * vertex groups defined by the Scene struct.
  *
  * Each vertex group describes a part of the meshes vertices by defining how
  * they should be rendered (as points, lines, triangles), how many indices and
  * vertices there are, how the content of the vertex buffer is to be
- * interpreted and which material from the Meshes materials array should be
+ * interpreted and which material from the Scenes materials array should be
  * used for the surface of the vertices.
  * As a bonus there is also the axis aligned bounding box of the vertices.
  *
@@ -44,20 +45,99 @@
 
 namespace vkcv::asset {
 
-/* This enum matches modes in fx-gltf, the library returns a standard mode
+/** This enum matches modes in fx-gltf, the library returns a standard mode
  * (TRIANGLES) if no mode is given in the file. */
-enum PrimitiveMode {
+enum class PrimitiveMode : uint8_t {
 	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
 	TRIANGLEFAN
 };
-/* The indices in the index buffer can be of different bit width. */
-enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
 
+/** The indices in the index buffer can be of different bit width. */
+enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
+
+typedef struct {
+	// TODO define struct for samplers (low priority)
+	// NOTE: glTF defines samplers based on OpenGL, which can not be
+	// directly translated to Vulkan. Specifically, OpenGL (and glTF)
+	// define a different set of Min/Mag-filters than Vulkan.
+} Sampler;
+
+/** struct for defining the loaded texture */
 typedef struct {
-	// TODO not yet needed for the first (unlit) triangle
+	int sampler;		// index into the sampler array of the Scene
+	uint8_t channels;	// number of channels
+	uint16_t w, h;		// width and height of the texture
+	std::vector<uint8_t> data;	// binary data of the decoded texture
+} Texture;
+
+/** The asset loader module only supports the PBR-MetallicRoughness model for
+ * materials.*/
+typedef struct {
+	uint16_t textureMask;	// bit mask with active texture targets
+	// Indices into the Array.textures array
+	int baseColor, metalRough, normal, occlusion, emissive;
+	// Scaling factors for each texture target
+	struct { float r, g, b, a; } baseColorFactor;
+	float metallicFactor, roughnessFactor;
+	float normalScale;
+	float occlusionStrength;
+	struct { float r, g, b; } emissiveFactor;
 } Material;
 
-/* This struct represents one (possibly the only) part of a mesh. There is
+/** Flags for the bit-mask in the Material struct. To check if a material has a
+ * certain texture target, you can use the hasTexture() function below, passing
+ * the material struct and the enum. */
+enum class PBRTextureTarget {
+	baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
+};
+
+/** This macro translates the index of an enum in the defined order to an
+ * integer with a single bit set in the corresponding place. It is used for
+ * working with the bitmask of texture targets ("textureMask") in the Material
+ * struct:
+ * 	Material mat = ...;
+ * 	if (mat.textureMask & bitflag(PBRTextureTarget::baseColor)) {...}
+ * However, this logic is also encapsulated in the convenience-function
+ * materialHasTexture() so users of the asset loader module can avoid direct
+ * contact with bit-level operations. */
+#define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))
+
+/** To signal that a certain texture target is active in a Material struct, its
+ * bit is set in the textureMask. You can use this function to check that:
+ * Material mat = ...;
+ * if (materialHasTexture(&mat, baseColor)) {...} */
+bool materialHasTexture(const Material *const m, const PBRTextureTarget t);
+
+/** With these enums, 0 is reserved to signal uninitialized or invalid data. */
+enum class PrimitiveType : uint32_t {
+    UNDEFINED = 0,
+    POSITION = 1,
+    NORMAL = 2,
+    TEXCOORD_0 = 3,
+    TEXCOORD_1 = 4
+};
+
+/** These integer values are used the same way in OpenGL, Vulkan and glTF. This
+ * enum is not needed for translation, it's only for the programmers
+ * convenience (easier to read in if/switch statements etc). While this enum
+ * exists in (almost) the same definition in the fx-gltf library, we want to
+ * avoid exposing that dependency, thus it is re-defined here. */
+enum class ComponentType : uint16_t {
+    NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123,
+    UINT32 = 5125, FLOAT32 = 5126
+};
+
+/** This struct describes one vertex attribute of a vertex buffer. */
+typedef struct {
+    PrimitiveType type;			// POSITION, NORMAL, ...
+    uint32_t offset;			// offset in bytes
+    uint32_t length;			// length of ... in bytes
+    uint32_t stride;			// stride in bytes
+    ComponentType componentType;		// eg. 5126 for float
+    uint8_t  componentCount;	// eg. 3 for vec3
+} VertexAttribute;
+
+/** This struct represents one (possibly the only) part of a mesh. There is
  * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
  * common but not always used). If there is no index buffer, this is indicated
  * by indexBuffer.data being empty. Each vertex buffer can have one or more
@@ -71,38 +151,43 @@ typedef struct {
 	} indexBuffer;
 	struct {
 		std::vector<uint8_t> data; // binary data of the vertex buffer
-		std::vector<VertexAttribute> attributes;
+		std::vector<VertexAttribute> attributes; // description of one
 	} vertexBuffer;
 	struct { float x, y, z; } min;	// bounding box lower left
 	struct { float x, y, z; } max;	// bounding box upper right
-	uint8_t materialIndex;		// index to one of the meshes materials
+	int materialIndex;		// index to one of the materials
 } VertexGroup;
 
-/* This struct represents a single mesh loaded from a glTF file. It consists of
- * at least one VertexVroup and any number of Materials. */
+/** This struct represents a single mesh as it was loaded from a glTF file. It
+ * consists of at least one VertexGroup, which then references other resources
+ * such as Materials. */
 typedef struct {
 	std::string name;
-	std::vector<VertexGroup> vertexGroups;
-	std::vector<Material> materials;
-	// FIXME Dirty hack to get one(!) texture for our cube demo
-	// hardcoded to always have RGBA channel layout
-	struct {
-		int w, h, ch;	// width, height and channels of image
-		uint8_t *img;	// raw bytes, free after use (deal with it)
-	} texture_hack;
+	std::array<float, 16> modelMatrix;
+	std::vector<int> vertexGroups;
 } Mesh;
 
+/** The scene struct is simply a collection of objects in the scene as well as
+ * the resources used by those objects.
+ * For now the only type of object are the meshes and they are represented in a
+ * flat array.
+ * Note that parent-child relations are not yet possible. */
+typedef struct {
+	std::vector<Mesh> meshes;
+	std::vector<VertexGroup> vertexGroups;
+	std::vector<Material> materials;
+	std::vector<Texture> textures;
+	std::vector<Sampler> samplers;
+} Scene;
 
 /**
- * In its first iteration the asset loader module will only allow loading
- * single meshes, one per glTF file.
- * It will later be extended to allow loading entire scenes from glTF files.
+ * Load every mesh from the glTF file, as well as materials and textures.
  *
- * @param path must be the path to a glTF file containing a single mesh.
- * @param mesh is a reference to a Mesh struct that will be filled with the
+ * @param path must be the path to a glTF or glb file.
+ * @param scene is a reference to a Scene struct that will be filled with the
  * 	content of the glTF file being loaded.
  * */
-int loadMesh(const std::string &path, Mesh &mesh);
+int loadScene(const std::string &path, Scene &scene);
 
 
 }
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index e660b442d0b9a0208f95c9d753ef19e926bcac44..c21d0c9f70bc81561e1078b15b8372e6dd4730f5 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -3,9 +3,9 @@
 #include <string.h>	// memcpy(3)
 #include <stdlib.h>	// calloc(3)
 #include <fx/gltf.h>
-#define STB_IMAGE_IMPLEMENTATION
-#define STBI_ONLY_JPEG
 #include <stb_image.h>
+#include <vkcv/Logger.hpp>
+#include <algorithm>
 
 namespace vkcv::asset {
 
@@ -39,170 +39,328 @@ uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) {
  * @param path path to file that is responsible for error
  */
 void print_what (const std::exception& e, const std::string &path) {
-	fprintf(stderr, "ERROR loading file %s: %s\n", path.c_str(), e.what());
+	vkcv_log(LogLevel::ERROR, "Loading file %s: %s",
+			 path.c_str(), e.what());
+	
 	try {
 		std::rethrow_if_nested(e);
 	} catch (const std::exception& nested) {
-		std::cerr << "nested: ";
 		print_what(nested, path);
 	}
 }
 
-int loadMesh(const std::string &path, Mesh &mesh) {
-	fx::gltf::Document object;
-
-	try {
-		if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
-			object = fx::gltf::LoadFromBinary(path);
-		} else {
-			object = fx::gltf::LoadFromText(path);
-		}
-	} catch (const std::system_error &err) {
-		print_what(err, path);
-		return 0;
-	} catch (const std::exception &e) {
-		print_what(e, path);
-		return 0;
-	}
-
-	// TODO Temporary restriction: Only one mesh per glTF file allowed
-	// currently. Later, we want to support whole scenes with more than
-	// just meshes.
-	if (object.meshes.size() != 1) return 0;
-
-	fx::gltf::Mesh const &objectMesh = object.meshes[0];
-	// TODO We want to support more than one vertex group per mesh
-	// eventually... right now this is hard-coded to use only the first one
-	// because we only care about the example triangle and cube
-	fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[0];
-	fx::gltf::Accessor posAccessor;
-	
-	std::vector<VertexAttribute> vertexAttributes;
-	vertexAttributes.reserve(objectPrimitive.attributes.size());
-	
-	for (auto const & attrib : objectPrimitive.attributes) {
-		fx::gltf::Accessor accessor =  object.accessors[attrib.second];
-		VertexAttribute attribute;
-
-		if (attrib.first == "POSITION") {
-			attribute.type = PrimitiveType::POSITION;
-			posAccessor = accessor;
-		} else if (attrib.first == "NORMAL") {
-			attribute.type = PrimitiveType::NORMAL;
-		} else if (attrib.first == "TEXCOORD_0") {
-			attribute.type = PrimitiveType::TEXCOORD_0;
-		} else {
-			return 0;
-		}
-		
-		attribute.offset = object.bufferViews[accessor.bufferView].byteOffset;
-		attribute.length = object.bufferViews[accessor.bufferView].byteLength;
-		attribute.stride = object.bufferViews[accessor.bufferView].byteStride;
-		attribute.componentType = static_cast<uint16_t>(accessor.componentType);
-		
-		if (convertTypeToInt(accessor.type) != 10) {
-			attribute.componentCount = convertTypeToInt(accessor.type);
-		} else {
-			return 0;
-		}
-		
-		vertexAttributes.push_back(attribute);
-	}
-
-	// TODO consider the case where there is no index buffer (not all
-	// meshes have to use indexed rendering)
-	const fx::gltf::Accessor &indexAccessor = object.accessors[objectPrimitive.indices];
-	const fx::gltf::BufferView &indexBufferView = object.bufferViews[indexAccessor.bufferView];
-	const fx::gltf::Buffer &indexBuffer = object.buffers[indexBufferView.buffer];
-	
-	std::vector<uint8_t> indexBufferData;
-	indexBufferData.resize(indexBufferView.byteLength);
-	{
-		const size_t off = indexBufferView.byteOffset;
-		const void *const ptr = ((char*)indexBuffer.data.data()) + off;
-		if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
-			std::cerr << "ERROR copying index buffer data.\n";
-			return 0;
-		}
-	}
-
-	const fx::gltf::BufferView&	vertexBufferView	= object.bufferViews[posAccessor.bufferView];
-	const fx::gltf::Buffer&		vertexBuffer		= object.buffers[vertexBufferView.buffer];
-	
-	// FIXME: This only works when all vertex attributes are in one buffer
-	std::vector<uint8_t> vertexBufferData;
-	vertexBufferData.resize(vertexBuffer.byteLength);
-	{
-		const size_t off = 0;
-		const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
-		if (!memcpy(vertexBufferData.data(), ptr, vertexBuffer.byteLength)) {
-			std::cerr << "ERROR copying vertex buffer data.\n";
-			return 0;
-		}
-	}
-
-	IndexType indexType;
-	switch(indexAccessor.componentType) {
+/** Translate the component type used in the index accessor of fx-gltf to our
+ * enum for index type. The reason we have defined an incompatible enum that
+ * needs translation is that only a subset of component types is valid for
+ * indices and we want to catch these incompatibilities here. */
+enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t)
+{
+	switch (t) {
 	case fx::gltf::Accessor::ComponentType::UnsignedByte:
-		indexType = UINT8; break;
+		return IndexType::UINT8;
 	case fx::gltf::Accessor::ComponentType::UnsignedShort:
-		indexType = UINT16; break;
+		return IndexType::UINT16;
 	case fx::gltf::Accessor::ComponentType::UnsignedInt:
-		indexType = UINT32; break;
+		return IndexType::UINT32;
 	default:
-		std::cerr << "ERROR: Index type not supported: " <<
-			static_cast<uint16_t>(indexAccessor.componentType) <<
-			std::endl;
-		return 0;
+        std::cerr << "ERROR: Index type not supported: " <<
+			static_cast<uint16_t>(t) << std::endl;
+		return IndexType::UNDEFINED;
 	}
+}
 
-	const size_t numVertexGroups = objectMesh.primitives.size();
-	
-	std::vector<VertexGroup> vertexGroups;
-	vertexGroups.reserve(numVertexGroups);
-	
-	vertexGroups.push_back({
-		static_cast<PrimitiveMode>(objectPrimitive.mode),
-		object.accessors[objectPrimitive.indices].count,
-		posAccessor.count,
-		{indexType, indexBufferData},
-		{vertexBufferData, vertexAttributes},
-		{posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
-		{posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
-		static_cast<uint8_t>(objectPrimitive.material)
-	});
-	
-	std::vector<Material> materials;
-
-	mesh = {
-		object.meshes[0].name,
-		vertexGroups,
-		materials,
-		0, 0, 0, NULL
-	};
-
-	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
-	// fail quietly if there is no texture
-	if (object.textures.size()) {
-		const std::string mime_type("image/jpeg");
-		const fx::gltf::Texture &tex = object.textures[0];
-		const fx::gltf::Image &img = object.images[tex.source];
-#ifndef NDEBUG
-		printf("texture name=%s sampler=%u source=%u\n",
-				tex.name.c_str(), tex.sampler, tex.source);
-		printf("image   name=%s uri=%s mime=%s\n", img.name.c_str(),
-				img.uri.c_str(), img.mimeType.c_str());
-#endif
-		
-		size_t pos = path.find_last_of("/");
-		auto dir = path.substr(0, pos);
-		
-		mesh.texture_hack.img = stbi_load((dir + "/" + img.uri).c_str(),
-				&mesh.texture_hack.w, &mesh.texture_hack.h,
-				&mesh.texture_hack.ch, 4);
-	}
-	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
-	return 1;
+/**
+ * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
+ * @param translation possible translation vector (default 0,0,0)
+ * @param scale possible scale vector (default 1,1,1)
+ * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
+ * @param matrix possible modelmatrix (default identity)
+ * @return model Matrix as an array of floats
+ */
+std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
+    std::array<float, 16> modelMatrix = {1,0,0,0,
+                                         0,1,0,0,
+                                         0,0,1,0,
+                                         0,0,0,1};
+    if (matrix != modelMatrix){
+        return matrix;
+    } else {
+        // translation
+        modelMatrix[3] = translation[0];
+        modelMatrix[7] = translation[1];
+        modelMatrix[11] = translation[2];
+        // rotation and scale
+        auto a = rotation[0];
+        auto q1 = rotation[1];
+        auto q2 = rotation[2];
+        auto q3 = rotation[3];
+
+        modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
+        modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
+        modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
+
+        modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
+        modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
+        modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
+
+        modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
+        modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
+        modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
+
+        // flip y, because GLTF uses y up, but vulkan -y up
+        modelMatrix[5] *= -1;
+
+        return modelMatrix;
+    }
+
+}
+
+bool materialHasTexture(const Material *const m, const PBRTextureTarget t)
+{
+	return m->textureMask & bitflag(t);
+}
+
+int loadScene(const std::string &path, Scene &scene){
+    fx::gltf::Document sceneObjects;
+
+    try {
+        if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
+            sceneObjects = fx::gltf::LoadFromBinary(path);
+        } else {
+            sceneObjects = fx::gltf::LoadFromText(path);
+        }
+    } catch (const std::system_error &err) {
+        print_what(err, path);
+        return 0;
+    } catch (const std::exception &e) {
+        print_what(e, path);
+        return 0;
+    }
+    size_t pos = path.find_last_of("/");
+    auto dir = path.substr(0, pos);
+
+    // file has to contain at least one mesh
+    if (sceneObjects.meshes.size() == 0) return 0;
+
+    fx::gltf::Accessor posAccessor;
+    std::vector<VertexAttribute> vertexAttributes;
+    std::vector<Material> materials;
+    std::vector<Texture> textures;
+    std::vector<Sampler> samplers;
+    std::vector<Mesh> meshes;
+    std::vector<VertexGroup> vertexGroups;
+    int groupCount = 0;
+
+    Mesh mesh = {};
+
+    for(int i = 0; i < sceneObjects.meshes.size(); i++){
+        std::vector<int> vertexGroupsIndices;
+        fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
+
+        for(int j = 0; j < objectMesh.primitives.size(); j++){
+            fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j];
+            vertexAttributes.clear();
+            vertexAttributes.reserve(objectPrimitive.attributes.size());
+
+            for (auto const & attrib : objectPrimitive.attributes) {
+
+                fx::gltf::Accessor accessor =  sceneObjects.accessors[attrib.second];
+                VertexAttribute attribute;
+
+                if (attrib.first == "POSITION") {
+                    attribute.type = PrimitiveType::POSITION;
+                    posAccessor = accessor;
+                } else if (attrib.first == "NORMAL") {
+                    attribute.type = PrimitiveType::NORMAL;
+                } else if (attrib.first == "TEXCOORD_0") {
+                    attribute.type = PrimitiveType::TEXCOORD_0;
+                } else if (attrib.first == "TEXCOORD_1") {
+                    attribute.type = PrimitiveType::TEXCOORD_1;
+                } else {
+                    return 0;
+                }
+
+                attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset;
+                attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength;
+                attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride;
+		        attribute.componentType = static_cast<ComponentType>(accessor.componentType);
+
+                if (convertTypeToInt(accessor.type) != 10) {
+                    attribute.componentCount = convertTypeToInt(accessor.type);
+                } else {
+                    return 0;
+                }
+
+                vertexAttributes.push_back(attribute);
+            }
+
+            IndexType indexType;
+            std::vector<uint8_t> indexBufferData = {};
+            if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf
+                const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
+                const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
+                const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
+
+                indexBufferData.resize(indexBufferView.byteLength);
+                {
+                    const size_t off = indexBufferView.byteOffset;
+                    const void *const ptr = ((char*)indexBuffer.data.data()) + off;
+                    if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
+                        vkcv_log(LogLevel::ERROR, "Copying index buffer data");
+                        return 0;
+                    }
+                }
+
+                indexType = getIndexType(indexAccessor.componentType);
+                if (indexType == IndexType::UNDEFINED){
+                    vkcv_log(LogLevel::ERROR, "Index Type undefined.");
+                    return 0;
+                }
+            }
+
+            const fx::gltf::BufferView&	vertexBufferView	= sceneObjects.bufferViews[posAccessor.bufferView];
+            const fx::gltf::Buffer&		vertexBuffer		= sceneObjects.buffers[vertexBufferView.buffer];
+
+            // only copy relevant part of vertex data
+            uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
+            uint32_t relevantBufferEnd = 0;
+            for (const auto &attribute : vertexAttributes) {
+                relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
+                const uint32_t attributeEnd = attribute.offset + attribute.length;
+                relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd);    // TODO: need to incorporate stride?
+            }
+            const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
+
+            // FIXME: This only works when all vertex attributes are in one buffer
+            std::vector<uint8_t> vertexBufferData;
+            vertexBufferData.resize(relevantBufferSize);
+            {
+                const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
+                if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
+                    vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
+                    return 0;
+                }
+            }
+
+            // make vertex attributes relative to copied section
+            for (auto &attribute : vertexAttributes) {
+                attribute.offset -= relevantBufferOffset;
+            }
+
+            const size_t numVertexGroups = objectMesh.primitives.size();
+            vertexGroups.reserve(numVertexGroups);
+
+            vertexGroups.push_back({
+                static_cast<PrimitiveMode>(objectPrimitive.mode),
+                sceneObjects.accessors[objectPrimitive.indices].count,
+                posAccessor.count,
+                {indexType, indexBufferData},
+                {vertexBufferData, vertexAttributes},
+                {posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
+                {posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
+                static_cast<uint8_t>(objectPrimitive.material)
+            });
+
+            vertexGroupsIndices.push_back(groupCount);
+            groupCount++;
+        }
+
+        mesh.name = sceneObjects.meshes[i].name;
+        mesh.vertexGroups = vertexGroupsIndices;
+
+        meshes.push_back(mesh);
+    }
+
+    for(int m = 0; m < sceneObjects.nodes.size(); m++) {
+        meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation,
+                                                                            sceneObjects.nodes[m].scale,
+                                                                            sceneObjects.nodes[m].rotation,
+                                                                            sceneObjects.nodes[m].matrix);
+    }
+
+    if (sceneObjects.textures.size() > 0){
+        textures.reserve(sceneObjects.textures.size());
+
+        for(int k = 0; k < sceneObjects.textures.size(); k++){
+            const fx::gltf::Texture &tex = sceneObjects.textures[k];
+            const fx::gltf::Image &img = sceneObjects.images[tex.source];
+            std::string img_uri = dir + "/" + img.uri;
+            int w, h, c;
+            uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
+            c = 4;	// FIXME hardcoded to always have RGBA channel layout
+            if (!data) {
+                vkcv_log(LogLevel::ERROR, "Loading texture image data.")
+                return 0;
+            }
+            const size_t byteLen = w * h * c;
+
+            std::vector<uint8_t> imgdata;
+            imgdata.resize(byteLen);
+            if (!memcpy(imgdata.data(), data, byteLen)) {
+                vkcv_log(LogLevel::ERROR, "Copying texture image data")
+                free(data);
+                return 0;
+            }
+            free(data);
+
+            textures.push_back({
+                0,
+                static_cast<uint8_t>(c),
+                static_cast<uint16_t>(w),
+                static_cast<uint16_t>(h),
+                imgdata
+            });
+
+        }
+    }
+
+    if (sceneObjects.materials.size() > 0){
+        materials.reserve(sceneObjects.materials.size());
+
+        for (int l = 0; l < sceneObjects.materials.size(); l++){
+            fx::gltf::Material material = sceneObjects.materials[l];
+	    // TODO I think we shouldn't set the index for a texture target if
+	    // it isn't defined. So we need to test first if there is a normal
+	    // texture before assigning material.normalTexture.index.
+	    // About the bitmask: If a normal texture is there, modify the
+	    // materials textureMask like this:
+	    // 		mat.textureMask |= bitflag(asset::normal);
+            materials.push_back({
+               0,
+               material.pbrMetallicRoughness.baseColorTexture.index,
+               material.pbrMetallicRoughness.metallicRoughnessTexture.index,
+               material.normalTexture.index,
+               material.occlusionTexture.index,
+               material.emissiveTexture.index,
+               {
+                   material.pbrMetallicRoughness.baseColorFactor[0],
+                   material.pbrMetallicRoughness.baseColorFactor[1],
+                   material.pbrMetallicRoughness.baseColorFactor[2],
+                   material.pbrMetallicRoughness.baseColorFactor[3]
+               },
+               material.pbrMetallicRoughness.metallicFactor,
+               material.pbrMetallicRoughness.roughnessFactor,
+               material.normalTexture.scale,
+               material.occlusionTexture.strength,
+               {
+                   material.emissiveFactor[0],
+                   material.emissiveFactor[1],
+                   material.emissiveFactor[2]
+               }
+
+            });
+        }
+    }
+
+    scene = {
+            meshes,
+            vertexGroups,
+            materials,
+            textures,
+            samplers
+    };
+
+    return 1;
 }
 
 }
diff --git a/modules/camera/CMakeLists.txt b/modules/camera/CMakeLists.txt
index 28080bf2b1cd3bbc88d6c13d7ef26a43d7c3e19a..60cfca4cf97cef30d989bdab064e20547764041c 100644
--- a/modules/camera/CMakeLists.txt
+++ b/modules/camera/CMakeLists.txt
@@ -11,10 +11,13 @@ set(vkcv_camera_include ${PROJECT_SOURCE_DIR}/include)
 set(vkcv_camera_sources
 		${vkcv_camera_include}/vkcv/camera/Camera.hpp
 		${vkcv_camera_source}/vkcv/camera/Camera.cpp
-		${vkcv_camera_include}/vkcv/camera/TrackballCamera.hpp
-		${vkcv_camera_source}/vkcv/camera/TrackballCamera.cpp
 		${vkcv_camera_include}/vkcv/camera/CameraManager.hpp
 		${vkcv_camera_source}/vkcv/camera/CameraManager.cpp
+		${vkcv_camera_include}/vkcv/camera/CameraController.hpp
+		${vkcv_camera_include}/vkcv/camera/PilotCameraController.hpp
+		${vkcv_camera_source}/vkcv/camera/PilotCameraController.cpp
+		${vkcv_camera_include}/vkcv/camera/TrackballCameraController.hpp
+		${vkcv_camera_source}/vkcv/camera/TrackballCameraController.cpp
 )
 
 # adding source files to the project
@@ -33,3 +36,4 @@ target_include_directories(vkcv_camera SYSTEM BEFORE PRIVATE ${vkcv_camera_inclu
 # add the own include directory for public headers
 target_include_directories(vkcv_camera BEFORE PUBLIC ${vkcv_camera_include} ${vkcv_camera_includes})
 
+target_compile_definitions(vkcv_camera PUBLIC ${vkcv_camera_definitions})
diff --git a/modules/camera/config/GLM.cmake b/modules/camera/config/GLM.cmake
index c4d14392ef0ea24243a45b19cd8583d90d3267be..efd6444451100b912aa0b5b4a532dc8f448b0b40 100644
--- a/modules/camera/config/GLM.cmake
+++ b/modules/camera/config/GLM.cmake
@@ -4,11 +4,17 @@ find_package(glm QUIET)
 if (glm_FOUND)
     list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS})
     list(APPEND vkcv_camera_libraries glm)
+
+    list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
+    list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
 else()
     if (EXISTS "${vkcv_camera_lib_path}/glm")
         add_subdirectory(${vkcv_camera_lib}/glm)
         
         list(APPEND vkcv_camera_libraries glm)
+        
+        list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
+        list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
     else()
         message(WARNING "GLM is required..! Update the submodules!")
     endif ()
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
index 7e177b9a2fbde0890e0c8ea6a1d9a19d6e277c7c..ce32d3f8a0c6ee3e0dd882f24a9ac2d12c14a024 100644
--- a/modules/camera/include/vkcv/camera/Camera.hpp
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -4,98 +4,194 @@
 #include <glm/gtc/matrix_transform.hpp>
 #include <glm/gtc/matrix_access.hpp>
 
-namespace vkcv {
+namespace vkcv::camera {
 
-    class Camera {
+    /**
+     * @brief Used to create a camera which governs the view and projection matrices.
+     */
+    class Camera final {
     protected:
 		glm::mat4 m_view;
 		glm::mat4 m_projection;
 
-		int m_width;
-		int m_height;
-
 		float m_near;
 		float m_far;
-		float m_fov;
-		float m_ratio;
 
-        glm::vec3 m_up;
+		glm::vec3 m_up;
         glm::vec3 m_position;
-        float m_cameraSpeed;
+        glm::vec3 m_center;
+
         float m_pitch;
         float m_yaw;
-
-        int m_fov_nsteps;
-        float m_fov_min;
-        float m_fov_max;
-
-        bool m_forward;
-        bool m_backward;
-        bool m_left;
-        bool m_right;
+	
+		/**
+		 * @brief Sets the view matrix of the camera to @p view
+		 * @param[in] view The view matrix
+		 */
+		void setView(const glm::mat4& view);
+	
+		/**
+		 * @brief Sets the projection matrix of the camera to @p projection
+		 * @param[in] projection The projection matrix
+		 */
+		void setProjection(const glm::mat4& projection);
 
     public:
-        Camera();
 
-        virtual ~Camera();
+        /**
+         * @brief The default constructor of the camera
+         */
+        Camera();
 
+        /**
+         * @brief The destructor of the camera (default behavior)
+         */
+        ~Camera();
+        
+        /**
+         * @brief Sets the perspective object according to @p fov, @p ratio, @p near and @p far. This leads to changes in the projection matrix of the camera
+         * @param[in] fov The desired field of view in radians
+         * @param[in] ratio The aspect ratio
+         * @param[in] near Distance to near clipping plane
+         * @param[in] far Distance to far clipping plane
+         */
         void setPerspective(float fov, float ratio, float near, float far);
 
-        const glm::mat4 getView() const;
-
-        void getView(glm::vec3 &x, glm::vec3 &y, glm::vec3 &z, glm::vec3 &pos);
-
-        glm::mat4 updateView(double deltatime);
-
-        void lookAt(glm::vec3 position, glm::vec3 center, glm::vec3 up);
-
+        /**
+         * @brief Gets the view matrix of the camera
+         * @return The view matrix of the camera
+         */
+        const glm::mat4& getView() const;
+
+        /**
+         * @brief Sets the view matrix of the camera according to @p position, @p center and @p up
+         * @param[in] position The position of the camera
+         * @param[in] center The target position the camera is looking at
+         * @param[in] up The vector that defines which direction is 'up' depending on the camera's orientation
+         */
+        void lookAt(const glm::vec3& position, const glm::vec3& center, const glm::vec3& up);
+
+        /**
+         * @brief Gets the current projection of the camera
+         * @return The current projection matrix
+         */
         const glm::mat4& getProjection() const;
 
-        void setProjection(const glm::mat4 projection);
-
+        /**
+         * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied
+         * @return The model-view-projection matrix
+         */
+        glm::mat4 getMVP() const;
+
+        /**
+         * @brief Gets the near and far bounds of the view frustum of the camera.
+         * @param[out] near The near bound of the view frustum
+         * @param[out] far The far bound of the view frustum
+         */
         void getNearFar(float &near, float &far) const;
 
-        void setUp(const glm::vec3 &Up);
-
+        /**
+         * @brief Gets the current field of view of the camera in radians
+         * @return[in] The current field of view in radians
+         */
         float getFov() const;
 
+        /**
+         * @brief Sets the field of view of the camera to @p fov in radians
+         * @param[in] fov The new field of view in radians
+         */
         void setFov(float fov);
-        
-        void changeFov(double fov);
-
-        void updateRatio(float ratio);
 
+        /**
+         * @brief Gets the current aspect ratio of the camera
+         * @return The current aspect ratio of the camera
+         */
         float getRatio() const;
 
+        /**
+         * @brief Updates the aspect ratio of the camera with @p ratio and, thus, changes the projection matrix
+         * @param[in] ratio The new aspect ratio of the camera
+         */
+        void setRatio(float ratio);
+
+        /**
+         * @brief Sets @p near and @p far as new values for the view frustum of the camera. This leads to changes in the projection matrix according to these two values.
+         * @param[in] near The new near bound of the view frustum
+         * @param[in] far The new far bound of the view frustum
+         */
         void setNearFar(float near, float far);
 
+        /**
+         * @brief Gets the current front vector of the camera in world space
+         * @return The current front vector of the camera
+         */
         glm::vec3 getFront() const;
-
-        glm::vec3 getPosition() const;
-
-        void setPosition( glm::vec3 position );
-
+        
+        /**
+         * @brief Sets the front vector of the camera in world space to @p front
+         * @param[in] front The new front vector of the camera
+         */
+        void setFront(const glm::vec3& front);
+
+        /**
+         * @brief Gets the current position of the camera in world space
+         * @return The current position of the camera in world space
+         */
+        const glm::vec3& getPosition() const;
+
+        /**
+         * @brief Sets the position of the camera to @p position
+         * @param[in] position The new position of the camera
+         */
+        void setPosition( const glm::vec3& position );
+
+        /**
+         * @brief Gets the center point.
+         * @return The center point.
+         */
+        const glm::vec3& getCenter() const;
+
+        /**
+         * @brief Sets @p center as the new center point.
+         * @param[in] center The new center point.
+         */
+        void setCenter(const glm::vec3& center);
+
+        /**
+         * @brief Gets the pitch value of the camera in degrees.
+         * @return The pitch value in degrees.
+         */
         float getPitch() const;
 
+        /**
+         * @brief Sets the pitch value of the camera to @p pitch in degrees.
+         * @param[in] pitch The new pitch value in degrees.
+         */
         void setPitch(float pitch);
 
+        /**
+         * @brief Gets the yaw value of the camera in degrees.
+         * @return The yaw value in degrees.
+         */
         float getYaw() const;
 
+        /**
+         * @brief Sets the yaw value of the camera to @p yaw.
+         * @param[in] yaw The new yaw value in degrees.
+         */
         void setYaw(float yaw);
 
-        void panView( double xOffset, double yOffset );
-
-        void updatePosition(double deltatime);
-
-        void moveForward(int action);
-
-        void moveBackward(int action);
-
-        void moveLeft(int action);
-
-        void moveRight(int action);
-
-
+        /**
+         * @brief Gets the up vector.
+         * @return The up vector.
+         */
+        const glm::vec3& getUp() const;
+
+        /**
+         * @brief Sets @p up as the new up vector.
+         * @param[in] up The new up vector.
+         */
+        void setUp(const glm::vec3 &up);
     };
 
 }
diff --git a/modules/camera/include/vkcv/camera/CameraController.hpp b/modules/camera/include/vkcv/camera/CameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..90fc97401851851194ec89a10757bbfb1453990d
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "Camera.hpp"
+#include "vkcv/Window.hpp"
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used as a base class for defining camera controller classes with different behaviors, e.g. the
+     * #PilotCameraController.
+     */
+    class CameraController {
+
+    public:
+
+        /**
+         * @brief The constructor of the #CameraController (default behavior).
+         */
+        CameraController() = default;
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object.
+         */
+        virtual void updateCamera(double deltaTime, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for key events.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        virtual void keyCallback(int key, int scancode, int action, int mods, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse scrolling events.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        virtual void scrollCallback( double offsetX, double offsetY, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse movement events.
+         * @param[in] x The horizontal mouse position.
+         * @param[in] y The vertical mouse position.
+         * @param[in] camera The camera object.
+         */
+        virtual void mouseMoveCallback(double offsetX, double offsetY, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for mouse button events.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        virtual void mouseButtonCallback(int button, int action, int mods, Camera &camera) = 0;
+
+        /**
+         * @brief A callback function for gamepad input events.
+         * @param gamepadIndex The gamepad index.
+         * @param camera The camera object.
+         * @param frametime The current frametime.
+         */
+        virtual void gamepadCallback(int gamepadIndex, Camera &camera, double frametime) = 0;
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index 9511b752e972afb1e10f41a118433a4e8933fd65..409f9196599be02e4215f3924c1102f0b8c72899 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -1,38 +1,195 @@
 #pragma once
 
-#include "TrackballCamera.hpp"
+#include "PilotCameraController.hpp"
+#include "TrackballCameraController.hpp"
+#include "CameraController.hpp"
 #include "vkcv/Window.hpp"
 #include <GLFW/glfw3.h>
 #include <functional>
 
-namespace vkcv{
+namespace vkcv::camera {
 
+    /**
+     * @brief Used for specifying existing types of camera controllers when adding a new controller object to the
+     * #CameraManager.
+     */
+    enum class ControllerType {
+        NONE,
+        PILOT,
+        TRACKBALL,
+        TRACE
+    };
+
+    /**
+     * @brief Used for managing an arbitrary amount of camera controllers.
+     */
     class CameraManager{
     private:
-        std::function<void(int, int, int, int)> m_keyHandle;
-        std::function<void(double, double)> m_mouseMoveHandle;
-        std::function<void(double, double)> m_mouseScrollHandle;
-        std::function<void(int, int, int)> m_mouseButtonHandle;
-
-        Window &m_window;
-        Camera m_camera;
-        float m_width;
-        float m_height;
-//        std::shared_ptr<vkcv::TrackballCamera> m_trackball;
-
-        bool m_roationActive = false;
-        double m_lastX ;
-        double m_lastY ;
-
-        void bindCamera();
+		event_handle<int, int, int, int> m_keyHandle;
+		event_handle<double, double> m_mouseMoveHandle;
+		event_handle<double, double> m_mouseScrollHandle;
+		event_handle<int, int, int> m_mouseButtonHandle;
+		event_handle<int, int> m_resizeHandle;
+        event_handle<int> m_gamepadHandle;
+
+        Window& m_window;
+        std::vector<Camera> m_cameras;
+        std::vector<ControllerType> m_cameraControllerTypes;
+        uint32_t m_activeCameraIndex;
+
+        PilotCameraController m_pilotController;
+        TrackballCameraController m_trackController;
+
+        double m_lastX;
+        double m_lastY;
+
+        double m_inputDelayTimer;
+        double m_frameTime;
+
+        /**
+         * @brief Binds the camera object to the window event handles.
+         */
+        void bindCameraToEvents();
+
+        /**
+         * @brief A callback function for key events. Currently, cycling between all existing camera controllers via Tab,
+         * window closure via Esc and polling key events from the active camera controller are supported.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         */
         void keyCallback(int key, int scancode, int action, int mods);
-        void scrollCallback( double offsetX, double offsetY);
-        void mouseMoveCallback( double offsetX, double offsetY);
+
+        /**
+         * @brief A callback function for mouse scrolling events.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         */
+        void scrollCallback(double offsetX, double offsetY);
+
+        /**
+         * @brief A callback function for mouse movement events.
+         * @param[in] x The horizontal mouse position.
+         * @param[in] y The vertical mouse position.
+         */
+        void mouseMoveCallback(double x, double y);
+
+        /**
+         * @brief A callback function for mouse button events.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         */
         void mouseButtonCallback(int button, int action, int mods);
 
+        /**
+         * @brief A callback function for handling the window resizing event. Each existing camera is resized in respect
+         * of the window size.
+         * @param[in] width The new width of the window.
+         * @param[in] height The new height of the window.
+         */
+        void resizeCallback(int width, int height);
+
+        /**
+         * @brief A callback function for gamepad input events. Currently, inputs are handled only for the first
+         * connected gamepad!
+         * @param gamepadIndex The gamepad index.
+         */
+        void gamepadCallback(int gamepadIndex);
+	
+		/**
+		 * @brief Gets a camera controller object of specified @p controllerType.
+		 * @param[in] controllerType The type of the camera controller.
+		 * @return The specified camera controller object.
+		 */
+		CameraController& getControllerByType(ControllerType controllerType);
+        
+        /**
+         * @briof A method to get the currently active controller for the active camera.
+         * @return Reference to the active #CameraController
+         */
+        CameraController& getActiveController();
+
     public:
-        CameraManager(Window &window, float width, float height, glm::vec3 up = glm::vec3(0.0f,-1.0f,0.0f), glm::vec3 position = glm::vec3(0.0f,0.0f,0.0f));
 
-        Camera &getCamera();
+        /**
+         * @brief The constructor of the #CameraManager.
+         * @param[in] window The window.
+         */
+        CameraManager(Window &window);
+
+        /**
+         * @brief The destructor of the #CameraManager. Destroying the #CameraManager leads to deletion of all stored
+         * camera objects and camera controller objects.
+         */
+        ~CameraManager();
+
+        /**
+         * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
+         * @p controllerType.
+         * @param[in] controllerType The type of the camera controller.
+         * @return The index of the newly created camera object.
+         */
+		uint32_t addCamera(ControllerType controllerType = ControllerType::NONE);
+	
+		/**
+		 * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
+		 * @p controllerType.
+		 * @param[in] controllerType The type of the camera controller.
+		 * @param[in] camera The new camera object.
+		 * @return The index of the newly bound camera object.
+		 */
+		uint32_t addCamera(ControllerType controllerType, const Camera& camera);
+
+        /**
+         * @brief Gets the stored camera object located at @p cameraIndex.
+         * @param[in] cameraIndex The camera index.
+         * @return The camera object at @p cameraIndex.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        Camera& getCamera(uint32_t cameraIndex);
+
+        /**
+         * @brief Gets the stored camera object set as the active camera.
+         * @return The active camera.
+         */
+        Camera& getActiveCamera();
+
+        /**
+         * @brief Sets the stored camera object located at @p cameraIndex as the active camera.
+         * @param[in] cameraIndex The camera index.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        void setActiveCamera(uint32_t cameraIndex);
+
+        /**
+         * @brief Gets the index of the stored active camera object.
+         * @return The active camera index.
+         */
+        uint32_t getActiveCameraIndex() const;
+
+        /**
+         * @brief Binds a stored camera object located at @p cameraIndex to a camera controller of specified
+         * @p controllerType.
+         * @param[in] cameraIndex The camera index.
+         * @param[in] controllerType The type of the camera controller.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        void setControllerType(uint32_t cameraIndex, ControllerType controllerType);
+
+        /**
+         * @brief Gets the currently bound camera controller type of the stored camera object located at @p cameraIndex.
+         * @param[in] cameraIndex The camera index.
+         * @return The type of the camera controller of the specified camera object.
+         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         */
+        ControllerType getControllerType(uint32_t cameraIndex);
+
+        /**
+         * @brief Updates all stored camera controllers in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         */
+        void update(double deltaTime);
     };
 }
diff --git a/modules/camera/include/vkcv/camera/PilotCameraController.hpp b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2b64cdc0dd3045714aba7b3b7c6241af2337c706
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
@@ -0,0 +1,150 @@
+#pragma once
+
+#include <vkcv/camera/CameraController.hpp>
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used to move around a camera object in world space.
+     */
+    class PilotCameraController final : public CameraController {
+    private:
+        // camera movement indicators
+        bool m_forward;
+        bool m_backward;
+        bool m_upward;
+        bool m_downward;
+        bool m_left;
+        bool m_right;
+
+        float m_gamepadX;
+        float m_gamepadY;
+        float m_gamepadZ;
+
+        bool m_rotationActive;
+
+        float m_cameraSpeed;
+
+        int m_fov_nsteps;
+        float m_fov_min;
+        float m_fov_max;
+
+        /**
+         * @brief Indicates forward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveForward(int action);
+
+        /**
+         * @brief Indicates backward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveBackward(int action);
+
+        /**
+         * @brief Indicates left movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveLeft(int action);
+
+        /**
+         * @brief Indicates right movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveRight(int action);
+
+        /**
+         * @brief Indicates upward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveUpward(int action);
+
+        /**
+         * @brief Indicates downward movement of the camera depending on the performed @p action.
+         * @param[in] action The performed action.
+         */
+        void moveDownward(int action);
+
+    public:
+
+        /**
+         * @brief The default constructor of the #PilotCameraController.
+         */
+        PilotCameraController();
+
+        /**
+         * @brief The destructor of the #PilotCameraController (default behavior).
+         */
+        ~PilotCameraController() = default;
+
+        /**
+         * @brief Changes the field of view of @p camera with an @p offset in degrees.
+         * @param[in] offset The offset in degrees.
+         * @param[in] camera The camera object.
+         */
+        void changeFov(double offset, Camera &camera);
+
+        /**
+         * @brief Pans the view of @p camera according to the pitch and yaw values and additional offsets @p xOffset
+         * and @p yOffset.
+         * @param[in] xOffset The offset added to the yaw value.
+         * @param[in] yOffset The offset added to the pitch value.
+         * @param[in] camera The camera object.
+         */
+        void panView(double xOffset, double yOffset, Camera &camera);
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object.
+         */
+        void updateCamera(double deltaTime, Camera &camera);
+
+        /**
+         * @brief A callback function for key events. Currently, 3D camera movement via W, A, S, D, E, Q are supported.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
+         * of @p camera.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the camera,
+         * if #mouseButtonCallback(int button, int action, int mods) enabled panning.
+         * @param[in] x The horizontal mouse position
+         * @param[in] y The vertical mouse position
+         * @param[in] camera The camera object.
+         */
+        void mouseMoveCallback(double x, double y, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
+         * view of the camera as long as it is pressed.
+         * @param[in] button The mouse button
+         * @param[in] action The button action
+         * @param[in] mods The modifier bits
+         * @param[in] camera The camera object.
+         */
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for gamepad input events.
+         * @param gamepadIndex The gamepad index.
+         * @param camera The camera object.
+         * @param frametime The current frametime.
+         */
+        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime);
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/TrackballCamera.hpp b/modules/camera/include/vkcv/camera/TrackballCamera.hpp
deleted file mode 100644
index c9e269e9f7ad708c68158d5b358efbf37c5bb7a9..0000000000000000000000000000000000000000
--- a/modules/camera/include/vkcv/camera/TrackballCamera.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include "Camera.hpp"
-
-namespace vkcv {
-
-    class TrackballCamera : public vkcv::Camera {
-    public:
-
-        TrackballCamera( int width, int height, glm::mat4 projection);
-
-        TrackballCamera(int width, int height);
-
-        ~TrackballCamera();
-
-        float getSensitivity() const;
-
-        void setSensitivity(float sensitivity);
-
-        float getStepSize() const;
-
-        void setStepSize(float stepSize);
-
-        float getTheta() const;
-
-        void setTheta(float theta);
-
-        float getPhi() const;
-
-        void setPhi(float phi);
-
-        float getRadius() const;
-
-        void setRadius(float radius);
-
-        const glm::vec3& getCenter() const;
-
-        void setCenter(const glm::vec3 &center);
-
-    private:
-        float m_sensitivity;
-        float m_stepSize, m_theta, m_phi, m_radius;
-        glm::vec3 m_center;
-
-        float m_oldX;
-        float m_oldY;
-    };
-
-}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4166bda9f6cb62e4c8f1b650557b00c6ec94b2a1
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "CameraController.hpp"
+
+namespace vkcv::camera {
+
+    /**
+     * @brief Used to orbit a camera around its center point.
+     */
+    class TrackballCameraController final : public CameraController {
+    private:
+        bool m_rotationActive;
+
+        float m_cameraSpeed;
+        float m_scrollSensitivity;
+        float m_radius;
+
+        /**
+         * @brief Updates the current radius of @p camera in respect to the @p offset.
+         * @param[in] offset The offset between the old and new radius.
+         * @param[in] camera The camera object.
+         */
+        void updateRadius(double offset, Camera &camera);
+
+    public:
+
+        /**
+         * @brief The default constructor of the #TrackballCameraController.
+         */
+        TrackballCameraController();
+
+        /**
+         * @brief The destructor of the #TrackballCameraController (default behavior).
+         */
+        ~TrackballCameraController() = default;
+
+        /**
+         * @brief Sets @p radius as the new radius for orbiting around the camera's center point.
+         * @param[in] radius The new radius.
+         */
+        void setRadius(const float radius);
+
+        /**
+         * @brief Pans the view of @p camera according to the pitch and yaw values and additional offsets @p xOffset
+         * and @p yOffset.
+         * @param[in] xOffset The offset added to the yaw value.
+         * @param[in] yOffset The offset added to the pitch value.
+         * @param[in] camera The camera object.
+         */
+        void panView(double xOffset, double yOffset, Camera &camera);
+
+        /**
+         * @brief Updates @p camera in respect to @p deltaTime.
+         * @param[in] deltaTime The time that has passed since last update.
+         * @param[in] camera The camera object
+         */
+        void updateCamera(double deltaTime, Camera &camera);
+
+        /**
+         * @brief A callback function for key events. Currently, the trackball camera does not support camera movement.
+         * It can only orbit around its center point.
+         * @param[in] key The keyboard key.
+         * @param[in] scancode The platform-specific scancode.
+         * @param[in] action The key action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
+         * of the camera object.
+         * @param[in] offsetX The offset in horizontal direction.
+         * @param[in] offsetY The offset in vertical direction.
+         * @param[in] camera The camera object.
+         */
+        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the
+         * camera, if #mouseButtonCallback(int button, int action, int mods) enabled panning.
+         * @param[in] xoffset The horizontal mouse position.
+         * @param[in] yoffset The vertical mouse position.
+         * @param[in] camera The camera object.
+         */
+        void mouseMoveCallback(double xoffset, double yoffset, Camera &camera);
+
+        /**
+         * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
+         * view of the camera as long as it is pressed.
+         * @param[in] button The mouse button.
+         * @param[in] action The button action.
+         * @param[in] mods The modifier bits.
+         * @param[in] camera The camera object.
+         */
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+
+        /**
+         * @brief A callback function for gamepad input events.
+         * @param gamepadIndex The gamepad index.
+         * @param camera The camera object.
+         * @param frametime The current frametime.
+         */
+        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime);
+    };
+
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp
index bc8a8498e67a6bd751f5a6ed1d4c4fba0279a68d..18bf94463a0e2c4cb7d64526f4c30835cb451eb2 100644
--- a/modules/camera/src/vkcv/camera/Camera.cpp
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -1,44 +1,27 @@
 #include "vkcv/camera/Camera.hpp"
-#include <iostream>
 
-namespace vkcv {
+#include <math.h>
 
-    Camera::Camera(){
-        m_up = glm::vec3(0.0f, -1.0f, 0.0f);
-        m_position = glm::vec3(0.0f, 0.0f, 0.0f);
-        m_cameraSpeed = 2.f;
-        // front
-        m_pitch = 0.0;
-        m_yaw = 180.0;
+namespace vkcv::camera {
 
-        m_fov_nsteps = 100;
-        m_fov_min = 10;
-        m_fov_max = 120;
-
-        m_forward = false;
-        m_backward = false;
-        m_left = false;
-        m_right = false;
+    Camera::Camera() {
+        lookAt(
+			glm::vec3(0.0f, 0.0f, -1.0f),
+			glm::vec3(0.0f, 0.0f, 0.0f),
+			glm::vec3(0.0f, 1.0f, 0.0f)
+		);
+  
+		setFront(glm::normalize(m_center - m_position));
     }
 
     Camera::~Camera() = default;
 
-    void Camera::lookAt(glm::vec3 position, glm::vec3 center, glm::vec3 up){
-        m_view = glm::lookAt(position, center, up);
-    }
-
-    glm::mat4 Camera::updateView(double deltatime){
-        updatePosition(deltatime);
-        return m_view = glm::lookAt(m_position, m_position + getFront() , m_up);
-    }
-
-    void Camera::getView(glm::vec3 &x, glm::vec3 &y, glm::vec3 &z, glm::vec3 &pos){
-        x = glm::vec3( glm::row(m_view, 0));
-        y = glm::vec3( glm::row(m_view, 1));
-        z = glm::vec3( glm::row(m_view, 2));
-        pos = glm::vec3( glm::column(m_view, 3));
-        glm::mat3 mat_inv = glm::inverse( glm::mat3(m_view));
-        pos = -mat_inv * pos;
+    void Camera::lookAt(const glm::vec3& position, const glm::vec3& center, const glm::vec3& up) {
+    	m_position = position;
+    	m_center = center;
+    	m_up = up;
+    	
+		setView(glm::lookAt(position, center, up));
     }
 
     void Camera::getNearFar( float &near, float &far) const {
@@ -46,96 +29,111 @@ namespace vkcv {
         far = m_far;
     }
 
-
-    const glm::mat4 Camera::getView() const {
+    const glm::mat4& Camera::getView() const {
         return m_view;
     }
+    
+    void Camera::setView(const glm::mat4 &view) {
+		m_view = view;
+	}
 
     const glm::mat4& Camera::getProjection() const {
         return m_projection;
     }
 
-    void Camera::setProjection(const glm::mat4 projection){
-        m_projection = projection;
+    void Camera::setProjection(const glm::mat4& projection) {
+        m_projection =  projection;
     }
 
-    float Camera::getFov() const {
-        return m_fov;
+    glm::mat4 Camera::getMVP() const {
+		const glm::mat4 y_correction (
+				1.0f,  0.0f,  0.0f,  0.0f,
+				0.0f, -1.0f,  0.0f,  0.0f,
+				0.0f,  0.0f,  1.0f,  0.0f,
+				0.0f,  0.0f,  0.0f,  1.0f
+		);
+    	
+        return y_correction * m_projection * m_view;
     }
 
-    void Camera::setFov( float fov){
-        m_fov = fov;
-        setPerspective( m_fov, m_ratio, m_near, m_far);
+    float Camera::getFov() const {
+    	const float tanHalfFovy = 1.0f / m_projection[1][1];
+    	float halfFovy = std::atan(tanHalfFovy);
+    	
+    	if (halfFovy < 0) {
+    		halfFovy += static_cast<float>(M_PI);
+    	}
+    	
+        return halfFovy * 2.0f;
     }
 
-    void Camera::changeFov(double offset){
-        float fov = m_fov;
-        float fov_range = m_fov_max - m_fov_min;
-        float fov_stepsize = glm::radians(fov_range)/m_fov_nsteps;
-        fov -= (float) offset*fov_stepsize;
-        if (fov < glm::radians(m_fov_min)) {
-            fov = glm::radians(m_fov_min);
-        }
-        if (fov > glm::radians(m_fov_max)) {
-            fov = glm::radians(m_fov_max);
-        }
-        setFov(fov);
+    void Camera::setFov( float fov){
+        setPerspective(fov, getRatio(), m_near, m_far);
     }
 
-    void Camera::updateRatio( float ratio){
-        m_ratio = ratio;
-        setPerspective( m_fov, m_ratio, m_near, m_far);
+    float Camera::getRatio() const {
+    	const float aspectProduct = 1.0f / m_projection[0][0];
+		const float tanHalfFovy = 1.0f / m_projection[1][1];
+		
+        return aspectProduct / tanHalfFovy;
     }
 
-    float Camera::getRatio() const {
-        return 0.0f;
+    void Camera::setRatio(float ratio){
+        setPerspective( getFov(), ratio, m_near, m_far);
     }
 
-    void Camera::setNearFar( float near, float far){
-        m_near = near;
-        m_far = far;
-        setPerspective(m_fov, m_ratio, m_near, m_far);
+    void Camera::setNearFar(float near, float far){
+        setPerspective(getFov(), getRatio(), near, far);
     }
 
-    void Camera::setPerspective(float fov, float ratio, float near, float far){
-        m_fov = fov;
-        m_ratio = ratio;
-        m_near = near;
-        m_far = far;
-        m_projection = glm::perspective( m_fov, ratio, m_near, m_far);
+    void Camera::setPerspective(float fov, float ratio, float near, float far) {
+		m_near = near;
+		m_far = far;
+		setProjection(glm::perspective(fov, ratio, near, far));
     }
 
     glm::vec3 Camera::getFront() const {
         glm::vec3 direction;
-        direction.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
-        direction.y = sin(glm::radians(m_pitch));
-        direction.z = cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
+        direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
+        direction.y = std::sin(glm::radians(m_pitch));
+        direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
         return glm::normalize(direction);
     }
+    
+    void Camera::setFront(const glm::vec3 &front) {
+		m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z));
+		m_yaw = std::atan2(front.x, front.z);
+    }
 
-    glm::vec3 Camera::getPosition() const {
+    const glm::vec3& Camera::getPosition() const {
         return m_position;
     }
 
-    void Camera::setPosition( glm::vec3 position ){
-        m_position = position;
+    void Camera::setPosition( const glm::vec3& position ){
+		lookAt(position, m_center, m_up);
     }
 
-    void Camera::setUp(const glm::vec3 &up) {
-        m_up = up;
+    const glm::vec3& Camera::getCenter() const {
+        return m_center;
     }
 
+    void Camera::setCenter(const glm::vec3& center) {
+		lookAt(m_position, center, m_up);
+    }
+	
+	const glm::vec3& Camera::getUp() const {
+		return m_up;
+	}
+	
+	void Camera::setUp(const glm::vec3 &up) {
+		lookAt(m_position, m_center, up);
+	}
+
     float Camera::getPitch() const {
         return m_pitch;
     }
 
     void Camera::setPitch(float pitch) {
-        if (pitch > 89.0f) {
-            pitch = 89.0f;
-        }
-        if (pitch < -89.0f) {
-            pitch = -89.0f;
-        }
         m_pitch = pitch;
     }
 
@@ -147,31 +145,4 @@ namespace vkcv {
         m_yaw = yaw;
     }
 
-    void Camera::panView(double xOffset, double yOffset) {
-        m_yaw += xOffset;
-        m_pitch += yOffset;
-    }
-
-    void Camera::updatePosition(double deltatime ){
-        m_position += (m_cameraSpeed * getFront() * static_cast<float> (m_forward) * static_cast<float>(deltatime));
-        m_position -= (m_cameraSpeed * getFront() * static_cast<float> (m_backward) * static_cast<float>(deltatime));
-        m_position -= (glm::normalize(glm::cross(getFront(), m_up)) * m_cameraSpeed * static_cast<float> (m_left) * static_cast<float>(deltatime));
-        m_position += (glm::normalize(glm::cross(getFront(), m_up)) * m_cameraSpeed * static_cast<float> (m_right) * static_cast<float>(deltatime));
-    }
-
-    void Camera::moveForward(int action){
-        m_forward = static_cast<bool>(action);
-    }
-
-    void Camera::moveBackward(int action){
-        m_backward = static_cast<bool>(action);
-    }
-
-    void Camera::moveLeft(int action){
-        m_left = static_cast<bool>(action);
-    }
-
-    void Camera::moveRight(int action){
-        m_right = static_cast<bool>(action);
-    }
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
index 18f499a2b34b64c1442c5d9e267d6476b8d69199..f129f3a248325957cb56470e2547a0146bc7c971 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -1,82 +1,194 @@
-#include <iostream>
+
 #include "vkcv/camera/CameraManager.hpp"
+#include <vkcv/Logger.hpp>
 
-namespace vkcv{
+namespace vkcv::camera {
 
-    CameraManager::CameraManager(Window &window, float width, float height, glm::vec3 up, glm::vec3 position):
-    m_window(window), m_width(width), m_height(height)
+    CameraManager::CameraManager(Window& window)
+    : m_window(window)
     {
-        m_camera.setUp(up);
-        m_camera.setPosition(position);
-        m_camera.setPerspective( glm::radians(60.0f), m_width / m_height, 0.1f, 10.f);
-        m_lastX = width/2.0;
-        m_lastY = height/2.0;
-        bindCamera();
+        bindCameraToEvents();
+        m_activeCameraIndex = 0;
+        m_lastX = static_cast<float>(window.getWidth()) / 2.0f;
+        m_lastY = static_cast<float>(window.getHeight()) / 2.0f;
+        m_inputDelayTimer = glfwGetTime() + 0.2;
+        m_frameTime = 0;
+    }
+
+    CameraManager::~CameraManager() {
+    	m_window.e_key.remove(m_keyHandle);
+		m_window.e_mouseMove.remove(m_mouseMoveHandle);
+		m_window.e_mouseScroll.remove(m_mouseScrollHandle);
+		m_window.e_mouseButton.remove(m_mouseButtonHandle);
+		m_window.e_resize.remove(m_resizeHandle);
+		m_window.e_gamepad.remove(m_gamepadHandle);
     }
 
-    void CameraManager::bindCamera(){
+    void CameraManager::bindCameraToEvents() {
         m_keyHandle = m_window.e_key.add( [&](int key, int scancode, int action, int mods) { this->keyCallback(key, scancode, action, mods); });
         m_mouseMoveHandle = m_window.e_mouseMove.add( [&]( double offsetX, double offsetY) {this->mouseMoveCallback( offsetX, offsetY);} );
         m_mouseScrollHandle =  m_window.e_mouseScroll.add([&](double offsetX, double offsetY) {this->scrollCallback( offsetX, offsetY);} );
         m_mouseButtonHandle = m_window.e_mouseButton.add([&] (int button, int action, int mods) {this->mouseButtonCallback( button,  action,  mods);});
+        m_resizeHandle = m_window.e_resize.add([&](int width, int height) {this->resizeCallback(width, height);});
+        m_gamepadHandle = m_window.e_gamepad.add([&](int gamepadIndex) {this->gamepadCallback(gamepadIndex);});
+    }
+
+    void CameraManager::resizeCallback(int width, int height) {
+        if (glfwGetWindowAttrib(m_window.getWindow(), GLFW_ICONIFIED) == GLFW_FALSE) {
+            for (size_t i = 0; i < m_cameras.size(); i++) {
+                getCamera(i).setRatio(static_cast<float>(width) / static_cast<float>(height));;
+            }
+        }
     }
 
     void CameraManager::mouseButtonCallback(int button, int action, int mods){
-        if(button == GLFW_MOUSE_BUTTON_2 && m_roationActive == false && action == GLFW_PRESS){
+        if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_PRESS){
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-            m_roationActive = true;
-        }else if(button == GLFW_MOUSE_BUTTON_2 && m_roationActive == true && action == GLFW_RELEASE){
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_RELEASE){
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
-            m_roationActive = false;
         }
+		getActiveController().mouseButtonCallback(button, action, mods, getActiveCamera());
     }
 
     void CameraManager::mouseMoveCallback(double x, double y){
-
-        float xoffset = x - m_lastX;
-        float yoffset = m_lastY - y;
+        auto xoffset = static_cast<float>(x - m_lastX);
+		auto yoffset = static_cast<float>(y - m_lastY);
         m_lastX = x;
         m_lastY = y;
+		getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
+    }
+
+    void CameraManager::scrollCallback(double offsetX, double offsetY) {
+		getActiveController().scrollCallback(offsetX, offsetY, getActiveCamera());
+    }
 
-        if(!m_roationActive){
-            return;
+    void CameraManager::keyCallback(int key, int scancode, int action, int mods)  {
+        switch (action) {
+            case GLFW_RELEASE:
+                switch (key) {
+                    case GLFW_KEY_TAB:
+                        if (m_activeCameraIndex + 1 == m_cameras.size()) {
+                            m_activeCameraIndex = 0;
+                        }
+                        else {
+                            m_activeCameraIndex++;
+                        }
+                        return;
+                    case GLFW_KEY_ESCAPE:
+                        glfwSetWindowShouldClose(m_window.getWindow(), 1);
+                        return;
+					default:
+						break;
+                }
+            default:
+				getActiveController().keyCallback(key, scancode, action, mods, getActiveCamera());
+                break;
         }
+    }
+
+    void CameraManager::gamepadCallback(int gamepadIndex) {
+        // handle camera switching
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
 
-        float sensitivity = 0.05f;
-        xoffset *= sensitivity;
-        yoffset *= sensitivity;
+        double time = glfwGetTime();
+        if (time - m_inputDelayTimer > 0.2) {
+            int switchDirection = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT];
+            m_activeCameraIndex += switchDirection;
+            if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
+                m_activeCameraIndex = 0;
+            }
+            else if (std::less<int>{}(m_activeCameraIndex, 0)) {
+                m_activeCameraIndex = m_cameras.size() - 1;
+            }
+            uint32_t triggered = abs(switchDirection);
+            m_inputDelayTimer = (1-triggered)*m_inputDelayTimer + triggered * time; // Only reset timer, if dpad was pressed - is this cheaper than if-clause?
+        }
 
-        m_camera.panView( xoffset , yoffset );
+        getActiveController().gamepadCallback(gamepadIndex, getActiveCamera(), m_frameTime);     // handle camera rotation, translation
     }
 
-    void CameraManager::scrollCallback(double offsetX, double offsetY) {
-        m_camera.changeFov(offsetY);
+    CameraController& CameraManager::getActiveController() {
+    	const ControllerType type = getControllerType(getActiveCameraIndex());
+    	return getControllerByType(type);
+    }
+	
+	uint32_t CameraManager::addCamera(ControllerType controllerType) {
+    	const float ratio = static_cast<float>(m_window.getWidth()) / static_cast<float>(m_window.getHeight());
+    	
+        Camera camera;
+        camera.setPerspective(glm::radians(60.0f), ratio, 0.1f, 10.0f);
+        return addCamera(controllerType, camera);
+    }
+    
+    uint32_t CameraManager::addCamera(ControllerType controllerType, const Camera &camera) {
+    	const uint32_t index = static_cast<uint32_t>(m_cameras.size());
+    	m_cameras.push_back(camera);
+		m_cameraControllerTypes.push_back(controllerType);
+		return index;
     }
 
-    void CameraManager::keyCallback(int key, int scancode, int action, int mods) {
+    Camera& CameraManager::getCamera(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+        	vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+        	return getActiveCamera();
+        }
+        
+        return m_cameras[cameraIndex];
+    }
 
-        switch (key) {
-            case GLFW_KEY_W:
-                m_camera.moveForward(action);
-                break;
-            case GLFW_KEY_S:
-                m_camera.moveBackward(action);
-                break;
-            case GLFW_KEY_A:
-                m_camera.moveLeft(action);
-                break;
-            case GLFW_KEY_D:
-                m_camera.moveRight(action);
-                break;
-            case GLFW_KEY_ESCAPE:
-                glfwSetWindowShouldClose(m_window.getWindow(), 1);
-                break;
-            default:
-                break;
+    Camera& CameraManager::getActiveCamera() {
+        return m_cameras[getActiveCameraIndex()];
+    }
+
+    void CameraManager::setActiveCamera(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return;
         }
+        
+        m_activeCameraIndex = cameraIndex;
     }
-    Camera &CameraManager::getCamera(){
-        return m_camera;
+
+    uint32_t CameraManager::getActiveCameraIndex() const {
+        return m_activeCameraIndex;
     }
 
-}
\ No newline at end of file
+    void CameraManager::setControllerType(uint32_t cameraIndex, ControllerType controllerType) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return;
+        }
+        
+        m_cameraControllerTypes[cameraIndex] = controllerType;
+    }
+
+    ControllerType CameraManager::getControllerType(uint32_t cameraIndex) {
+        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
+			return ControllerType::NONE;
+        }
+        
+        return m_cameraControllerTypes[cameraIndex];
+    }
+
+    CameraController& CameraManager::getControllerByType(ControllerType controllerType) {
+        switch(controllerType) {
+            case ControllerType::PILOT:
+                return m_pilotController;
+            case ControllerType::TRACKBALL:
+                return m_trackController;
+            default:
+                return m_pilotController;
+        }
+    }
+
+    void CameraManager::update(double deltaTime) {
+        m_frameTime = deltaTime;
+        if (glfwGetWindowAttrib(m_window.getWindow(), GLFW_FOCUSED) == GLFW_TRUE) {
+            getActiveController().updateCamera(deltaTime, getActiveCamera());
+        }
+	}
+	
+}
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5460858ab48d81252787b3c0141dd72982faca7d
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -0,0 +1,191 @@
+#include "vkcv/camera/PilotCameraController.hpp"
+#include <GLFW/glfw3.h>
+
+namespace vkcv::camera {
+
+    PilotCameraController::PilotCameraController() {
+        m_forward = false;
+        m_backward = false;
+        m_upward = false;
+        m_downward = false;
+        m_left = false;
+        m_right = false;
+
+        m_gamepadX = 0.0f;
+        m_gamepadY = 0.0f;
+        m_gamepadZ = 0.0f;
+
+        m_rotationActive = false;
+
+        m_cameraSpeed = 2.5f;
+
+        m_fov_nsteps = 100;
+        m_fov_min = 10;
+        m_fov_max = 120;
+    }
+
+    void PilotCameraController::changeFov(double offset, Camera &camera){
+        // update only if there is (valid) input
+        if (offset == 0.0) {
+            return;
+        }
+
+        float fov = camera.getFov();
+        float fov_range = m_fov_max - m_fov_min;
+        float fov_stepsize = glm::radians(fov_range) / static_cast<float>(m_fov_nsteps);
+        fov -= (float) offset*fov_stepsize;
+        if (fov < glm::radians(m_fov_min)) {
+            fov = glm::radians(m_fov_min);
+        }
+        if (fov > glm::radians(m_fov_max)) {
+            fov = glm::radians(m_fov_max);
+        }
+        camera.setFov(fov);
+    }
+
+    void PilotCameraController::panView(double xOffset, double yOffset, Camera &camera) {
+        // update only if there is (valid) input
+        if (xOffset == 0.0 && yOffset == 0.0) {
+            return;
+        }
+
+        // handle yaw rotation
+        float yaw = camera.getYaw() + static_cast<float>(xOffset);
+        yaw += 360.0f * (yaw < -180.0f) - 360.0f * (yaw > 180.0f);
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() - static_cast<float>(yOffset);
+        pitch = glm::clamp(pitch, -89.0f, 89.0f);
+        camera.setPitch(pitch);
+    }
+    
+    constexpr float getDirectionFactor(bool positive, bool negative) {
+    	return static_cast<float>(positive) - static_cast<float>(negative);
+    }
+
+    void PilotCameraController::updateCamera(double deltaTime, Camera &camera) {
+		glm::vec3 position = camera.getPosition();
+	
+		const glm::vec3 front = camera.getFront();
+		const glm::vec3 up = camera.getUp();
+		const glm::vec3 left = glm::normalize(glm::cross(front, up));
+	
+		const float distance = m_cameraSpeed * static_cast<float>(deltaTime);
+	
+		position += distance * (getDirectionFactor(m_forward, m_backward) + m_gamepadZ) * front;
+		position += distance * (getDirectionFactor(m_left, m_right) + m_gamepadX) * left;
+		position += distance * (getDirectionFactor(m_upward, m_downward) + m_gamepadY) * up;
+	
+		camera.lookAt(position, position + front, up);
+    }
+
+    void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {
+        switch (key) {
+            case GLFW_KEY_W:
+                moveForward(action);
+                break;
+            case GLFW_KEY_S:
+                moveBackward(action);
+                break;
+            case GLFW_KEY_A:
+                moveLeft(action);
+                break;
+            case GLFW_KEY_D:
+                moveRight(action);
+                break;
+            case GLFW_KEY_E:
+                moveUpward(action);
+                break;
+            case GLFW_KEY_Q:
+                moveDownward(action);
+                break;
+            default:
+                break;
+        }
+    }
+
+    void PilotCameraController::scrollCallback(double offsetX, double offsetY, Camera &camera) {
+        changeFov(offsetY, camera);
+    }
+
+    void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
+        if(!m_rotationActive){
+            return;
+        }
+
+        float sensitivity = 0.05f;
+        xoffset *= sensitivity;
+        yoffset *= sensitivity;
+
+        panView(xoffset , yoffset, camera);
+    }
+
+    void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
+        if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){
+            m_rotationActive = true;
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){
+            m_rotationActive = false;
+        }
+    }
+
+    void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) {
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
+
+        float sensitivity = 100.0f;
+        double threshold = 0.1;
+
+        // handle rotations
+        double stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
+        double stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+
+        double rightXVal = glm::clamp(std::abs(stickRightX) - threshold, 0.0, 1.0)
+                * copysign(1.0, stickRightX) * sensitivity * frametime;
+        double rightYVal = glm::clamp(std::abs(stickRightY) - threshold, 0.0, 1.0)
+                * copysign(1.0, stickRightY) * sensitivity * frametime;
+        panView(rightXVal, rightYVal, camera);
+
+        // handle zooming
+        double zoom = static_cast<double>((gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]
+                - gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER])
+                * sensitivity * frametime);
+        changeFov(zoom, camera);
+
+        // handle translation
+        m_gamepadY = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER];
+        float stickLeftX = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X];
+        float stickLeftY = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y];
+        m_gamepadZ = glm::clamp(std::abs(stickLeftY) - threshold, 0.0, 1.0)
+                     * -copysign(1.0, stickLeftY);
+        m_gamepadX = glm::clamp(std::abs(stickLeftX) - threshold, 0.0, 1.0)
+                     * -copysign(1.0, stickLeftX);
+    }
+
+
+    void PilotCameraController::moveForward(int action){
+        m_forward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveBackward(int action){
+        m_backward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveLeft(int action){
+        m_left = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveRight(int action){
+        m_right = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveUpward(int action){
+        m_upward = static_cast<bool>(action);
+    }
+
+    void PilotCameraController::moveDownward(int action){
+        m_downward = static_cast<bool>(action);
+    }
+
+}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/TrackballCamera.cpp b/modules/camera/src/vkcv/camera/TrackballCamera.cpp
deleted file mode 100644
index 3bbb8611dd234499fb9ba08ba87009c8c76660f6..0000000000000000000000000000000000000000
--- a/modules/camera/src/vkcv/camera/TrackballCamera.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-#include "vkcv/camera/TrackballCamera.hpp"
-
-namespace vkcv{
-
-    TrackballCamera::TrackballCamera( int width, int height, glm::mat4 projection)
-    {
-        setPosition( glm::vec3(0.0f, 0.0f, 5.0) );
-        m_center = glm::vec3( 0.0f, 0.0f, 0.0f);
-        m_up = glm::vec3(0.0f, 1.0f, 0.0f);
-
-        m_width = width;
-        m_height = height;
-
-        m_sensitivity = 0.010f;
-        m_stepSize = 0.1f;
-        m_theta = glm::pi<float>() / 2.0f;
-        m_phi = 0.f;
-        m_radius = 1.5;
-
-        m_view = glm::lookAt( m_center + getPosition(), m_center, m_up);
-
-        m_oldX = width/2.f;
-        m_oldY = height/2.f;
-
-        setProjection(projection);
-    }
-
-
-    TrackballCamera::TrackballCamera(int width, int height)
-    {
-        setPosition(    glm::vec3(0.0f, 0.0f, 5.0));
-        m_center = glm::vec3( 0.0f, 0.0f, 0.0f);
-        m_up = glm::vec3(0.0f, 1.0f, 0.0f);
-
-        m_width = width;
-        m_height = height;
-
-        m_sensitivity = 0.010f;
-        m_stepSize = 0.1f;
-        m_theta = glm::pi<float>() / 2.0f;
-        m_phi = 0.f;
-        m_radius = 1.5;
-
-        m_view = glm::lookAt( m_center + getPosition(), m_center, m_up);
-
-        m_oldX = width/2.f;
-        m_oldY = height/2.f;
-
-        m_fov = glm::radians(60.f);
-        m_ratio = m_width / (float) m_height;
-        m_near = 0.001f;
-        m_far = 10.f;
-        glm::mat4 projection = glm::perspective(m_fov, m_ratio, m_near, m_far);
-
-        setProjection(projection);
-    }
-
-    TrackballCamera::~TrackballCamera()
-    {
-    }
-
-	// TODO: Can be done as events... (mouseMove, mouseDown, mouseUp)
-    /*void TrackballCamera::update( GLFWwindow* window) {
-
-        double x, y;
-
-        glfwGetCursorPos( window, &x, &y);
-        if (glfwGetMouseButton( window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
-        {
-            float changeX = ((float) x - m_oldX) * m_sensitivity;
-            float changeY = ((float) y - m_oldY) * m_sensitivity;
-
-            m_theta -= changeY;
-            if (m_theta < 0.01f) m_theta = 0.01f;
-            else if (m_theta > glm::pi<float>() - 0.01f) m_theta = glm::pi<float>() - 0.01f;
-
-            m_phi -= changeX;
-            if (m_phi < 0) m_phi += 2*glm::pi<float>();
-            else if (m_phi > 2*glm::pi<float>()) m_phi -= 2*glm::pi<float>();
-        }
-
-        m_oldX = (float) x;
-        m_oldY = (float) y;
-
-        if (glfwGetKey( window, GLFW_KEY_UP) == GLFW_PRESS)
-            m_radius -= m_stepSize;
-        if (glfwGetKey( window, GLFW_KEY_DOWN) == GLFW_PRESS)
-            m_radius += m_stepSize;
-        if (m_radius < 0.1f) m_radius = 0.1f;
-
-        m_position.x = m_center.x + m_radius * sin(m_theta) * sin(m_phi);
-        m_position.y = m_center.y + m_radius * cos(m_theta);
-        m_position.z = m_center.z + m_radius * sin(m_theta) * cos(m_phi);
-
-        m_view = glm::lookAt( m_position, m_center, m_up);
-
-    }*/
-
-    float TrackballCamera::getSensitivity() const {
-        return m_sensitivity;
-    }
-
-    void TrackballCamera::setSensitivity(float sensitivity) {
-        m_sensitivity = sensitivity;
-    }
-
-    float TrackballCamera::getStepSize() const {
-        return m_stepSize;
-    }
-
-    void TrackballCamera::setStepSize(float stepSize) {
-        m_stepSize = stepSize;
-    }
-
-    float TrackballCamera::getTheta() const {
-        return m_theta;
-    }
-
-    void TrackballCamera::setTheta(float theta) {
-        m_theta = theta;
-    }
-
-    float TrackballCamera::getPhi() const {
-        return m_phi;
-    }
-
-    void TrackballCamera::setPhi(float phi) {
-        m_phi = phi;
-    }
-
-    float TrackballCamera::getRadius() const {
-        return m_radius;
-    }
-
-    void TrackballCamera::setRadius(float radius) {
-        m_radius = radius;
-    }
-
-    const glm::vec3& TrackballCamera::getCenter() const {
-        return m_center;
-    }
-
-    void TrackballCamera::setCenter(const glm::vec3 &center) {
-        m_center = center;
-    }
-}
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cdd66cdb7fdd650d5112fe7bb4738f1fcded7783
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -0,0 +1,118 @@
+#include "vkcv/camera/TrackballCameraController.hpp"
+#include <GLFW/glfw3.h>
+
+namespace vkcv::camera {
+
+    TrackballCameraController::TrackballCameraController() {
+        m_rotationActive = false;
+        m_radius = 3.0f;
+        m_cameraSpeed = 2.5f;
+        m_scrollSensitivity = 0.2f;
+    }
+
+    void TrackballCameraController::setRadius(const float radius) {
+        m_radius = 0.1f * (radius < 0.1f) + radius * (1 - (radius < 0.1f));
+    }
+
+    void TrackballCameraController::panView(double xOffset, double yOffset, Camera &camera) {
+        // update only if there is (valid) input
+        if (xOffset == 0.0 && yOffset == 0.0) {
+            return;
+        }
+
+        // handle yaw rotation
+        float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed;
+        yaw += 360.0f * (yaw < 0.0f) - 360.0f * (yaw > 360.0f);
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() + static_cast<float>(yOffset) * m_cameraSpeed;
+        pitch += 360.0f * (pitch < 0.0f) - 360.0f * (pitch > 360.0f);
+        camera.setPitch(pitch);
+    }
+
+    void TrackballCameraController::updateRadius(double offset, Camera &camera) {
+        // update only if there is (valid) input
+        if (offset == 0.0) {
+            return;
+        }
+
+        glm::vec3 cameraPosition = camera.getPosition();
+        glm::vec3 cameraCenter = camera.getCenter();
+        float radius = glm::length(cameraCenter - cameraPosition);  // get current camera radius
+        setRadius(radius - static_cast<float>(offset) * m_scrollSensitivity);
+    }
+
+    void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) {
+		float yaw = camera.getYaw();
+		float pitch = camera.getPitch();
+		
+		const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
+		const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
+	
+		const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis);
+		const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis);
+		const glm::vec3 translation = glm::vec3(
+				rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f)
+		);
+		
+		const glm::vec3 center = camera.getCenter();
+		const glm::vec3 position = center + translation;
+		const glm::vec3 up = glm::vec3(
+				rotationX * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)
+		);
+		
+		camera.lookAt(position, center, up);
+    }
+
+    void TrackballCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {}
+
+    void TrackballCameraController::scrollCallback(double offsetX, double offsetY, Camera &camera) {
+        updateRadius(offsetY, camera);
+    }
+
+    void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
+        if(!m_rotationActive){
+            return;
+        }
+
+        float sensitivity = 0.025f;
+        xoffset *= sensitivity;
+        yoffset *= sensitivity;
+
+        panView(xoffset , yoffset, camera);
+    }
+
+    void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
+        if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){
+            m_rotationActive = true;
+        }
+        else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){
+            m_rotationActive = false;
+        }
+    }
+
+    void TrackballCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) {
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
+
+        float sensitivity = 100.0f;
+        double threshold = 0.1;
+
+        // handle rotations
+        double stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
+        double stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+        
+        double rightXVal = glm::clamp((abs(stickRightX)-threshold), 0.0, 1.0)
+                * std::copysign(1.0, stickRightX) * sensitivity * frametime;
+        double rightYVal = glm::clamp((abs(stickRightY)-threshold), 0.0, 1.0)
+                * std::copysign(1.0, stickRightY) * sensitivity * frametime;
+        panView(rightXVal, rightYVal, camera);
+
+        // handle translation
+        double stickLeftY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
+        double leftYVal = glm::clamp((abs(stickLeftY)-threshold), 0.0, 1.0)
+                * std::copysign(1.0, stickLeftY) * sensitivity * frametime;
+        updateRadius(-leftYVal, camera);
+    }
+}
\ No newline at end of file
diff --git a/modules/gui/CMakeLists.txt b/modules/gui/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3b5202ccfe454f38745c53ac711cc05095ef88a1
--- /dev/null
+++ b/modules/gui/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_gui)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_gui_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_gui_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_gui_sources
+		${vkcv_gui_include}/vkcv/gui/GUI.hpp
+		${vkcv_gui_source}/vkcv/gui/GUI.cpp
+		)
+
+# Setup some path variables to load libraries
+set(vkcv_gui_lib lib)
+set(vkcv_gui_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_gui_lib})
+
+# Check and load IMGUI
+include(config/ImGui.cmake)
+
+# adding source files to the module
+add_library(vkcv_gui STATIC ${vkcv_gui_sources} ${vkcv_imgui_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_gui ${vkcv_gui_libraries} vkcv ${vkcv_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_gui SYSTEM BEFORE PRIVATE ${vkcv_gui_includes} ${vkcv_include} ${vkcv_includes})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_gui BEFORE PUBLIC ${vkcv_gui_include} ${vkcv_imgui_includes})
+
+target_compile_definitions(vkcv_gui PUBLIC ${vkcv_gui_defines})
diff --git a/modules/gui/config/ImGui.cmake b/modules/gui/config/ImGui.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..90cdafdeee355af9e63723632572799e135b04da
--- /dev/null
+++ b/modules/gui/config/ImGui.cmake
@@ -0,0 +1,27 @@
+
+if (EXISTS "${vkcv_gui_lib_path}/imgui")
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_glfw.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_glfw.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_vulkan.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_vulkan.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imconfig.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_draw.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_demo.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_internal.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_tables.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_widgets.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imstb_rectpack.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imstb_textedit.h)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imstb_truetype.h)
+	
+	list(APPEND vkcv_imgui_includes ${vkcv_gui_lib}/imgui)
+	list(APPEND vkcv_imgui_includes ${vkcv_gui_lib}/imgui/backend)
+	
+	list(APPEND vkcv_gui_include ${vkcv_gui_lib})
+	
+	list(APPEND vkcv_gui_defines IMGUI_DISABLE_WIN32_FUNCTIONS=1)
+else()
+	message(WARNING "IMGUI is required..! Update the submodules!")
+endif ()
diff --git a/modules/gui/include/vkcv/gui/GUI.hpp b/modules/gui/include/vkcv/gui/GUI.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d1a9986c5f69bfd9d4392bd5ae50f0b1f8b60642
--- /dev/null
+++ b/modules/gui/include/vkcv/gui/GUI.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "imgui/imgui.h"
+#include "imgui/backends/imgui_impl_glfw.h"
+#include "imgui/backends/imgui_impl_vulkan.h"
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Window.hpp>
+
+namespace vkcv::gui {
+
+	class GUI final {
+	private:
+		Window& m_window;
+		Core& m_core;
+		
+		const Context& m_context;
+		
+		ImGuiContext* m_gui_context;
+		
+		vk::DescriptorPool m_descriptor_pool;
+		vk::RenderPass m_render_pass;
+		
+		event_handle<int,int,int> f_mouseButton;
+		event_handle<double,double> f_mouseScroll;
+		event_handle<int,int,int,int> f_key;
+		event_handle<unsigned int> f_char;
+		
+	public:
+		/**
+		 * Constructor of a new instance of ImGui management
+		 *
+		 * @param core Valid #Core instance of the framework
+		 * @param window Valid #Window instance of the framework
+		 */
+		GUI(Core& core, Window& window);
+		
+		GUI(const GUI& other) = delete;
+		GUI(GUI&& other) = delete;
+		
+		GUI& operator=(const GUI& other) = delete;
+		GUI& operator=(GUI&& other) = delete;
+		
+		/**
+		 * Destructor of a #GUI instance
+		 */
+		virtual ~GUI();
+		
+		/**
+		 * Sets up a new frame for ImGui to draw
+		 */
+		void beginGUI();
+		
+		/**
+		 * Ends a frame for ImGui, renders it and draws it onto
+		 * the currently active swapchain image of the core (ready to present).
+		 */
+		void endGUI();
+		
+	};
+
+}
diff --git a/modules/gui/lib/imgui b/modules/gui/lib/imgui
new file mode 160000
index 0000000000000000000000000000000000000000..d5828cd988db525f27128edeadb1a689cd2d7461
--- /dev/null
+++ b/modules/gui/lib/imgui
@@ -0,0 +1 @@
+Subproject commit d5828cd988db525f27128edeadb1a689cd2d7461
diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..38bb6894fb2b40c6ab10445f19431f87f7370afc
--- /dev/null
+++ b/modules/gui/src/vkcv/gui/GUI.cpp
@@ -0,0 +1,241 @@
+
+#include "vkcv/gui/GUI.hpp"
+
+#include <GLFW/glfw3.h>
+#include <vkcv/Logger.hpp>
+
+namespace vkcv::gui {
+	
+	static void checkVulkanResult(VkResult resultCode) {
+		if (resultCode == 0)
+			return;
+		
+		const auto result = vk::Result(resultCode);
+		
+		vkcv_log(LogLevel::ERROR, "ImGui has a problem with Vulkan! (%s)", vk::to_string(result).c_str());
+	}
+	
+	GUI::GUI(Core& core, Window& window) :
+	m_window(window),
+	m_core(core),
+	m_context(m_core.getContext()),
+	m_gui_context(nullptr) {
+		IMGUI_CHECKVERSION();
+		
+		m_gui_context = ImGui::CreateContext();
+		
+		ImGui_ImplGlfw_InitForVulkan(m_window.getWindow(), false);
+		
+		f_mouseButton = m_window.e_mouseButton.add([&](int button, int action, int mods) {
+			ImGui_ImplGlfw_MouseButtonCallback(m_window.getWindow(), button, action, mods);
+		});
+		
+		f_mouseScroll = m_window.e_mouseScroll.add([&](double xoffset, double yoffset) {
+			ImGui_ImplGlfw_ScrollCallback(m_window.getWindow(), xoffset, yoffset);
+		});
+		
+		f_key = m_window.e_key.add([&](int key, int scancode, int action, int mods) {
+			ImGui_ImplGlfw_KeyCallback(m_window.getWindow(), key, scancode, action, mods);
+		});
+		
+		f_char = m_window.e_char.add([&](unsigned int c) {
+			ImGui_ImplGlfw_CharCallback(m_window.getWindow(), c);
+		});
+		
+		vk::DescriptorPoolSize pool_sizes[] = {
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageImage, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformTexelBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageTexelBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eInputAttachment, 1000)
+		};
+		
+		const vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo (
+				vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
+				static_cast<uint32_t>(1000 * IM_ARRAYSIZE(pool_sizes)),
+				static_cast<uint32_t>(IM_ARRAYSIZE(pool_sizes)),
+				pool_sizes
+		);
+		
+		m_descriptor_pool = m_context.getDevice().createDescriptorPool(descriptorPoolCreateInfo);
+		
+		const vk::PhysicalDevice& physicalDevice = m_context.getPhysicalDevice();
+		const Swapchain& swapchain = m_core.getSwapchain();
+		
+		const uint32_t graphicsQueueFamilyIndex = (
+				m_context.getQueueManager().getGraphicsQueues()[0].familyIndex
+		);
+		
+		ImGui_ImplVulkan_InitInfo init_info = {};
+		init_info.Instance = static_cast<VkInstance>(m_context.getInstance());
+		init_info.PhysicalDevice = static_cast<VkPhysicalDevice>(m_context.getPhysicalDevice());
+		init_info.Device = static_cast<VkDevice>(m_context.getDevice());
+		init_info.QueueFamily = graphicsQueueFamilyIndex;
+		init_info.Queue = static_cast<VkQueue>(m_context.getQueueManager().getGraphicsQueues()[0].handle);
+		init_info.PipelineCache = 0;
+		init_info.DescriptorPool = static_cast<VkDescriptorPool>(m_descriptor_pool);
+		init_info.Allocator = nullptr;
+		init_info.MinImageCount = swapchain.getImageCount();
+		init_info.ImageCount = swapchain.getImageCount();
+		init_info.CheckVkResultFn = checkVulkanResult;
+		
+		const vk::AttachmentDescription attachment (
+				vk::AttachmentDescriptionFlags(),
+				swapchain.getFormat(),
+				vk::SampleCountFlagBits::e1,
+				vk::AttachmentLoadOp::eLoad,
+				vk::AttachmentStoreOp::eStore,
+				vk::AttachmentLoadOp::eDontCare,
+				vk::AttachmentStoreOp::eDontCare,
+				vk::ImageLayout::eUndefined,
+				vk::ImageLayout::ePresentSrcKHR
+		);
+		
+		const vk::AttachmentReference attachmentReference (
+				0,
+				vk::ImageLayout::eColorAttachmentOptimal
+		);
+		
+		const vk::SubpassDescription subpass (
+				vk::SubpassDescriptionFlags(),
+				vk::PipelineBindPoint::eGraphics,
+				0,
+				nullptr,
+				1,
+				&attachmentReference,
+				nullptr,
+				nullptr,
+				0,
+				nullptr
+		);
+		
+		const vk::SubpassDependency dependency (
+				VK_SUBPASS_EXTERNAL,
+				0,
+				vk::PipelineStageFlagBits::eColorAttachmentOutput,
+				vk::PipelineStageFlagBits::eColorAttachmentOutput,
+				vk::AccessFlags(),
+				vk::AccessFlagBits::eColorAttachmentWrite,
+				vk::DependencyFlags()
+		);
+		
+		const vk::RenderPassCreateInfo passCreateInfo (
+				vk::RenderPassCreateFlags(),
+				1,
+				&attachment,
+				1,
+				&subpass,
+				1,
+				&dependency
+		);
+		
+		m_render_pass = m_context.getDevice().createRenderPass(passCreateInfo);
+		
+		ImGui_ImplVulkan_Init(&init_info, static_cast<VkRenderPass>(m_render_pass));
+		
+		const SubmitInfo submitInfo { QueueType::Graphics, {}, {} };
+		
+		m_core.recordAndSubmitCommandsImmediate(submitInfo, [](const vk::CommandBuffer& commandBuffer) {
+			ImGui_ImplVulkan_CreateFontsTexture(static_cast<VkCommandBuffer>(commandBuffer));
+		}, []() {
+			ImGui_ImplVulkan_DestroyFontUploadObjects();
+		});
+		
+		m_context.getDevice().waitIdle();
+	}
+	
+	GUI::~GUI() {
+		m_context.getDevice().waitIdle();
+		
+		ImGui_ImplVulkan_Shutdown();
+		
+		m_context.getDevice().destroyRenderPass(m_render_pass);
+		m_context.getDevice().destroyDescriptorPool(m_descriptor_pool);
+		
+		ImGui_ImplGlfw_Shutdown();
+		
+		m_window.e_mouseButton.remove(f_mouseButton);
+		m_window.e_mouseScroll.remove(f_mouseScroll);
+		m_window.e_key.remove(f_key);
+		m_window.e_char.remove(f_char);
+		
+		if (m_gui_context) {
+			ImGui::DestroyContext(m_gui_context);
+		}
+	}
+	
+	void GUI::beginGUI() {
+		const Swapchain& swapchain = m_core.getSwapchain();
+		const auto extent = swapchain.getExtent();
+		
+		if ((extent.width > 0) && (extent.height > 0)) {
+			ImGui_ImplVulkan_SetMinImageCount(swapchain.getImageCount());
+		}
+		
+		ImGui_ImplVulkan_NewFrame();
+		ImGui_ImplGlfw_NewFrame();
+		ImGui::NewFrame();
+	}
+	
+	void GUI::endGUI() {
+		ImGui::Render();
+		
+		ImDrawData* drawData = ImGui::GetDrawData();
+		
+		if ((!drawData) ||
+			(drawData->DisplaySize.x <= 0.0f) ||
+			(drawData->DisplaySize.y <= 0.0f)) {
+			return;
+		}
+		
+		const Swapchain& swapchain = m_core.getSwapchain();
+		const auto extent = swapchain.getExtent();
+		
+		const vk::ImageView swapchainImageView = m_core.getSwapchainImageView();
+
+		const vk::FramebufferCreateInfo framebufferCreateInfo (
+				vk::FramebufferCreateFlags(),
+				m_render_pass,
+				1,
+				&swapchainImageView,
+				extent.width,
+				extent.height,
+				1
+		);
+		
+		const vk::Framebuffer framebuffer = m_context.getDevice().createFramebuffer(framebufferCreateInfo);
+		
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Graphics;
+		
+		m_core.recordAndSubmitCommandsImmediate(submitInfo, [&](const vk::CommandBuffer& commandBuffer) {
+			const vk::Rect2D renderArea (
+					vk::Offset2D(0, 0),
+					extent
+			);
+			
+			const vk::RenderPassBeginInfo beginInfo (
+					m_render_pass,
+					framebuffer,
+					renderArea,
+					0,
+					nullptr
+			);
+			
+			commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline);
+			
+			ImGui_ImplVulkan_RenderDrawData(drawData, static_cast<VkCommandBuffer>(commandBuffer));
+			
+			commandBuffer.endRenderPass();
+		}, [&]() {
+			m_context.getDevice().destroyFramebuffer(framebuffer);
+		});
+	}
+	
+}
diff --git a/modules/material/CMakeLists.txt b/modules/material/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d5b654cc6d00ce77d93b4666f48b7a5097e674b3
--- /dev/null
+++ b/modules/material/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_material)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_material_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_material_sources
+		${vkcv_material_include}/vkcv/material/Material.hpp
+		${vkcv_material_source}/vkcv/material/Material.cpp
+		${vkcv_material_include}/vkcv/material/PBRMaterial.hpp
+		${vkcv_material_source}/vkcv/material/PBRMaterial.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_material STATIC ${vkcv_material_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_material vkcv ${vkcv_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_material SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_material BEFORE PUBLIC ${vkcv_material_include})
diff --git a/modules/material/include/vkcv/material/Material.hpp b/modules/material/include/vkcv/material/Material.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..00b492072fa4ef8b7b41f70202d515ee4ac828fa
--- /dev/null
+++ b/modules/material/include/vkcv/material/Material.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::material {
+
+	class Material {
+	private:
+	public:
+		const DescriptorSetHandle m_DescriptorSetHandle;
+	protected:
+		Material(const DescriptorSetHandle& setHandle); 
+	};
+	
+}
diff --git a/modules/material/include/vkcv/material/PBRMaterial.hpp b/modules/material/include/vkcv/material/PBRMaterial.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..09a5214b0e748a09ef8caefe5bf2b1a69ecbd8e1
--- /dev/null
+++ b/modules/material/include/vkcv/material/PBRMaterial.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/DescriptorConfig.hpp>
+#include <vkcv/Core.hpp>
+
+
+#include "Material.hpp"
+
+namespace vkcv::material
+{
+    class PBRMaterial : Material
+    {
+    private:
+        struct vec3 {
+            float x, y, z;
+        };
+        struct vec4 {
+            float x, y, z, a;
+        };
+        PBRMaterial(const ImageHandle& colorImg,
+            const SamplerHandle& colorSmp,
+            const ImageHandle& normalImg,
+            const SamplerHandle& normalSmp,
+            const ImageHandle& metRoughImg,
+            const SamplerHandle& metRoughSmp,
+            const ImageHandle& occlusionImg,
+            const SamplerHandle& occlusionSmp,
+            const ImageHandle& emissiveImg,
+            const SamplerHandle& emissiveSmp,
+            const DescriptorSetHandle& setHandle,
+            vec4 baseColorFactor,
+            float metallicFactor,
+            float roughnessFactor,
+            float normalScale,
+            float occlusionStrength,
+            vec3 emissiveFactor) noexcept;
+
+
+    public:
+        PBRMaterial() = delete;
+        
+        const ImageHandle   m_ColorTexture;
+        const SamplerHandle m_ColorSampler;
+
+        const ImageHandle   m_NormalTexture;
+        const SamplerHandle m_NormalSampler;
+
+        const ImageHandle   m_MetRoughTexture;
+        const SamplerHandle m_MetRoughSampler;
+
+        const ImageHandle m_OcclusionTexture;
+        const SamplerHandle m_OcclusionSampler;
+
+        const ImageHandle m_EmissiveTexture;
+        const SamplerHandle m_EmissiveSampler;
+
+        //
+        const vec4 m_BaseColorFactor;
+		const float m_MetallicFactor;
+		const float m_RoughnessFactor;
+		const float m_NormalScale;
+		const float m_OcclusionStrength;
+		const vec3 m_EmissiveFactor;
+
+        /*
+        * Returns the material's necessary descriptor bindings which serves as its descriptor layout
+        * The binding is in the following order:
+        * 0 - diffuse texture
+        * 1 - diffuse sampler
+        * 2 - normal texture
+        * 3 - normal sampler
+        * 4 - metallic roughness texture
+        * 5 - metallic roughness sampler
+        * 6 - occlusion texture
+        * 7 - occlusion sampler
+        * 8 - emissive texture
+        * 9 - emissive sampler
+        */
+        static std::vector<DescriptorBinding> getDescriptorBindings() noexcept;
+
+        static PBRMaterial create(
+            vkcv::Core* core,
+            ImageHandle          &colorImg,
+            SamplerHandle        &colorSmp,
+            ImageHandle          &normalImg,
+            SamplerHandle        &normalSmp,
+            ImageHandle          &metRoughImg,
+            SamplerHandle        &metRoughSmp,
+			ImageHandle			&occlusionImg,
+			SamplerHandle		&occlusionSmp,
+			ImageHandle			&emissiveImg,
+			SamplerHandle		&emissiveSmp,
+            vec4 baseColorFactor,
+            float metallicFactor,
+            float roughnessFactor,
+            float normalScale,
+            float occlusionStrength,
+            vec3 emissiveFactor);
+
+    };
+}
\ No newline at end of file
diff --git a/modules/material/src/vkcv/material/Material.cpp b/modules/material/src/vkcv/material/Material.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9168bcfbf924e9868ceaaff74aef5d3c6b99739c
--- /dev/null
+++ b/modules/material/src/vkcv/material/Material.cpp
@@ -0,0 +1,12 @@
+
+#include "vkcv/material/Material.hpp"
+
+namespace vkcv::material {
+
+	//TODO
+
+	Material::Material(const DescriptorSetHandle& setHandle) : m_DescriptorSetHandle(setHandle)
+	{
+	}
+
+}
diff --git a/modules/material/src/vkcv/material/PBRMaterial.cpp b/modules/material/src/vkcv/material/PBRMaterial.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d27e755c06a39e369d22efc997a0b411d067c132
--- /dev/null
+++ b/modules/material/src/vkcv/material/PBRMaterial.cpp
@@ -0,0 +1,194 @@
+#include "vkcv/material/PBRMaterial.hpp"
+
+
+namespace vkcv::material
+{
+    PBRMaterial::PBRMaterial(
+        const ImageHandle& colorImg,
+        const SamplerHandle& colorSmp,
+        const ImageHandle& normalImg,
+        const SamplerHandle& normalSmp,
+        const ImageHandle& metRoughImg,
+        const SamplerHandle& metRoughSmp,
+        const ImageHandle& occlusionImg,
+        const SamplerHandle& occlusionSmp,
+        const ImageHandle& emissiveImg,
+        const SamplerHandle& emissiveSmp,
+        const DescriptorSetHandle& setHandle,
+        vec4 baseColorFactor,
+        float metallicFactor,
+        float roughnessFactor,
+        float normalScale,
+        float occlusionStrength,
+        vec3 emissiveFactor) noexcept :
+        m_ColorTexture(colorImg),
+        m_ColorSampler(colorSmp),
+        m_NormalTexture(normalImg),
+        m_NormalSampler(normalSmp),
+        m_MetRoughTexture(metRoughImg),
+        m_MetRoughSampler(metRoughSmp),
+        m_OcclusionTexture(occlusionImg),
+        m_OcclusionSampler(occlusionSmp),
+        m_EmissiveTexture(emissiveImg),
+        m_EmissiveSampler(emissiveSmp),
+        Material(setHandle),
+        m_BaseColorFactor(baseColorFactor),
+        m_MetallicFactor(metallicFactor),
+        m_RoughnessFactor(roughnessFactor),
+        m_NormalScale(normalScale),
+        m_OcclusionStrength(occlusionStrength),
+        m_EmissiveFactor(emissiveFactor)
+    {
+    }
+
+    std::vector<DescriptorBinding> PBRMaterial::getDescriptorBindings() noexcept
+    {
+		static std::vector<DescriptorBinding> bindings;
+		
+		if (bindings.empty()) {
+			bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+			bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+		}
+    	
+        return bindings;
+    }
+
+    PBRMaterial PBRMaterial::create(
+        vkcv::Core* core,
+        ImageHandle& colorImg,
+        SamplerHandle& colorSmp,
+        ImageHandle& normalImg,
+        SamplerHandle& normalSmp,
+        ImageHandle& metRoughImg,
+        SamplerHandle& metRoughSmp,
+        ImageHandle& occlusionImg,
+        SamplerHandle& occlusionSmp,
+        ImageHandle& emissiveImg,
+        SamplerHandle& emissiveSmp,
+        vec4 baseColorFactor,
+        float metallicFactor,
+        float roughnessFactor,
+        float normalScale,
+        float occlusionStrength,
+        vec3 emissiveFactor)
+    {
+        //Test if Images and samplers valid, if not use default
+         if (!colorImg) {
+            vkcv::Image defaultColor = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
+            vec4 colorData{ 228, 51, 255,1 };
+            defaultColor.fill(&colorData);
+            colorImg = defaultColor.getHandle();
+        }
+        if (!normalImg) {
+            vkcv::Image defaultNormal = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
+            vec4 normalData{ 0, 0, 1,0 };
+            defaultNormal.fill(&normalData);
+            normalImg = defaultNormal.getHandle();
+        }
+        if (!metRoughImg) {
+            vkcv::Image defaultRough = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
+            vec4 roughData{ 228, 51, 255,1 };
+            defaultRough.fill(&roughData);
+            metRoughImg = defaultRough.getHandle();
+        }
+        if (!occlusionImg) {
+            vkcv::Image defaultOcclusion = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
+            vec4 occlusionData{ 228, 51, 255,1 };
+            defaultOcclusion.fill(&occlusionData);
+            occlusionImg = defaultOcclusion.getHandle();
+        }
+        if (!emissiveImg) {
+            vkcv::Image defaultEmissive = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
+            vec4 emissiveData{ 0, 0, 0,1 };
+            defaultEmissive.fill(&emissiveData);
+            emissiveImg = defaultEmissive.getHandle();
+        }
+        if (!colorSmp) {            
+            colorSmp = core->createSampler(
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerMipmapMode::LINEAR,
+                vkcv::SamplerAddressMode::REPEAT
+            );            
+        }
+        if (!normalSmp) {            
+            normalSmp = core->createSampler(
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerMipmapMode::LINEAR,
+                vkcv::SamplerAddressMode::REPEAT
+            );            
+        }
+        if (!metRoughSmp) {
+            metRoughSmp = core->createSampler(
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerMipmapMode::LINEAR,
+                vkcv::SamplerAddressMode::REPEAT
+            );            
+        }
+        if (!occlusionSmp) {
+            occlusionSmp = core->createSampler(
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerMipmapMode::LINEAR,
+                vkcv::SamplerAddressMode::REPEAT
+            );
+        }
+        if (!emissiveSmp) {
+            emissiveSmp = core->createSampler(
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerFilterType::LINEAR,
+                vkcv::SamplerMipmapMode::LINEAR,
+                vkcv::SamplerAddressMode::REPEAT
+            );
+        }
+        
+
+
+        //create descriptorset
+        vkcv::DescriptorSetHandle descriptorSetHandle = core->createDescriptorSet(getDescriptorBindings());
+        //writes
+        vkcv::DescriptorWrites setWrites;
+        setWrites.sampledImageWrites = {
+            vkcv::SampledImageDescriptorWrite(0, colorImg),
+            vkcv::SampledImageDescriptorWrite(2, normalImg),
+            vkcv::SampledImageDescriptorWrite(4, metRoughImg),
+            vkcv::SampledImageDescriptorWrite(6, occlusionImg),
+            vkcv::SampledImageDescriptorWrite(8, emissiveImg) };
+        setWrites.samplerWrites = {
+            vkcv::SamplerDescriptorWrite(1, colorSmp),
+            vkcv::SamplerDescriptorWrite(3, normalSmp),
+            vkcv::SamplerDescriptorWrite(5, metRoughSmp),
+            vkcv::SamplerDescriptorWrite(7, occlusionSmp),
+            vkcv::SamplerDescriptorWrite(9, emissiveSmp) };
+        core->writeDescriptorSet(descriptorSetHandle, setWrites);
+
+        return PBRMaterial(
+            colorImg,
+            colorSmp,
+            normalImg,
+            normalSmp,
+            metRoughImg,
+            metRoughSmp,
+            occlusionImg,
+            occlusionSmp,
+            emissiveImg,
+            emissiveSmp,
+            descriptorSetHandle,
+            baseColorFactor,
+            metallicFactor,
+            roughnessFactor,
+            normalScale,
+            occlusionStrength,
+            emissiveFactor);
+    }
+}
\ No newline at end of file
diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4b674ec41ed4ea5f42dc73187c212e6a69952cec
--- /dev/null
+++ b/modules/shader_compiler/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_shader_compiler)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_shader_compiler_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_shader_compiler_sources
+		${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp
+		${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_shader_compiler STATIC ${vkcv_shader_compiler_sources})
+
+# Setup some path variables to load libraries
+set(vkcv_shader_compiler_lib lib)
+set(vkcv_shader_compiler_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_shader_compiler_lib})
+
+# Check and load GLSLANG
+include(config/GLSLANG.cmake)
+
+# link the required libraries to the module
+target_link_libraries(vkcv_shader_compiler ${vkcv_shader_compiler_libraries} vkcv)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_shader_compiler BEFORE PUBLIC ${vkcv_shader_compiler_include})
diff --git a/modules/shader_compiler/config/GLSLANG.cmake b/modules/shader_compiler/config/GLSLANG.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..50b9fd46bd0db9421c632aa0b80fb8df7e3f2123
--- /dev/null
+++ b/modules/shader_compiler/config/GLSLANG.cmake
@@ -0,0 +1,28 @@
+
+if (EXISTS "${vkcv_shader_compiler_lib_path}/glslang")
+	set(SKIP_GLSLANG_INSTALL ON CACHE INTERNAL "")
+	set(ENABLE_SPVREMAPPER OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_BINARIES OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_JS OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_WEBMIN OFF CACHE INTERNAL "")
+	set(ENABLE_GLSLANG_WEBMIN_DEVEL OFF CACHE INTERNAL "")
+	set(ENABLE_EMSCRIPTEN_SINGLE_FILE OFF CACHE INTERNAL "")
+	set(ENABLE_EMSCRIPTEN_ENVIRONMENT_NODE OFF CACHE INTERNAL "")
+	set(ENABLE_HLSL OFF CACHE INTERNAL "")
+	set(ENABLE_RTTI OFF CACHE INTERNAL "")
+	set(ENABLE_EXCEPTIONS OFF CACHE INTERNAL "")
+	set(ENABLE_OPT OFF CACHE INTERNAL "")
+	set(ENABLE_PCH OFF CACHE INTERNAL "")
+	set(ENABLE_CTEST OFF CACHE INTERNAL "")
+	set(USE_CCACHE OFF CACHE INTERNAL "")
+	
+	set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
+	set(BUILD_EXTERNAL OFF CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_shader_compiler_lib}/glslang)
+	
+	list(APPEND vkcv_shader_compiler_libraries glslang SPIRV)
+	list(APPEND vkcv_shader_compiler_includes ${vkcv_shader_compiler_lib})
+else()
+	message(WARNING "GLSLANG is required..! Update the submodules!")
+endif ()
diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d7b7af7178531aea358cecbc8b86a29527173014
--- /dev/null
+++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <vkcv/Event.hpp>
+
+namespace vkcv::shader {
+	
+	typedef typename event_function<ShaderStage, const std::filesystem::path&>::type ShaderCompiledFunction;
+	
+	class Compiler {
+	private:
+	public:
+		virtual void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
+							 const ShaderCompiledFunction& compiled, bool update = false) = 0;
+		
+	};
+	
+}
diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7105d93a0c3e153bf3abe1d624d0c13c6f09ac6d
--- /dev/null
+++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <filesystem>
+
+#include <vkcv/ShaderStage.hpp>
+#include "Compiler.hpp"
+
+namespace vkcv::shader {
+	
+	class GLSLCompiler {
+	private:
+	public:
+		GLSLCompiler();
+		
+		GLSLCompiler(const GLSLCompiler& other);
+		GLSLCompiler(GLSLCompiler&& other) = default;
+	
+		~GLSLCompiler();
+		
+		GLSLCompiler& operator=(const GLSLCompiler& other);
+		GLSLCompiler& operator=(GLSLCompiler&& other) = default;
+		
+		void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
+					 const ShaderCompiledFunction& compiled, bool update = false);
+		
+	};
+	
+}
diff --git a/modules/shader_compiler/lib/glslang b/modules/shader_compiler/lib/glslang
new file mode 160000
index 0000000000000000000000000000000000000000..fe15158676657bf965e41c32e15ae5db7ea2ab6a
--- /dev/null
+++ b/modules/shader_compiler/lib/glslang
@@ -0,0 +1 @@
+Subproject commit fe15158676657bf965e41c32e15ae5db7ea2ab6a
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ec358188b8e871da6f4d62ffd397f32bfb795ee2
--- /dev/null
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -0,0 +1,283 @@
+
+#include "vkcv/shader/GLSLCompiler.hpp"
+
+#include <fstream>
+#include <glslang/SPIRV/GlslangToSpv.h>
+#include <glslang/StandAlone/DirStackFileIncluder.h>
+
+#include <vkcv/Logger.hpp>
+
+namespace vkcv::shader {
+	
+	static uint32_t s_CompilerCount = 0;
+	
+	GLSLCompiler::GLSLCompiler() {
+		if (s_CompilerCount == 0) {
+			glslang::InitializeProcess();
+		}
+		
+		s_CompilerCount++;
+	}
+	
+	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) {
+		s_CompilerCount++;
+	}
+	
+	GLSLCompiler::~GLSLCompiler() {
+		s_CompilerCount--;
+		
+		if (s_CompilerCount == 0) {
+			glslang::FinalizeProcess();
+		}
+	}
+	
+	GLSLCompiler &GLSLCompiler::operator=(const GLSLCompiler &other) {
+		s_CompilerCount++;
+		return *this;
+	}
+	
+	constexpr EShLanguage findShaderLanguage(ShaderStage shaderStage) {
+		switch (shaderStage) {
+			case ShaderStage::VERTEX:
+				return EShLangVertex;
+			case ShaderStage::TESS_CONTROL:
+				return EShLangTessControl;
+			case ShaderStage::TESS_EVAL:
+				return EShLangTessEvaluation;
+			case ShaderStage::GEOMETRY:
+				return EShLangGeometry;
+			case ShaderStage::FRAGMENT:
+				return EShLangFragment;
+			case ShaderStage::COMPUTE:
+				return EShLangCompute;
+			default:
+				return EShLangCount;
+		}
+	}
+	
+	static void initResources(TBuiltInResource& resources) {
+		resources.maxLights = 32;
+		resources.maxClipPlanes = 6;
+		resources.maxTextureUnits = 32;
+		resources.maxTextureCoords = 32;
+		resources.maxVertexAttribs = 64;
+		resources.maxVertexUniformComponents = 4096;
+		resources.maxVaryingFloats = 64;
+		resources.maxVertexTextureImageUnits = 32;
+		resources.maxCombinedTextureImageUnits = 80;
+		resources.maxTextureImageUnits = 32;
+		resources.maxFragmentUniformComponents = 4096;
+		resources.maxDrawBuffers = 32;
+		resources.maxVertexUniformVectors = 128;
+		resources.maxVaryingVectors = 8;
+		resources.maxFragmentUniformVectors = 16;
+		resources.maxVertexOutputVectors = 16;
+		resources.maxFragmentInputVectors = 15;
+		resources.minProgramTexelOffset = -8;
+		resources.maxProgramTexelOffset = 7;
+		resources.maxClipDistances = 8;
+		resources.maxComputeWorkGroupCountX = 65535;
+		resources.maxComputeWorkGroupCountY = 65535;
+		resources.maxComputeWorkGroupCountZ = 65535;
+		resources.maxComputeWorkGroupSizeX = 1024;
+		resources.maxComputeWorkGroupSizeY = 1024;
+		resources.maxComputeWorkGroupSizeZ = 64;
+		resources.maxComputeUniformComponents = 1024;
+		resources.maxComputeTextureImageUnits = 16;
+		resources.maxComputeImageUniforms = 8;
+		resources.maxComputeAtomicCounters = 8;
+		resources.maxComputeAtomicCounterBuffers = 1;
+		resources.maxVaryingComponents = 60;
+		resources.maxVertexOutputComponents = 64;
+		resources.maxGeometryInputComponents = 64;
+		resources.maxGeometryOutputComponents = 128;
+		resources.maxFragmentInputComponents = 128;
+		resources.maxImageUnits = 8;
+		resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
+		resources.maxCombinedShaderOutputResources = 8;
+		resources.maxImageSamples = 0;
+		resources.maxVertexImageUniforms = 0;
+		resources.maxTessControlImageUniforms = 0;
+		resources.maxTessEvaluationImageUniforms = 0;
+		resources.maxGeometryImageUniforms = 0;
+		resources.maxFragmentImageUniforms = 8;
+		resources.maxCombinedImageUniforms = 8;
+		resources.maxGeometryTextureImageUnits = 16;
+		resources.maxGeometryOutputVertices = 256;
+		resources.maxGeometryTotalOutputComponents = 1024;
+		resources.maxGeometryUniformComponents = 1024;
+		resources.maxGeometryVaryingComponents = 64;
+		resources.maxTessControlInputComponents = 128;
+		resources.maxTessControlOutputComponents = 128;
+		resources.maxTessControlTextureImageUnits = 16;
+		resources.maxTessControlUniformComponents = 1024;
+		resources.maxTessControlTotalOutputComponents = 4096;
+		resources.maxTessEvaluationInputComponents = 128;
+		resources.maxTessEvaluationOutputComponents = 128;
+		resources.maxTessEvaluationTextureImageUnits = 16;
+		resources.maxTessEvaluationUniformComponents = 1024;
+		resources.maxTessPatchComponents = 120;
+		resources.maxPatchVertices = 32;
+		resources.maxTessGenLevel = 64;
+		resources.maxViewports = 16;
+		resources.maxVertexAtomicCounters = 0;
+		resources.maxTessControlAtomicCounters = 0;
+		resources.maxTessEvaluationAtomicCounters = 0;
+		resources.maxGeometryAtomicCounters = 0;
+		resources.maxFragmentAtomicCounters = 8;
+		resources.maxCombinedAtomicCounters = 8;
+		resources.maxAtomicCounterBindings = 1;
+		resources.maxVertexAtomicCounterBuffers = 0;
+		resources.maxTessControlAtomicCounterBuffers = 0;
+		resources.maxTessEvaluationAtomicCounterBuffers = 0;
+		resources.maxGeometryAtomicCounterBuffers = 0;
+		resources.maxFragmentAtomicCounterBuffers = 1;
+		resources.maxCombinedAtomicCounterBuffers = 1;
+		resources.maxAtomicCounterBufferSize = 16384;
+		resources.maxTransformFeedbackBuffers = 4;
+		resources.maxTransformFeedbackInterleavedComponents = 64;
+		resources.maxCullDistances = 8;
+		resources.maxCombinedClipAndCullDistances = 8;
+		resources.maxSamples = 4;
+		resources.maxMeshOutputVerticesNV = 256;
+		resources.maxMeshOutputPrimitivesNV = 512;
+		resources.maxMeshWorkGroupSizeX_NV = 32;
+		resources.maxMeshWorkGroupSizeY_NV = 1;
+		resources.maxMeshWorkGroupSizeZ_NV = 1;
+		resources.maxTaskWorkGroupSizeX_NV = 32;
+		resources.maxTaskWorkGroupSizeY_NV = 1;
+		resources.maxTaskWorkGroupSizeZ_NV = 1;
+		resources.maxMeshViewCountNV = 4;
+		resources.limits.nonInductiveForLoops = 1;
+		resources.limits.whileLoops = 1;
+		resources.limits.doWhileLoops = 1;
+		resources.limits.generalUniformIndexing = 1;
+		resources.limits.generalAttributeMatrixVectorIndexing = 1;
+		resources.limits.generalVaryingIndexing = 1;
+		resources.limits.generalSamplerIndexing = 1;
+		resources.limits.generalVariableIndexing = 1;
+		resources.limits.generalConstantMatrixVectorIndexing = 1;
+	}
+	
+	static std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) {
+		std::ifstream file (shaderPath.string(), std::ios::ate);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return std::vector<char>{};
+		}
+		
+		std::streamsize fileSize = file.tellg();
+		std::vector<char> buffer (fileSize + 1);
+		
+		file.seekg(0);
+		file.read(buffer.data(), fileSize);
+		file.close();
+		
+		buffer[fileSize] = '\0';
+		return buffer;
+	}
+	
+	static bool writeSpirvCode(const std::filesystem::path &shaderPath, const std::vector<uint32_t>& spirv) {
+		std::ofstream file (shaderPath.string(), std::ios::out | std::ios::binary);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		const auto fileSize = static_cast<std::streamsize>(
+				sizeof(uint32_t) * spirv.size()
+		);
+		
+		file.seekp(0);
+		file.write(reinterpret_cast<const char*>(spirv.data()), fileSize);
+		file.close();
+		
+		return true;
+	}
+	
+	void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath,
+							   const ShaderCompiledFunction& compiled, bool update) {
+		const EShLanguage language = findShaderLanguage(shaderStage);
+		
+		if (language == EShLangCount) {
+			vkcv_log(LogLevel::ERROR, "Shader stage not supported (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		const std::vector<char> code = readShaderCode(shaderPath);
+		
+		glslang::TShader shader (language);
+		glslang::TProgram program;
+		
+		const char *shaderStrings [1];
+		shaderStrings[0] = code.data();
+		
+		shader.setStrings(shaderStrings, 1);
+		
+		TBuiltInResource resources = {};
+		initResources(resources);
+
+		const auto messages = (EShMessages)(
+			EShMsgSpvRules |
+			EShMsgVulkanRules
+			);
+
+		std::string preprocessedGLSL;
+
+		DirStackFileIncluder includer;
+		includer.pushExternalLocalDirectory(shaderPath.parent_path().string());
+
+		if (!shader.preprocess(&resources, 100, ENoProfile, false, false, messages, &preprocessedGLSL, includer)) {
+			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
+				shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
+		
+		const char* preprocessedCString = preprocessedGLSL.c_str();
+		shader.setStrings(&preprocessedCString, 1);
+
+		if (!shader.parse(&resources, 100, false, messages)) {
+			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
+					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
+		
+		program.addShader(&shader);
+		
+		if (!program.link(messages)) {
+			vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n} (%s)",
+					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
+		
+		const glslang::TIntermediate* intermediate = program.getIntermediate(language);
+		
+		if (!intermediate) {
+			vkcv_log(LogLevel::ERROR, "No valid intermediate representation (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		std::vector<uint32_t> spirv;
+		glslang::GlslangToSpv(*intermediate, spirv);
+		
+		const std::filesystem::path tmp_path (std::tmpnam(nullptr));
+		
+		if (!writeSpirvCode(tmp_path, spirv)) {
+			vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk (%s)", shaderPath.string().c_str());
+			return;
+		}
+		
+		if (compiled) {
+			compiled(shaderStage, tmp_path);
+		}
+		
+		std::filesystem::remove(tmp_path);
+		
+		if (update) {
+			// TODO: Shader hot compilation during runtime
+		}
+	}
+	
+}
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 7ca73a0811df7f1568508b56312ce3348237a695..34fbcb0cf8dd3f1d34efd2cc8424994c7da76e32 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -1,4 +1,7 @@
 
 # Add new projects/examples here:
+add_subdirectory(bloom)
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
+add_subdirectory(first_scene)
+add_subdirectory(voxelization)
\ No newline at end of file
diff --git a/projects/bloom/.gitignore b/projects/bloom/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3643183e0628e666abab193e1dd1d92c1774ac61
--- /dev/null
+++ b/projects/bloom/.gitignore
@@ -0,0 +1 @@
+bloom
\ No newline at end of file
diff --git a/projects/bloom/CMakeLists.txt b/projects/bloom/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8171938e7cb430aacce5562af44f628c11c97c54
--- /dev/null
+++ b/projects/bloom/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.16)
+project(bloom)
+
+# 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(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})
+	set_target_properties(bloom 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(bloom PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(bloom SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(bloom vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/bloom/resources/Sponza/Sponza.bin b/projects/bloom/resources/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cfedd26ca5a67b6d0a47d44d13a75e14a141717a
--- /dev/null
+++ b/projects/bloom/resources/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
+size 6347696
diff --git a/projects/bloom/resources/Sponza/Sponza.gltf b/projects/bloom/resources/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..172ea07e21c94465211c860cd805355704cef230
--- /dev/null
+++ b/projects/bloom/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
+size 713962
diff --git a/projects/bloom/resources/Sponza/background.png b/projects/bloom/resources/Sponza/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..b64def129da38f4e23d89e21b4af1039008a4327
--- /dev/null
+++ b/projects/bloom/resources/Sponza/background.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
+size 1417666
diff --git a/projects/bloom/resources/Sponza/chain_texture.png b/projects/bloom/resources/Sponza/chain_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1768cff78e0614ad707eca8602a4c4edab5e5
--- /dev/null
+++ b/projects/bloom/resources/Sponza/chain_texture.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
+size 490895
diff --git a/projects/bloom/resources/Sponza/lion.png b/projects/bloom/resources/Sponza/lion.png
new file mode 100644
index 0000000000000000000000000000000000000000..c49c7f0ed31e762e19284d0d3624fbc47664e56b
--- /dev/null
+++ b/projects/bloom/resources/Sponza/lion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
+size 2088728
diff --git a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png b/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cde4c7a6511e9a5f03c63ad996437fcdba3ce2df
--- /dev/null
+++ b/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
+size 1928829
diff --git a/projects/bloom/resources/Sponza/sponza_arch_diff.png b/projects/bloom/resources/Sponza/sponza_arch_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9bda2918d226039f9e2d03902d377b706fab6
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_arch_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
+size 1596151
diff --git a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png b/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..59de631ffac4414cabf69b2dc794c46fc187d6cb
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
+size 1872074
diff --git a/projects/bloom/resources/Sponza/sponza_column_a_diff.png b/projects/bloom/resources/Sponza/sponza_column_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..01a82432d3f9939bbefe850bdb900f1ff9a3f6db
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_column_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
+size 1840992
diff --git a/projects/bloom/resources/Sponza/sponza_column_b_diff.png b/projects/bloom/resources/Sponza/sponza_column_b_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a660cce2a5a9b8997772c746058ce23e7d45d7
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_column_b_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
+size 2170433
diff --git a/projects/bloom/resources/Sponza/sponza_column_c_diff.png b/projects/bloom/resources/Sponza/sponza_column_c_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc46fd979044a938d3adca7601689e71504e48bf
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_column_c_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
+size 2066950
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..384c8c2c051160d530eb3ac8b05c9c60752a2d2b
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
+size 9510905
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..af842e9f5fe18c1f609875e00899a6770fa4488b
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_curtain_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
+size 9189263
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9b6391a199407637fa71033d79fb58b8b4f0d7
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
+size 8785458
diff --git a/projects/bloom/resources/Sponza/sponza_details_diff.png b/projects/bloom/resources/Sponza/sponza_details_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..12656686362c3e0a297e060491f33bd7351551f9
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_details_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
+size 1522643
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..879d16ef84722a4fc13e83a771778de326e4bc54
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
+size 2229440
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..3311287a219d2148620b87fe428fea071688d051
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_fabric_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
+size 2267382
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..de110f369004388dae4cd5067c63428db3a07834
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
+size 2258220
diff --git a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png b/projects/bloom/resources/Sponza/sponza_flagpole_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6e0812a0df80346318baa3cb50a6888afc58f8
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_flagpole_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
+size 1425673
diff --git a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png b/projects/bloom/resources/Sponza/sponza_floor_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..788ed764f79ba724f04a2d603076a5b85013e188
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_floor_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
+size 1996838
diff --git a/projects/bloom/resources/Sponza/sponza_roof_diff.png b/projects/bloom/resources/Sponza/sponza_roof_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5b84261fdd1cc776a94b3ce398c7806b895f9a3
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_roof_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
+size 2320533
diff --git a/projects/bloom/resources/Sponza/sponza_thorn_diff.png b/projects/bloom/resources/Sponza/sponza_thorn_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9142674a7d4a6f94a48c5152cf0300743b597a
--- /dev/null
+++ b/projects/bloom/resources/Sponza/sponza_thorn_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
+size 635001
diff --git a/projects/bloom/resources/Sponza/vase_dif.png b/projects/bloom/resources/Sponza/vase_dif.png
new file mode 100644
index 0000000000000000000000000000000000000000..61236a81cb324af8797b05099cd264cefe189e56
--- /dev/null
+++ b/projects/bloom/resources/Sponza/vase_dif.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
+size 1842101
diff --git a/projects/bloom/resources/Sponza/vase_hanging.png b/projects/bloom/resources/Sponza/vase_hanging.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a3cee71d8213225090c74f8c0dce33b9d44378
--- /dev/null
+++ b/projects/bloom/resources/Sponza/vase_hanging.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
+size 1432804
diff --git a/projects/bloom/resources/Sponza/vase_plant.png b/projects/bloom/resources/Sponza/vase_plant.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ad95e702e229f1ebd803e5203a266d15f2c07b9
--- /dev/null
+++ b/projects/bloom/resources/Sponza/vase_plant.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
+size 998089
diff --git a/projects/bloom/resources/Sponza/vase_round.png b/projects/bloom/resources/Sponza/vase_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..c17953abc000c44b8991e23c136c2b67348f3d1b
--- /dev/null
+++ b/projects/bloom/resources/Sponza/vase_round.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
+size 1871399
diff --git a/projects/bloom/resources/shaders/comp.spv b/projects/bloom/resources/shaders/comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..85c7e74cfc0a89917bf6dd1a7ec449368274c1d3
Binary files /dev/null and b/projects/bloom/resources/shaders/comp.spv differ
diff --git a/projects/bloom/resources/shaders/composite.comp b/projects/bloom/resources/shaders/composite.comp
new file mode 100644
index 0000000000000000000000000000000000000000..190bed0657d70e0217bf654820d0b2b2c58f12c2
--- /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/downsample.comp b/projects/bloom/resources/shaders/downsample.comp
new file mode 100644
index 0000000000000000000000000000000000000000..2ab00c7c92798769153634f3479c5b7f3fb61d94
--- /dev/null
+++ b/projects/bloom/resources/shaders/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/bloom/resources/shaders/gammaCorrection.comp b/projects/bloom/resources/shaders/gammaCorrection.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f89ad167c846cca8e80f69d33eda83bd6ed00d46
--- /dev/null
+++ b/projects/bloom/resources/shaders/gammaCorrection.comp
@@ -0,0 +1,20 @@
+#version 440
+
+layout(set=0, binding=0, r11f_g11f_b10f)    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;
+    // cheap Reinhard tone mapping
+    linearColor = linearColor/(linearColor + 1.0f);
+    vec3 gammaCorrected = pow(linearColor, vec3(1.f / 2.2f));
+    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/lensFlares.comp b/projects/bloom/resources/shaders/lensFlares.comp
new file mode 100644
index 0000000000000000000000000000000000000000..ce27d8850b709f61332d467914ddc944dc63109f
--- /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/resources/shaders/perMeshResources.inc b/projects/bloom/resources/shaders/perMeshResources.inc
new file mode 100644
index 0000000000000000000000000000000000000000..95e4fb7c27009965659d14a9c72acfec950c37e3
--- /dev/null
+++ b/projects/bloom/resources/shaders/perMeshResources.inc
@@ -0,0 +1,2 @@
+layout(set=1, binding=0) uniform texture2D  albedoTexture;
+layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.frag b/projects/bloom/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3e95b4508f112c1ed9aa4a7050a98fa789dccd09
--- /dev/null
+++ b/projects/bloom/resources/shaders/shader.frag
@@ -0,0 +1,45 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "perMeshResources.inc"
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in vec3 passPos;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform sunBuffer {
+    vec3 L; float padding;
+    mat4 lightMatrix;
+};
+layout(set=0, binding=1) uniform texture2D  shadowMap;
+layout(set=0, binding=2) uniform sampler    shadowMapSampler;
+
+float shadowTest(vec3 worldPos){
+    vec4 lightPos = lightMatrix * vec4(worldPos, 1);
+    lightPos /= lightPos.w;
+    lightPos.xy = lightPos.xy * 0.5 + 0.5;
+    
+    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
+        return 1;
+    }
+    
+    lightPos.z = clamp(lightPos.z, 0, 1);
+    
+    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
+    float bias = 0.01f;
+    shadowMapSample += bias;
+    return shadowMapSample < lightPos.z ? 0 : 1;
+}
+
+void main()	{
+    vec3 N = normalize(passNormal);
+    vec3 sunColor = vec3(10);
+    vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
+    sun *= shadowTest(passPos);
+    vec3 ambient = vec3(0.05);
+    vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+	outColor = albedo * (sun + ambient);
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.vert b/projects/bloom/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..926f86af2860cb57c44d2d5ee78712b6ae155e5c
--- /dev/null
+++ b/projects/bloom/resources/shaders/shader.vert
@@ -0,0 +1,22 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out vec3 passPos;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 model;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = mat3(model) * inNormal;    // assuming no weird stuff like shearing or non-uniform scaling
+    passUV      = inUV;
+    passPos     = (model * vec4(inPosition, 1)).xyz;
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shadow.frag b/projects/bloom/resources/shaders/shadow.frag
new file mode 100644
index 0000000000000000000000000000000000000000..848f853f556660b4900b5db7fb6fc98d57c1cd5b
--- /dev/null
+++ b/projects/bloom/resources/shaders/shadow.frag
@@ -0,0 +1,6 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+void main()	{
+
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shadow.vert b/projects/bloom/resources/shaders/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8
--- /dev/null
+++ b/projects/bloom/resources/shaders/shadow.vert
@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/upsample.comp b/projects/bloom/resources/shaders/upsample.comp
new file mode 100644
index 0000000000000000000000000000000000000000..0ddeedb5b5af9e476dc19012fed6430544006c0e
--- /dev/null
+++ b/projects/bloom/resources/shaders/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/bloom/src/BloomAndFlares.cpp b/projects/bloom/src/BloomAndFlares.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6f26db9de0f2c8334b6dd7e5dd6cf4b6f48baedc
--- /dev/null
+++ b/projects/bloom/src/BloomAndFlares.cpp
@@ -0,0 +1,274 @@
+#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_DownsampleDescSets.size(); 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());
+
+    const uint32_t upsampleMipLevels = std::min(
+    		static_cast<uint32_t>(m_UpsampleDescSets.size() - 1),
+    		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 0000000000000000000000000000000000000000..756b1ca154ea5232df04eb09a88bb743c5bd28aa
--- /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
new file mode 100644
index 0000000000000000000000000000000000000000..7a17a51f1c7d638575c0b5aafcdca49b589533ef
--- /dev/null
+++ b/projects/bloom/src/main.cpp
@@ -0,0 +1,419 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#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) {
+	const char* applicationName = "Bloom";
+
+	uint32_t windowWidth = 1920;
+	uint32_t windowHeight = 1080;
+	
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    
+    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
+    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camIndex).setYaw(180.0f);
+	
+	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	vkcv::asset::Scene scene;
+	int result = vkcv::asset::loadScene(path, scene);
+
+	if (result == 1) {
+		std::cout << "Scene loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Scene loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	// build index and vertex buffers
+	assert(!scene.vertexGroups.empty());
+	std::vector<std::vector<uint8_t>> vBuffers;
+	std::vector<std::vector<uint8_t>> iBuffers;
+
+	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
+	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
+	std::vector<vkcv::asset::VertexAttribute> vAttributes;
+
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+
+		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
+		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
+
+		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
+
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+		});
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
+	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
+		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			group.vertexBuffer.data.size()));
+		vertexBuffers.back().fill(group.vertexBuffer.data);
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
+	for (const auto& dataBuffer : iBuffers) {
+		indexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			dataBuffer.size()));
+		indexBuffers.back().fill(dataBuffer);
+	}
+
+	int vertexBufferIndex = 0;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+			vAttributes.push_back(attribute);
+			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
+		}
+		vertexBufferBindings.push_back(vBufferBindings);
+		vBufferBindings.clear();
+		vertexBufferIndex++;
+	}
+
+	const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32;
+	const vkcv::AttachmentDescription color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		colorBufferFormat
+	);
+	
+	const vk::Format depthBufferFormat = vk::Format::eD32Sfloat;
+	const vkcv::AttachmentDescription depth_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		depthBufferFormat
+	);
+
+	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment });
+	vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition);
+
+	vkcv::shader::GLSLCompiler compiler;
+
+	vkcv::ShaderProgram forwardProgram;
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), 
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		forwardProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		forwardProgram.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = forwardProgram.getVertexAttachments();
+
+	std::vector<vkcv::VertexBinding> vertexBindings;
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+	}
+	const vkcv::VertexLayout vertexLayout (vertexBindings);
+
+	// shadow map
+	vkcv::SamplerHandle shadowSampler = core.createSampler(
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerMipmapMode::NEAREST,
+		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+	);
+	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
+	const uint32_t shadowMapResolution = 1024;
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
+
+	// light info buffer
+	struct LightInfo {
+		glm::vec3 direction;
+		float padding;
+		glm::mat4 lightMatrix;
+	};
+	LightInfo lightInfo;
+	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+
+	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
+		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
+
+	vkcv::DescriptorWrites forwardDescriptorWrites;
+	forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) };
+	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
+	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
+	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
+
+	vkcv::SamplerHandle colorSampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	// prepare per mesh descriptor sets
+	std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets;
+	std::vector<vkcv::Image> sceneImages;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		perMeshDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1]));
+
+		const auto& material = scene.materials[vertexGroup.materialIndex];
+
+		int baseColorIndex = material.baseColor;
+		if (baseColorIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
+			baseColorIndex = 0;
+		}
+
+		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
+
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
+		sceneImages.back().fill(sceneTexture.data.data());
+
+		vkcv::DescriptorWrites setWrites;
+		setWrites.sampledImageWrites = {
+			vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle())
+		};
+		setWrites.samplerWrites = {
+			vkcv::SamplerDescriptorWrite(1, colorSampler),
+		};
+		core.writeDescriptorSet(perMeshDescriptorSets.back(), setWrites);
+	}
+
+	const vkcv::PipelineConfig forwardPipelineConfig {
+		forwardProgram,
+		windowWidth,
+		windowHeight,
+		forwardPass,
+		vertexLayout,
+		{	core.getDescriptorSet(forwardShadingDescriptorSet).layout, 
+			core.getDescriptorSet(perMeshDescriptorSets[0]).layout },
+		true
+	};
+	
+	vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
+	
+	if (!forwardPipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ImageHandle depthBuffer       = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
+	vkcv::ImageHandle colorBuffer       = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::ShaderProgram shadowShader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+	const vkcv::PipelineConfig shadowPipeConfig{
+		shadowShader,
+		shadowMapResolution,
+		shadowMapResolution,
+		shadowPass,
+		vertexLayout,
+		{},
+		false
+	};
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	// gamma correction compute shader
+	vkcv::ShaderProgram gammaCorrectionProgram;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/gammaCorrection.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		gammaCorrectionProgram.addShader(shaderStage, path);
+	});
+	vkcv::DescriptorSetHandle gammaCorrectionDescriptorSet = core.createDescriptorSet(gammaCorrectionProgram.getReflectedDescriptors()[0]);
+	vkcv::PipelineHandle gammaCorrectionPipeline = core.createComputePipeline(gammaCorrectionProgram,
+		{ core.getDescriptorSet(gammaCorrectionDescriptorSet).layout });
+
+    BloomAndFlares baf(&core, colorBufferFormat, windowWidth, windowHeight);
+
+
+	// model matrices per mesh
+	std::vector<glm::mat4> modelMatrices;
+	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
+	for (const auto& mesh : scene.meshes) {
+		const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]);
+		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
+			modelMatrices[vertexGroupIndex] = m;
+		}
+	}
+
+	// prepare drawcalls
+	std::vector<vkcv::Mesh> meshes;
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+		vkcv::Mesh mesh(
+			vertexBufferBindings[i], 
+			indexBuffers[i].getVulkanHandle(), 
+			scene.vertexGroups[i].numIndices);
+		meshes.push_back(mesh);
+	}
+
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
+	for (int i = 0; i < meshes.size(); i++) {
+		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
+			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
+			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
+	}
+
+	auto start = std::chrono::system_clock::now();
+	const auto appStartTime = start;
+	while (window.isWindowOpen()) {
+		vkcv::Window::pollEvents();
+
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
+			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
+
+			baf.updateImageDimensions(swapchainWidth, swapchainHeight);
+
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
+		
+		const float sunTheta = 0.0001f * static_cast<float>(duration.count());
+		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
+
+		const float shadowProjectionSize = 20.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
+		for (const auto& m : modelMatrices) {
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix * m);
+		}
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
+
+		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		// shadow map
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
+		// main pass
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+            forwardPass,
+            forwardPipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+
+        const uint32_t gammaCorrectionLocalGroupSize = 8;
+        const uint32_t gammaCorrectionDispatchCount[3] = {
+                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowWidth) / static_cast<float>(gammaCorrectionLocalGroupSize))),
+                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowHeight) / static_cast<float>(gammaCorrectionLocalGroupSize))),
+                1
+        };
+
+        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(
+			cmdStream, 
+			gammaCorrectionPipeline, 
+			gammaCorrectionDispatchCount,
+			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gammaCorrectionDescriptorSet).vulkanHandle) },
+			vkcv::PushConstantData(nullptr, 0));
+
+		// present and end
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		core.endFrame();
+	}
+	
+	return 0;
+}
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eccc0af7331dc140f3a15ddf12c5645e685abc90
--- /dev/null
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -0,0 +1,317 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "First Mesh";
+
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+	
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    
+    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
+    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camIndex).setYaw(180.0f);
+	
+	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::asset::Scene mesh;
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+	int result = vkcv::asset::loadScene(path, mesh);
+
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	assert(mesh.vertexGroups.size() > 0);
+	auto vertexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			mesh.vertexGroups[0].vertexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+
+	auto indexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			mesh.vertexGroups[0].indexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+	
+	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
+	
+	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+	});
+
+	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		vkcv::VertexBufferBinding(attributes[0].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(attributes[1].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(attributes[2].offset, vertexBuffer.getVulkanHandle()) };
+
+	const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchain().getFormat()
+	);
+	
+	const vkcv::AttachmentDescription depth_attachment(
+			vkcv::AttachmentOperation::STORE,
+			vkcv::AttachmentOperation::CLEAR,
+			vk::Format::eD32Sfloat
+	);
+
+	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
+
+	if (!firstMeshPass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram firstMeshProgram{};
+    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.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 firstMeshLayout (bindings);
+
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] };
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
+
+	const vkcv::PipelineConfig firstMeshPipelineConfig {
+        firstMeshProgram,
+		windowWidth,
+		windowHeight,
+        firstMeshPass,
+        firstMeshLayout,
+		{ core.getDescriptorSet(descriptorSet).layout },
+		true
+	};
+	
+	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
+	
+	if (!firstMeshPipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	//vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
+	//texture.fill(mesh.texture_hack.img);
+    vkcv::asset::Texture &tex = mesh.textures[0];
+    vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+    texture.fill(tex.data.data());
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+    vkcv::SamplerHandle shadowSampler = core.createSampler(
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerMipmapMode::NEAREST,
+        vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+    );
+
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+
+	const std::vector<glm::vec3> instancePositions = {
+		glm::vec3( 0.f, -2.f, 0.f),
+		glm::vec3( 3.f,  0.f, 0.f),
+		glm::vec3(-3.f,  0.f, 0.f),
+		glm::vec3( 0.f,  2.f, 0.f),
+		glm::vec3( 0.f, -5.f, 0.f)
+	};
+
+	std::vector<glm::mat4> modelMatrices;
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
+	for (const auto& position : instancePositions) {
+		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
+		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {}));
+	}
+
+	modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	vkcv::ShaderProgram shadowShader;
+	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
+	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
+
+	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+
+	const uint32_t shadowMapResolution = 1024;
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
+	const vkcv::PipelineConfig shadowPipeConfig {
+		shadowShader, 
+		shadowMapResolution, 
+		shadowMapResolution, 
+		shadowPass,
+        firstMeshLayout,
+		{},
+		false
+	};
+	
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	struct LightInfo {
+		glm::vec3 direction;
+		float padding;
+		glm::mat4 lightMatrix;
+	};
+	LightInfo lightInfo;
+	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+
+	vkcv::DescriptorWrites setWrites;
+	setWrites.sampledImageWrites    = { 
+        vkcv::SampledImageDescriptorWrite(0, texture.getHandle()),
+        vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) };
+	setWrites.samplerWrites         = { 
+        vkcv::SamplerDescriptorWrite(1, sampler), 
+        vkcv::SamplerDescriptorWrite(4, shadowSampler) };
+    setWrites.uniformBufferWrites   = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) };
+	core.writeDescriptorSet(descriptorSet, setWrites);
+
+	auto start = std::chrono::system_clock::now();
+	const auto appStartTime = start;
+	while (window.isWindowOpen()) {
+		window.pollEvents();
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
+			
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+		
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
+		
+		const float sunTheta = 0.001f * static_cast<float>(duration.count());
+		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
+
+		const float shadowProjectionSize = 5.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+		
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
+		for (const auto& m : modelMatrices) {
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix* m);
+		}
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+
+		vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+            firstMeshPass,
+            firstMeshPipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		core.endFrame();
+	}
+	
+	return 0;
+}
diff --git a/projects/first_mesh/resources/Szene/Szene.bin b/projects/first_mesh/resources/Szene/Szene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..c87d27637516b0bbf864251dd162773f5cc53e06
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/Szene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
+size 70328
diff --git a/projects/first_mesh/resources/Szene/Szene.gltf b/projects/first_mesh/resources/Szene/Szene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/Szene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
+size 7421
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc.jpg b/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg b/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 107cd432e1bc902692bb5b1fa694120ddf24038b..dc43c905784525a34732bc0e66343fbdcc17a639 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -8,32 +8,29 @@
 int main(int argc, const char** argv) {
 	const char* applicationName = "First Mesh";
 
-	const int windowWidth = 800;
-	const int windowHeight = 600;
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+
 	vkcv::Window window = vkcv::Window::create(
 		applicationName,
 		windowWidth,
 		windowHeight,
-		false
+		true
 	);
 
-	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-
-	window.initEvents();
-
 	vkcv::Core core = vkcv::Core::create(
 		window,
 		applicationName,
 		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
 		{},
 		{ "VK_KHR_swapchain" }
 	);
 
-	vkcv::asset::Mesh mesh;
+	vkcv::asset::Scene mesh;
 
 	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadMesh(path, mesh);
+	int result = vkcv::asset::loadScene(path, mesh);
 
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
@@ -43,7 +40,7 @@ int main(int argc, const char** argv) {
 		return 1;
 	}
 
-	assert(mesh.vertexGroups.size() > 0);
+	assert(!mesh.vertexGroups.empty());
 	auto vertexBuffer = core.createBuffer<uint8_t>(
 			vkcv::BufferType::VERTEX,
 			mesh.vertexGroups[0].vertexBuffer.data.size(),
@@ -62,65 +59,70 @@ int main(int argc, const char** argv) {
 
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentLayout::UNDEFINED,
-		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
-		vkcv::AttachmentLayout::PRESENTATION,
 		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchainImageFormat()
+		core.getSwapchain().getFormat()
 	);
 	
 	const vkcv::AttachmentDescription depth_attachment(
-			vkcv::AttachmentLayout::UNDEFINED,
-			vkcv::AttachmentLayout::DEPTH_STENCIL_ATTACHMENT,
-			vkcv::AttachmentLayout::DEPTH_STENCIL_ATTACHMENT,
 			vkcv::AttachmentOperation::STORE,
 			vkcv::AttachmentOperation::CLEAR,
 			vk::Format::eD32Sfloat
 	);
 
-	vkcv::PassConfig trianglePassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
 
-	if (!trianglePass) {
+	if (!firstMeshPass) {
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	vkcv::ShaderProgram triangleShaderProgram{};
-	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	vkcv::ShaderProgram firstMeshProgram{};
+    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
 	
 	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
 	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::VertexAttribute& x, const vkcv::VertexAttribute& y) {
+	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
 		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
 	});
 
-	vkcv::DescriptorSetConfig setConfig({
-		vkcv::DescriptorBinding(vkcv::DescriptorType::IMAGE_SAMPLED,	1, vkcv::ShaderStage::FRAGMENT),
-		vkcv::DescriptorBinding(vkcv::DescriptorType::SAMPLER,			1, vkcv::ShaderStage::FRAGMENT)
-	});
-	vkcv::ResourcesHandle set = core.createResourceDescription({ setConfig });
-
-	const vkcv::PipelineConfig trianglePipelineDefinition(
-		triangleShaderProgram, 
-		windowWidth,
-		windowHeight,
-		trianglePass,
-		mesh.vertexGroups[0].vertexBuffer.attributes,
-		{ core.getDescritorSetLayout(set, 0) });
-	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.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 firstMeshLayout (bindings);
+
+	uint32_t setID = 0;
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[setID] };
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
+
+	const vkcv::PipelineConfig firstMeshPipelineConfig {
+        firstMeshProgram,
+        UINT32_MAX,
+        UINT32_MAX,
+        firstMeshPass,
+        {firstMeshLayout},
+		{ core.getDescriptorSet(descriptorSet).layout },
+		true
+	};
+	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
 	
-	if (!trianglePipeline) {
+	if (!firstMeshPipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 	
-	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	texture.fill(mesh.texture_hack.img);
+	// FIXME There should be a test here to make sure there is at least 1
+	// texture in the mesh.
+	vkcv::asset::Texture &tex = mesh.textures[0];
+	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+	texture.fill(tex.data.data());
+	texture.generateMipChainImmediate();
+	texture.switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
 
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
@@ -129,41 +131,73 @@ int main(int argc, const char** argv) {
 		vkcv::SamplerAddressMode::REPEAT
 	);
 
-	std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[0].offset, vertexBuffer.getHandle() },
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[1].offset, vertexBuffer.getHandle() },
-		{ mesh.vertexGroups[0].vertexBuffer.attributes[2].offset, vertexBuffer.getHandle() }
-	};
+	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[0].offset), vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[1].offset), vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) };
 
 	vkcv::DescriptorWrites setWrites;
 	setWrites.sampledImageWrites	= { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
 	setWrites.samplerWrites			= { vkcv::SamplerDescriptorWrite(1, sampler) };
-	core.writeResourceDescription(set, 0, setWrites);
 
-	auto start = std::chrono::system_clock::now();
+	core.writeDescriptorSet(descriptorSet, setWrites);
+
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight, 1, false).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
+
+	vkcv::DescriptorSetUsage    descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+	vkcv::DrawcallInfo          drawcall(renderMesh, { descriptorUsage });
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
+
+    auto start = std::chrono::system_clock::now();
+    
 	while (window.isWindowOpen()) {
-		core.beginFrame();
-		window.pollEvents();
+        window.pollEvents();
+		
+		if(window.getHeight() == 0 || window.getWidth() == 0)
+			continue;
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
+			
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+  
 		auto end = std::chrono::system_clock::now();
-		auto deltatime = end - start;
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
 		start = end;
-		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
-		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
-
-		core.renderMesh(
-			trianglePass,
-			trianglePipeline,
-			windowWidth,
-			windowHeight,
-			sizeof(mvp),
-			&mvp,
-			vertexBufferBindings,
-			indexBuffer.getHandle(),
-			mesh.vertexGroups[0].numIndices,
-			set,
-			0
-		);
-
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+
+		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			firstMeshPass,
+			firstMeshPipeline,
+			pushConstantData,
+			{ drawcall },
+			renderTargets);
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
 		core.endFrame();
 	}
 	
diff --git a/projects/first_scene/.gitignore b/projects/first_scene/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b0d802667a184267e6ee764264e976dfead8e9dd
--- /dev/null
+++ b/projects/first_scene/.gitignore
@@ -0,0 +1 @@
+first_scene
diff --git a/projects/first_scene/CMakeLists.txt b/projects/first_scene/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8b90739750011a36b4c1d9e0bff7cba986074228
--- /dev/null
+++ b/projects/first_scene/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+project(first_scene)
+
+# 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(first_scene src/main.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(first_scene PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(first_scene 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(first_scene PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..8258525f22097f2382ec5c26ad0df7eb50718642
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0ce87f6407ee40ffa60983587aeb52333d59b4b1c01a53e11f4bb227ba1099d9
+size 109
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..620fe7621cf35409ae8c04099dc8bc4bbc04343b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:68a0064d457a6f7994814b07d943deda778754128935689874334300ede6161d
+size 2332064
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..5570e88c569036b9d00155ef6113013aa23f2503
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42c2715635081eb29c4489ce631798b0e9c881460efc0aa63d0e81641a0dcfe9
+size 108
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..d07681f5bfa25c279321c3074e21e4900c5eb0fa
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:15b0133e140899c47ccf35b0f99a7e337e3110ae089f45d27faf9983f3e0a1f7
+size 770758
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..1845e8a7d586a0d23300ad9d04a82f1d5048fcf5
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ea7c82c0f9e25afa401470df1fb6903f508fa138d21ad30f57a9153b0395b198
+size 521315
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..1c800c0489693b66d6163bdc2156d453b68ca19b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:119efbbc020244ff9b7ff16ac9795a6d4b1808d1b90d81d20d2c874d0dc8a924
+size 1693468
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..36f46ebf25158c78bc26d83860e1c08b2c9e96cf
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:99896468c7d47dd5391d585eecf149f420eca3bfec31923c21fa86c45fe02d0f
+size 108
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..28c205d4e70867ec58448ca8ba2c2117c611a367
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
+size 90
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0104189a1190c160152101e9e328e81ed89121b
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6eff6ccd12d8b39d60ae5ee91edd73d4d7838fcb5d9bc6ff0e671bdf009134e9
+size 109
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa13483d25595ee6b3a00e7fe6ce48aed7b6aaca
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fe92c40ff4032fdaf10eeafd943657a0c6e0bfb3f38770f5654aa943a660f421
+size 59419
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..b91d0ac62fbeabbef1ed78f1343f919836cbca40
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ca7d436a68a2a1237aee6e763b2954f01666b21f1dbd46929a322ea277483d2
+size 779227
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..6efd967984ee2e68b89de9e471b93396c13ca69a
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b01fc6482054c64d7407b283731e57fce0601a8db28b6781c14fae3c6b30b0fe
+size 504362
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0e0c4f4134cb99d3765d6f078f71efab8861bf1
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df138ee68c1d455652d1b9ae3dd03e93fcd2f6a0d8a1f12e3710f39143088674
+size 1593466
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f310653b5211575da3cab2f6deb47ec6826a936
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6af5da97cbb25d79aea2dde8dd71ecbd495334fe34e99497ba17821be93fd7fd
+size 2696676
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png b/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e4f0fcb312b8f8bd0df965bfe6b8ac62ec5df4d
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2796affdfdcf6bc805176d9f85505680b5ee52eeec625e9eaeea4f0ff3854883
+size 108
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png b/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..28c205d4e70867ec58448ca8ba2c2117c611a367
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
+size 90
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.bin b/projects/first_scene/resources/Cutlery/cutlerySzene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..ab9a0aa47205e8f3064d2f16a950d4733fb4f472
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f545b986e0a1ac5bff5d49693a52042aa37878425818f72c69c243da20d1f99d
+size 183324
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.glb b/projects/first_scene/resources/Cutlery/cutlerySzene.glb
new file mode 100644
index 0000000000000000000000000000000000000000..b0c5f345aaaa3f96d7158a0992ee124aae99a69a
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fb1bad604192ca36222c0ca485ba87b846ecbd11ee8254327e04e3c993b00116
+size 11150396
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.gltf b/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..53e339cda4511f3f1a8670b36469e184aac530e2
--- /dev/null
+++ b/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c77cd60e2327daca1a01044e45f2c38655f7b781bd07985fc0135328a8a96b57
+size 34312
diff --git a/projects/first_scene/resources/Sponza/Sponza.bin b/projects/first_scene/resources/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cfedd26ca5a67b6d0a47d44d13a75e14a141717a
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
+size 6347696
diff --git a/projects/first_scene/resources/Sponza/Sponza.gltf b/projects/first_scene/resources/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..172ea07e21c94465211c860cd805355704cef230
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
+size 713962
diff --git a/projects/first_scene/resources/Sponza/background.png b/projects/first_scene/resources/Sponza/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..b64def129da38f4e23d89e21b4af1039008a4327
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/background.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
+size 1417666
diff --git a/projects/first_scene/resources/Sponza/chain_texture.png b/projects/first_scene/resources/Sponza/chain_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1768cff78e0614ad707eca8602a4c4edab5e5
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/chain_texture.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
+size 490895
diff --git a/projects/first_scene/resources/Sponza/lion.png b/projects/first_scene/resources/Sponza/lion.png
new file mode 100644
index 0000000000000000000000000000000000000000..c49c7f0ed31e762e19284d0d3624fbc47664e56b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/lion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
+size 2088728
diff --git a/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png b/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cde4c7a6511e9a5f03c63ad996437fcdba3ce2df
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
+size 1928829
diff --git a/projects/first_scene/resources/Sponza/sponza_arch_diff.png b/projects/first_scene/resources/Sponza/sponza_arch_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9bda2918d226039f9e2d03902d377b706fab6
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_arch_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
+size 1596151
diff --git a/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png b/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..59de631ffac4414cabf69b2dc794c46fc187d6cb
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
+size 1872074
diff --git a/projects/first_scene/resources/Sponza/sponza_column_a_diff.png b/projects/first_scene/resources/Sponza/sponza_column_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..01a82432d3f9939bbefe850bdb900f1ff9a3f6db
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
+size 1840992
diff --git a/projects/first_scene/resources/Sponza/sponza_column_b_diff.png b/projects/first_scene/resources/Sponza/sponza_column_b_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a660cce2a5a9b8997772c746058ce23e7d45d7
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_b_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
+size 2170433
diff --git a/projects/first_scene/resources/Sponza/sponza_column_c_diff.png b/projects/first_scene/resources/Sponza/sponza_column_c_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc46fd979044a938d3adca7601689e71504e48bf
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_column_c_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
+size 2066950
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..384c8c2c051160d530eb3ac8b05c9c60752a2d2b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
+size 9510905
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..af842e9f5fe18c1f609875e00899a6770fa4488b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
+size 9189263
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png b/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9b6391a199407637fa71033d79fb58b8b4f0d7
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
+size 8785458
diff --git a/projects/first_scene/resources/Sponza/sponza_details_diff.png b/projects/first_scene/resources/Sponza/sponza_details_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..12656686362c3e0a297e060491f33bd7351551f9
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_details_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
+size 1522643
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..879d16ef84722a4fc13e83a771778de326e4bc54
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
+size 2229440
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..3311287a219d2148620b87fe428fea071688d051
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
+size 2267382
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png b/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..de110f369004388dae4cd5067c63428db3a07834
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
+size 2258220
diff --git a/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png b/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6e0812a0df80346318baa3cb50a6888afc58f8
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
+size 1425673
diff --git a/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png b/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..788ed764f79ba724f04a2d603076a5b85013e188
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
+size 1996838
diff --git a/projects/first_scene/resources/Sponza/sponza_roof_diff.png b/projects/first_scene/resources/Sponza/sponza_roof_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5b84261fdd1cc776a94b3ce398c7806b895f9a3
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_roof_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
+size 2320533
diff --git a/projects/first_scene/resources/Sponza/sponza_thorn_diff.png b/projects/first_scene/resources/Sponza/sponza_thorn_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9142674a7d4a6f94a48c5152cf0300743b597a
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/sponza_thorn_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
+size 635001
diff --git a/projects/first_scene/resources/Sponza/vase_dif.png b/projects/first_scene/resources/Sponza/vase_dif.png
new file mode 100644
index 0000000000000000000000000000000000000000..61236a81cb324af8797b05099cd264cefe189e56
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_dif.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
+size 1842101
diff --git a/projects/first_scene/resources/Sponza/vase_hanging.png b/projects/first_scene/resources/Sponza/vase_hanging.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a3cee71d8213225090c74f8c0dce33b9d44378
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_hanging.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
+size 1432804
diff --git a/projects/first_scene/resources/Sponza/vase_plant.png b/projects/first_scene/resources/Sponza/vase_plant.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ad95e702e229f1ebd803e5203a266d15f2c07b9
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_plant.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
+size 998089
diff --git a/projects/first_scene/resources/Sponza/vase_round.png b/projects/first_scene/resources/Sponza/vase_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..c17953abc000c44b8991e23c136c2b67348f3d1b
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/vase_round.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
+size 1871399
diff --git a/projects/first_scene/resources/Szene/Szene.bin b/projects/first_scene/resources/Szene/Szene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..c87d27637516b0bbf864251dd162773f5cc53e06
--- /dev/null
+++ b/projects/first_scene/resources/Szene/Szene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
+size 70328
diff --git a/projects/first_scene/resources/Szene/Szene.gltf b/projects/first_scene/resources/Szene/Szene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9
--- /dev/null
+++ b/projects/first_scene/resources/Szene/Szene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
+size 7421
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc.jpg b/projects/first_scene/resources/Szene/boards2_vcyc.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_scene/resources/Szene/boards2_vcyc.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg b/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/first_scene/resources/shaders/compile.bat b/projects/first_scene/resources/shaders/compile.bat
new file mode 100644
index 0000000000000000000000000000000000000000..b4521235c40fe5fb163bab874560c2f219b7517f
--- /dev/null
+++ b/projects/first_scene/resources/shaders/compile.bat
@@ -0,0 +1,3 @@
+%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+pause
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/frag.spv b/projects/first_scene/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..087e4e22fb2fcec27d99b3ff2aa1a705fe755796
Binary files /dev/null and b/projects/first_scene/resources/shaders/frag.spv differ
diff --git a/projects/first_scene/resources/shaders/shader.frag b/projects/first_scene/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b5494bea7d6497e2e3dcd8559606864a71adb74e
--- /dev/null
+++ b/projects/first_scene/resources/shaders/shader.frag
@@ -0,0 +1,15 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform texture2D  meshTexture;
+layout(set=0, binding=1) uniform sampler    textureSampler;
+
+void main()	{
+	outColor = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+    //outColor = passNormal * 0.5 + 0.5;
+}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/shader.vert b/projects/first_scene/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..76855152253b48b7400f016d063ed4f0e507435e
--- /dev/null
+++ b/projects/first_scene/resources/shaders/shader.vert
@@ -0,0 +1,19 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    passUV      = inUV;
+}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/vert.spv b/projects/first_scene/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..374c023e14b351eb43cbcda5951cbb8b3d6f96a1
Binary files /dev/null and b/projects/first_scene/resources/shaders/vert.spv differ
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..420400cdd04865ddd48eec7cf6000e36417d8095
--- /dev/null
+++ b/projects/first_scene/src/main.cpp
@@ -0,0 +1,264 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/Logger.hpp>
+
+glm::mat4 arrayTo4x4Matrix(std::array<float,16> array){
+    glm::mat4 matrix;
+    for (int i = 0; i < 4; i++){
+        for (int j = 0; j < 4; j++){
+            matrix[i][j] = array[j * 4 + i];
+        }
+    }
+    return matrix;
+}
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "First Scene";
+
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+	vkcv::camera::CameraManager cameraManager(window);
+	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f);
+	
+	cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f);
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::asset::Scene scene;
+
+	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	int result = vkcv::asset::loadScene(path, scene);
+
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	assert(!scene.vertexGroups.empty());
+	std::vector<std::vector<uint8_t>> vBuffers;
+	std::vector<std::vector<uint8_t>> iBuffers;
+
+	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
+	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
+	std::vector<vkcv::asset::VertexAttribute> vAttributes;
+
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+
+		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
+		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
+
+		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
+
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+			});
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
+	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
+		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			group.vertexBuffer.data.size()));
+		vertexBuffers.back().fill(group.vertexBuffer.data);
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
+	for (const auto& dataBuffer : iBuffers) {
+		indexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			dataBuffer.size()));
+		indexBuffers.back().fill(dataBuffer);
+	}
+
+	int vertexBufferIndex = 0;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+			vAttributes.push_back(attribute);
+			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
+		}
+		vertexBufferBindings.push_back(vBufferBindings);
+		vBufferBindings.clear();
+		vertexBufferIndex++;
+	}
+
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchain().getFormat()
+	);
+
+	const vkcv::AttachmentDescription depth_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		vk::Format::eD32Sfloat
+	);
+
+	vkcv::PassConfig scenePassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle scenePass = core.createPass(scenePassDefinition);
+
+	if (!scenePass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram sceneShaderProgram{};
+	sceneShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+	sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.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 sceneLayout(bindings);
+
+	uint32_t setID = 0;
+
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] };
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	std::vector<vkcv::Image> sceneImages;
+	std::vector<vkcv::DescriptorSetHandle> descriptorSets;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
+
+		const auto& material = scene.materials[vertexGroup.materialIndex];
+
+		int baseColorIndex = material.baseColor;
+		if (baseColorIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
+			baseColorIndex = 0;
+		}
+
+		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
+
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
+		sceneImages.back().fill(sceneTexture.data.data());
+
+		vkcv::DescriptorWrites setWrites;
+		setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
+		setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
+		core.writeDescriptorSet(descriptorSets.back(), setWrites);
+	}
+
+	const vkcv::PipelineConfig scenePipelineDefsinition{
+		sceneShaderProgram,
+		UINT32_MAX,
+		UINT32_MAX,
+		scenePass,
+		{sceneLayout},
+		{ core.getDescriptorSet(descriptorSets[0]).layout },
+		true };
+	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition);
+	
+	if (!scenePipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+    std::vector<vkcv::DrawcallInfo> drawcalls;
+	for(int i = 0; i < scene.vertexGroups.size(); i++){
+        vkcv::Mesh renderMesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices);
+
+        vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSets[i]).vulkanHandle);
+
+	    drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}));
+	}
+
+	std::vector<glm::mat4> modelMatrices;
+	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
+	for (const auto &mesh : scene.meshes) {
+		const glm::mat4 m = arrayTo4x4Matrix(mesh.modelMatrix);
+		for (const auto &vertexGroupIndex : mesh.vertexGroups) {
+			modelMatrices[vertexGroupIndex] = m;
+		}
+	}
+	std::vector<glm::mat4> mvp;
+
+	auto start = std::chrono::system_clock::now();
+	while (window.isWindowOpen()) {
+        vkcv::Window::pollEvents();
+		
+		if(window.getHeight() == 0 || window.getWidth() == 0)
+			continue;
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
+			
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+  
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		glm::mat4 vp = cameraManager.getActiveCamera().getMVP();
+
+		mvp.clear();
+        for (const auto& m : modelMatrices) {
+            mvp.push_back(vp * m);
+        }
+
+		vkcv::PushConstantData pushConstantData((void*)mvp.data(), sizeof(glm::mat4));
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			scenePass,
+			scenePipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		core.endFrame();
+	}
+	
+	return 0;
+}
diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt
index e7c8373e085df6497060b8d1d8164cf740dfb01f..ba8c83c06fc804082e6a0a14c3c0414899ef3057 100644
--- a/projects/first_triangle/CMakeLists.txt
+++ b/projects/first_triangle/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include})
+target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera)
+target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera vkcv_shader_compiler vkcv_gui)
diff --git a/projects/first_triangle/shaders/comp.spv b/projects/first_triangle/shaders/comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b414e36b2bea66dab00746298e536d029091e0fd
Binary files /dev/null and b/projects/first_triangle/shaders/comp.spv differ
diff --git a/projects/first_triangle/shaders/compile.bat b/projects/first_triangle/shaders/compile.bat
index b4521235c40fe5fb163bab874560c2f219b7517f..17743a7c49cdfc6e091c43a42a0adb755a731682 100644
--- a/projects/first_triangle/shaders/compile.bat
+++ b/projects/first_triangle/shaders/compile.bat
@@ -1,3 +1,4 @@
 %VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
 %VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.comp -o comp.spv
 pause
\ No newline at end of file
diff --git a/projects/first_triangle/shaders/shader.comp b/projects/first_triangle/shaders/shader.comp
new file mode 100644
index 0000000000000000000000000000000000000000..fad6cd0815f2f09bf92dcc3171e2e3723f5466df
--- /dev/null
+++ b/projects/first_triangle/shaders/shader.comp
@@ -0,0 +1,25 @@
+#version 440
+
+layout(std430, binding = 0) buffer testBuffer
+{ 
+    float test1[10];
+    float test2[10];
+    float test3[10];
+};
+
+layout( push_constant ) uniform constants{
+    float pushConstant;
+};
+
+layout(local_size_x = 5) in;
+
+void main(){
+
+    if(gl_GlobalInvocationID.x >= 10){
+        return;
+    }
+
+    test1[gl_GlobalInvocationID.x] = gl_GlobalInvocationID.x;
+    test2[gl_GlobalInvocationID.x] = 69;  // nice!
+    test3[gl_GlobalInvocationID.x] = pushConstant;    
+}
\ No newline at end of file
diff --git a/projects/first_triangle/shaders/shader.frag b/projects/first_triangle/shaders/shader.frag
index d26446a73020111695aa2c86166205796dfa5e44..080678beb011afe4b03aed3bf7ae7148b77932dc 100644
--- a/projects/first_triangle/shaders/shader.frag
+++ b/projects/first_triangle/shaders/shader.frag
@@ -4,6 +4,6 @@
 layout(location = 0) in vec3 fragColor;
 layout(location = 0) out vec4 outColor;
 
-void main()	{
+void main() {
 	outColor = vec4(fragColor, 1.0);
 }
\ No newline at end of file
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 2c071b727f7e41e7eb4dee17a2da3302f615ffa0..20cfdddf5c1baa9e8727312daa36de94bd56672f 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -4,6 +4,9 @@
 #include <vkcv/camera/CameraManager.hpp>
 #include <chrono>
 
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/gui/GUI.hpp>
+
 int main(int argc, const char** argv) {
 	const char* applicationName = "First Triangle";
 
@@ -16,10 +19,6 @@ int main(int argc, const char** argv) {
 		false
 	);
 
-	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-
-	window.initEvents();
-
 	vkcv::Core core = vkcv::Core::create(
 		window,
 		applicationName,
@@ -28,6 +27,8 @@ int main(int argc, const char** argv) {
 		{},
 		{ "VK_KHR_swapchain" }
 	);
+	
+	vkcv::gui::GUI gui (core, window);
 
 	const auto& context = core.getContext();
 	const vk::Instance& instance = context.getInstance();
@@ -79,12 +80,9 @@ int main(int argc, const char** argv) {
 
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentLayout::UNDEFINED,
-		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
-		vkcv::AttachmentLayout::PRESENTATION,
 		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchainImageFormat());
+		core.getSwapchain().getFormat());
 
 	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
 	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
@@ -96,27 +94,58 @@ int main(int argc, const char** argv) {
 	}
 
 	vkcv::ShaderProgram triangleShaderProgram{};
-	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv"));
-	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv"));
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
-	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"),
+					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		 triangleShaderProgram.addShader(shaderStage, path);
+	});
+	
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"),
+					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		triangleShaderProgram.addShader(shaderStage, path);
+	});
 
-	const vkcv::PipelineConfig trianglePipelineDefinition(
+	const vkcv::PipelineConfig trianglePipelineDefinition {
 		triangleShaderProgram,
-		windowWidth,
-		windowHeight,
+		(uint32_t)windowWidth,
+		(uint32_t)windowHeight,
 		trianglePass,
 		{},
-		{});
+		{},
+		false
+	};
+
 	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
-	
+
 	if (!trianglePipeline)
 	{
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	std::vector<vkcv::VertexBufferBinding> vertexBufferBindings;
+	// Compute Pipeline
+	vkcv::ShaderProgram computeShaderProgram{};
+	computeShaderProgram.addShader(vkcv::ShaderStage::COMPUTE, std::filesystem::path("shaders/comp.spv"));
+
+	// take care, assuming shader has exactly one descriptor set
+	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeShaderProgram.getReflectedDescriptors()[0]);
+
+	vkcv::PipelineHandle computePipeline = core.createComputePipeline(
+		computeShaderProgram, 
+		{ core.getDescriptorSet(computeDescriptorSet).layout });
+
+	struct ComputeTestBuffer {
+		float test1[10];
+		float test2[10];
+		float test3[10];
+	};
+
+	vkcv::Buffer computeTestBuffer = core.createBuffer<ComputeTestBuffer>(vkcv::BufferType::STORAGE, 1);
+
+	vkcv::DescriptorWrites computeDescriptorWrites;
+	computeDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, computeTestBuffer.getHandle()) };
+	core.writeDescriptorSet(computeDescriptorSet, computeDescriptorWrites);
 
 	/*
 	 * BufferHandle triangleVertices = core.createBuffer(vertices);
@@ -133,29 +162,70 @@ int main(int argc, const char** argv) {
 	 *
 	 * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline);
 	 */
-    auto start = std::chrono::system_clock::now();
+	auto start = std::chrono::system_clock::now();
+
+	vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3);
+	vkcv::DrawcallInfo drawcall(renderMesh, {});
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2));
+    cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
+    cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
+
 	while (window.isWindowOpen())
 	{
-		core.beginFrame();
         window.pollEvents();
+
+		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
         auto end = std::chrono::system_clock::now();
-        auto deltatime = end - start;
+        auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
         start = end;
-        cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
-		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+		
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+
+		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
-	    core.renderMesh(
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
 			trianglePass,
 			trianglePipeline,
-			windowWidth,
-			windowHeight,
-			sizeof(mvp),
-			&mvp,
-			vertexBufferBindings,
-			triangleIndexBuffer.getHandle(),
-			3,
-			vkcv::ResourcesHandle(),
-			0);
+			pushConstantData,
+			{ drawcall },
+			{ swapchainInput });
+
+		const uint32_t dispatchSize[3] = { 2, 1, 1 };
+		const float theMeaningOfLife = 42;
+
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			computePipeline,
+			dispatchSize,
+			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(computeDescriptorSet).vulkanHandle) },
+			vkcv::PushConstantData((void*)&theMeaningOfLife, sizeof(theMeaningOfLife)));
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		
+		ImGui::Begin("Hello world");
+		ImGui::Text("This is a test!");
+		ImGui::End();
+		
+		gui.endGUI();
 	    
 	    core.endFrame();
 	}
diff --git a/projects/voxelization/.gitignore b/projects/voxelization/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f07a22d4e0641a9114e998212f38ca9cb83a9655
--- /dev/null
+++ b/projects/voxelization/.gitignore
@@ -0,0 +1 @@
+voxelization
\ No newline at end of file
diff --git a/projects/voxelization/CMakeLists.txt b/projects/voxelization/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bc87996096226af4e3f3d05c3e10bb287c61cc8d
--- /dev/null
+++ b/projects/voxelization/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.16)
+project(voxelization)
+
+# 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(voxelization src/main.cpp)
+
+target_sources(voxelization PRIVATE
+    src/Voxelization.hpp
+    src/Voxelization.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(voxelization PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(voxelization 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(voxelization PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(voxelization SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(voxelization vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui)
diff --git a/projects/voxelization/resources/Sponza/Sponza.bin b/projects/voxelization/resources/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cfedd26ca5a67b6d0a47d44d13a75e14a141717a
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
+size 6347696
diff --git a/projects/voxelization/resources/Sponza/Sponza.gltf b/projects/voxelization/resources/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..172ea07e21c94465211c860cd805355704cef230
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
+size 713962
diff --git a/projects/voxelization/resources/Sponza/background.png b/projects/voxelization/resources/Sponza/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..b64def129da38f4e23d89e21b4af1039008a4327
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/background.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
+size 1417666
diff --git a/projects/voxelization/resources/Sponza/chain_texture.png b/projects/voxelization/resources/Sponza/chain_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1768cff78e0614ad707eca8602a4c4edab5e5
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/chain_texture.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
+size 490895
diff --git a/projects/voxelization/resources/Sponza/lion.png b/projects/voxelization/resources/Sponza/lion.png
new file mode 100644
index 0000000000000000000000000000000000000000..c49c7f0ed31e762e19284d0d3624fbc47664e56b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/lion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
+size 2088728
diff --git a/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png b/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cde4c7a6511e9a5f03c63ad996437fcdba3ce2df
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
+size 1928829
diff --git a/projects/voxelization/resources/Sponza/sponza_arch_diff.png b/projects/voxelization/resources/Sponza/sponza_arch_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9bda2918d226039f9e2d03902d377b706fab6
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_arch_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
+size 1596151
diff --git a/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png b/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..59de631ffac4414cabf69b2dc794c46fc187d6cb
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
+size 1872074
diff --git a/projects/voxelization/resources/Sponza/sponza_column_a_diff.png b/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..01a82432d3f9939bbefe850bdb900f1ff9a3f6db
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
+size 1840992
diff --git a/projects/voxelization/resources/Sponza/sponza_column_b_diff.png b/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a660cce2a5a9b8997772c746058ce23e7d45d7
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
+size 2170433
diff --git a/projects/voxelization/resources/Sponza/sponza_column_c_diff.png b/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc46fd979044a938d3adca7601689e71504e48bf
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
+size 2066950
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..384c8c2c051160d530eb3ac8b05c9c60752a2d2b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
+size 9510905
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..af842e9f5fe18c1f609875e00899a6770fa4488b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
+size 9189263
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9b6391a199407637fa71033d79fb58b8b4f0d7
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
+size 8785458
diff --git a/projects/voxelization/resources/Sponza/sponza_details_diff.png b/projects/voxelization/resources/Sponza/sponza_details_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..12656686362c3e0a297e060491f33bd7351551f9
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_details_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
+size 1522643
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..879d16ef84722a4fc13e83a771778de326e4bc54
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
+size 2229440
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..3311287a219d2148620b87fe428fea071688d051
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
+size 2267382
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..de110f369004388dae4cd5067c63428db3a07834
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
+size 2258220
diff --git a/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png b/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6e0812a0df80346318baa3cb50a6888afc58f8
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
+size 1425673
diff --git a/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png b/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..788ed764f79ba724f04a2d603076a5b85013e188
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
+size 1996838
diff --git a/projects/voxelization/resources/Sponza/sponza_roof_diff.png b/projects/voxelization/resources/Sponza/sponza_roof_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5b84261fdd1cc776a94b3ce398c7806b895f9a3
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_roof_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
+size 2320533
diff --git a/projects/voxelization/resources/Sponza/sponza_thorn_diff.png b/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9142674a7d4a6f94a48c5152cf0300743b597a
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
+size 635001
diff --git a/projects/voxelization/resources/Sponza/vase_dif.png b/projects/voxelization/resources/Sponza/vase_dif.png
new file mode 100644
index 0000000000000000000000000000000000000000..61236a81cb324af8797b05099cd264cefe189e56
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_dif.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
+size 1842101
diff --git a/projects/voxelization/resources/Sponza/vase_hanging.png b/projects/voxelization/resources/Sponza/vase_hanging.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a3cee71d8213225090c74f8c0dce33b9d44378
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_hanging.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
+size 1432804
diff --git a/projects/voxelization/resources/Sponza/vase_plant.png b/projects/voxelization/resources/Sponza/vase_plant.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ad95e702e229f1ebd803e5203a266d15f2c07b9
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_plant.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
+size 998089
diff --git a/projects/voxelization/resources/Sponza/vase_round.png b/projects/voxelization/resources/Sponza/vase_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..c17953abc000c44b8991e23c136c2b67348f3d1b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_round.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
+size 1871399
diff --git a/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg b/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
+size 1192476
diff --git a/projects/voxelization/resources/cube/cube.bin b/projects/voxelization/resources/cube/cube.bin
new file mode 100644
index 0000000000000000000000000000000000000000..3303cd8635848bee18e10ab8754d5e4e7218db92
--- /dev/null
+++ b/projects/voxelization/resources/cube/cube.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9bb9b6b8bbe50a0aaa517057f245ee844f80afa7426dacb2aed4128f71629ce4
+size 840
diff --git a/projects/voxelization/resources/cube/cube.blend b/projects/voxelization/resources/cube/cube.blend
new file mode 100644
index 0000000000000000000000000000000000000000..62ccb2c742094bcfb5ed194ab905bffae86bfd65
--- /dev/null
+++ b/projects/voxelization/resources/cube/cube.blend
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6c1e245f259c610528c9485db6688928faac0ab2addee9e3c2dde7740e4dd09
+size 774920
diff --git a/projects/voxelization/resources/cube/cube.blend1 b/projects/voxelization/resources/cube/cube.blend1
new file mode 100644
index 0000000000000000000000000000000000000000..13f21dcca218d7bc7a07a8a9682b5e1d9e607736
--- /dev/null
+++ b/projects/voxelization/resources/cube/cube.blend1
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4496f423569b8ca81f3b3a55fad00f925557e0193fb9dbe6cdce7e71fb48f7b
+size 774920
diff --git a/projects/voxelization/resources/cube/cube.glb b/projects/voxelization/resources/cube/cube.glb
new file mode 100644
index 0000000000000000000000000000000000000000..66a42c65e71dcf375e04cc378256024dd3c7834d
--- /dev/null
+++ b/projects/voxelization/resources/cube/cube.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:198568b715f397d78f7c358c0f709a419e7fd677e54cdec7c19f71b5ed264897
+size 1194508
diff --git a/projects/voxelization/resources/cube/cube.gltf b/projects/voxelization/resources/cube/cube.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..428176144843dd06c78fe1d11a6392a0ea02b22d
--- /dev/null
+++ b/projects/voxelization/resources/cube/cube.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f82f455647a84ca6242882ae26a79a499d3ce594f8de317ab89488c5b79721ac
+size 2823
diff --git a/projects/voxelization/resources/shaders/lightInfo.inc b/projects/voxelization/resources/shaders/lightInfo.inc
new file mode 100644
index 0000000000000000000000000000000000000000..4345d4f1504d27df7392b34bcaf17efdcfecef33
--- /dev/null
+++ b/projects/voxelization/resources/shaders/lightInfo.inc
@@ -0,0 +1,6 @@
+struct LightInfo{
+    vec3 L;             float padding;
+    vec3 sunColor;      
+    float sunStrength;
+    mat4 lightMatrix;
+};
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/perMeshResources.inc b/projects/voxelization/resources/shaders/perMeshResources.inc
new file mode 100644
index 0000000000000000000000000000000000000000..95e4fb7c27009965659d14a9c72acfec950c37e3
--- /dev/null
+++ b/projects/voxelization/resources/shaders/perMeshResources.inc
@@ -0,0 +1,2 @@
+layout(set=1, binding=0) uniform texture2D  albedoTexture;
+layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.frag b/projects/voxelization/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8653ae5958ce3b42eac6b1eaa6813f85b6ed589c
--- /dev/null
+++ b/projects/voxelization/resources/shaders/shader.frag
@@ -0,0 +1,28 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "perMeshResources.inc"
+#include "lightInfo.inc"
+#include "shadowMapping.inc"
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in vec3 passPos;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform sunBuffer {
+    LightInfo lightInfo;
+};
+layout(set=0, binding=1) uniform texture2D  shadowMap;
+layout(set=0, binding=2) uniform sampler    shadowMapSampler;
+
+void main()	{
+    vec3 N          = normalize(passNormal);
+    vec3 sun        = lightInfo.sunStrength * lightInfo.sunColor * clamp(dot(N, lightInfo.L), 0, 1);
+    sun             *= shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
+    vec3 ambient    = vec3(0.05);
+    vec3 albedo     = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+	outColor        = albedo * (sun + ambient);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.vert b/projects/voxelization/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..926f86af2860cb57c44d2d5ee78712b6ae155e5c
--- /dev/null
+++ b/projects/voxelization/resources/shaders/shader.vert
@@ -0,0 +1,22 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out vec3 passPos;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 model;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = mat3(model) * inNormal;    // assuming no weird stuff like shearing or non-uniform scaling
+    passUV      = inUV;
+    passPos     = (model * vec4(inPosition, 1)).xyz;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadow.frag b/projects/voxelization/resources/shaders/shadow.frag
new file mode 100644
index 0000000000000000000000000000000000000000..848f853f556660b4900b5db7fb6fc98d57c1cd5b
--- /dev/null
+++ b/projects/voxelization/resources/shaders/shadow.frag
@@ -0,0 +1,6 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+void main()	{
+
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadow.vert b/projects/voxelization/resources/shaders/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8
--- /dev/null
+++ b/projects/voxelization/resources/shaders/shadow.vert
@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadowMapping.inc b/projects/voxelization/resources/shaders/shadowMapping.inc
new file mode 100644
index 0000000000000000000000000000000000000000..1fa34a388c35b96a3316e972ca562d35e2c3cf90
--- /dev/null
+++ b/projects/voxelization/resources/shaders/shadowMapping.inc
@@ -0,0 +1,16 @@
+float shadowTest(vec3 worldPos, LightInfo lightInfo, texture2D shadowMap, sampler shadowMapSampler){
+    vec4 lightPos = lightInfo.lightMatrix * vec4(worldPos, 1);
+    lightPos /= lightPos.w;
+    lightPos.xy = lightPos.xy * 0.5 + 0.5;
+    
+    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
+        return 1;
+    }
+    
+    lightPos.z = clamp(lightPos.z, 0, 1);
+    
+    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
+    float bias = 0.01f;
+    shadowMapSample += bias;
+    return shadowMapSample < lightPos.z ? 0 : 1;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/tonemapping.comp b/projects/voxelization/resources/shaders/tonemapping.comp
new file mode 100644
index 0000000000000000000000000000000000000000..2383302fa946e7d92871039daff28232df2eafdd
--- /dev/null
+++ b/projects/voxelization/resources/shaders/tonemapping.comp
@@ -0,0 +1,19 @@
+#version 440
+
+layout(set=0, binding=0, r11f_g11f_b10f)    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 / (linearColor + 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/voxelization/resources/shaders/voxel.inc b/projects/voxelization/resources/shaders/voxel.inc
new file mode 100644
index 0000000000000000000000000000000000000000..25c0a82bbc887913a4d69ccdeee2b0d8934828c8
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxel.inc
@@ -0,0 +1,42 @@
+struct VoxelInfo{
+    vec3 offset;
+    float extent;
+};
+
+uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
+    return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
+}
+
+// packed voxel data: 
+// 1 bit opacity
+// 7 bit exposure
+// 8 bit blue
+// 8 bit green
+// 8 bit red
+float maxExposure = 16.f;
+
+uint packVoxelInfo(vec3 color){
+    
+    color               = clamp(color, vec3(0), vec3(maxExposure));
+    float maxComponent  = max(max(max(color.r, color.g), color.b), 1.f);
+    color               /= maxComponent;
+    
+    uint opaqueBit      = 1 << 31;
+    uint exposureBits   = (0x0000007F & uint(maxComponent / maxExposure * 127)) << 24;
+    uint redBits        = (0x000000FF & uint(color.r * 255)) << 0;
+    uint greenBits      = (0x000000FF & uint(color.g * 255)) << 8;
+    uint blueBits       = (0x000000FF & uint(color.b * 255)) << 16;
+    return opaqueBit | exposureBits | blueBits | greenBits | redBits;
+}
+
+vec4 unpackVoxelInfo(uint packed){
+    vec4 rgba;
+    rgba.r = (packed >> 0  & 0x000000FF) / 255.f;
+    rgba.g = (packed >> 8  & 0x000000FF) / 255.f;
+    rgba.b = (packed >> 16 & 0x000000FF) / 255.f;
+    rgba.a =  packed  >> 31; 
+    
+    rgba.rgb *= (packed >> 24 & 0x0000007F) / 127.f * maxExposure; 
+    
+    return rgba;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelBufferToImage.comp b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
new file mode 100644
index 0000000000000000000000000000000000000000..5e8298886cb2bacbc81f981e8e90310cdc876d5d
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
@@ -0,0 +1,24 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
+
+layout(set=0, binding=0, std430) buffer voxelBuffer{
+    uint packedVoxelData[];
+};
+
+layout(set=0, binding=1, rgba16f) uniform image3D voxelImage;
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+void main(){
+
+    ivec3 voxelImageSize = imageSize(voxelImage);
+    if(any(greaterThanEqual(gl_GlobalInvocationID, voxelImageSize))){
+        return;
+    }
+    ivec3 UV = ivec3(gl_GlobalInvocationID);
+    uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
+    
+    vec4 color = unpackVoxelInfo(packedVoxelData[flatIndex]);
+    imageStore(voxelImage, UV, vec4(color));
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelReset.comp b/projects/voxelization/resources/shaders/voxelReset.comp
new file mode 100644
index 0000000000000000000000000000000000000000..14b78d6584d703be68594e3cb03ebcd47c94b6e0
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelReset.comp
@@ -0,0 +1,19 @@
+#version 450
+
+layout(set=0, binding=0) buffer voxelizationBuffer{
+    uint isFilled[];
+};
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    uint voxelCount;
+};
+
+void main(){
+
+    if(gl_GlobalInvocationID.x> voxelCount){
+        return;
+    }  
+    isFilled[gl_GlobalInvocationID.x] = 0;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.frag b/projects/voxelization/resources/shaders/voxelVisualisation.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0b02beb7e848ab20cda4b012f77d1fa664b6ab53
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.frag
@@ -0,0 +1,9 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location=0) in vec3 passColorToFrag;
+layout(location=0) out vec3 outColor;
+
+void main()	{
+    outColor = passColorToFrag;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.geom b/projects/voxelization/resources/shaders/voxelVisualisation.geom
new file mode 100644
index 0000000000000000000000000000000000000000..e98076fcc83a69a903df454cb00267da84e3f223
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.geom
@@ -0,0 +1,104 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(points) in;
+layout (triangle_strip, max_vertices = 24) out;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+};
+
+layout(location = 0) in float passCubeHalf[1];
+layout(location = 1) in vec3 passColorToGeom[1];
+
+
+layout(location = 0) out vec3 passColorToFrag;
+
+void main()	{
+    float cubeHalf = passCubeHalf[0];
+    // right
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, 1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // left
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // back
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,   1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1,  1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // front
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,   1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1,  1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // bot
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex(); 
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // top
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();   
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();   
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.vert b/projects/voxelization/resources/shaders/voxelVisualisation.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8377143f4f4bbf351d3251df9724d37e1747a4dc
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.vert
@@ -0,0 +1,36 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "voxel.inc"
+
+layout(location = 0) out float passCubeHalf;
+layout(location = 1) out vec3 passColorToGeom;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+};
+
+layout(set=0, binding=0, rgba16f) uniform image3D  voxelImage;
+layout(set=0, binding=1) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+
+void main()	{
+    passCubeHalf        = voxelInfo.extent / float(imageSize(voxelImage).x) * 0.5f;
+    int voxelResolution = imageSize(voxelImage).x;
+    int slicePixelCount = voxelResolution * voxelResolution;
+    int z               = gl_VertexIndex / slicePixelCount;
+    int index2D         = gl_VertexIndex % slicePixelCount;
+    int y               = index2D / voxelResolution;
+    int x               = index2D % voxelResolution;
+    vec3 position       = (vec3(x, y, z) / voxelResolution - 0.5) * voxelInfo.extent + passCubeHalf + voxelInfo.offset;
+	gl_Position         = vec4(position, 1.0);
+    
+    vec4 voxelColor = imageLoad(voxelImage, ivec3(x,y,z));
+    if(voxelColor.a == 0){
+        gl_Position.x /= 0; // clip
+    }
+    passColorToGeom = voxelColor.rgb;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.frag b/projects/voxelization/resources/shaders/voxelization.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a49b13185ec26b069661141cfdbbfbbe45d14fd3
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.frag
@@ -0,0 +1,57 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "voxel.inc"
+#include "perMeshResources.inc"
+#include "lightInfo.inc"
+#include "shadowMapping.inc"
+
+layout(location = 0) in vec3 passPos;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in vec3 passN;
+
+layout(set=0, binding=0, std430) buffer voxelizationBuffer{
+    uint packedVoxelData[];
+};
+
+layout(set=0, binding=1) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+layout(set=0, binding=2, r8) uniform image3D voxelImage;
+
+layout(set=0, binding=3) uniform sunBuffer {
+    LightInfo lightInfo;
+};
+
+layout(set=0, binding=4) uniform texture2D  shadowMap;
+layout(set=0, binding=5) uniform sampler    shadowMapSampler;
+
+vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
+    return (world - info.offset) / info.extent + 0.5f;
+}
+
+ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
+    return ivec3(voxelCoordinates * voxelImageResolution);
+}
+
+void main()	{
+    vec3 voxelCoordinates = worldToVoxelCoordinates(passPos, voxelInfo);
+    ivec3 voxelImageSize = imageSize(voxelImage);
+    ivec3 UV = voxelCoordinatesToUV(voxelCoordinates, voxelImageSize);
+    if(any(lessThan(UV, ivec3(0))) || any(greaterThanEqual(UV, voxelImageSize))){
+        return;
+    }
+    uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
+
+    vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+    
+    vec3 N      = normalize(passN);
+    float NoL   = clamp(dot(N, lightInfo.L), 0, 1);
+    vec3 sun    = lightInfo.sunStrength * lightInfo.sunColor * NoL * shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
+    vec3 color  = albedo * sun;
+    color = albedo * sun;
+    
+    atomicMax(packedVoxelData[flatIndex], packVoxelInfo(color));
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.geom b/projects/voxelization/resources/shaders/voxelization.geom
new file mode 100644
index 0000000000000000000000000000000000000000..56542d960d65db6ca12c5f84837cb0c0a9ff0ded
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.geom
@@ -0,0 +1,38 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(triangles) in;
+layout (triangle_strip, max_vertices = 3) out;
+
+layout(location = 0) in vec3 passPosIn[3];
+layout(location = 1) in vec2 passUVIn[3];
+layout(location = 2) in vec3 passNIn[3];
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out vec3 passN;
+
+void main()	{
+    // compute geometric normal, no normalization necessary
+    vec3 N = cross(passPosIn[0] - passPosIn[1], passPosIn[0] - passPosIn[2]);
+    N = abs(N); // only interested in the magnitude
+    
+    for(int i = 0; i < 3; i++){
+        // swizzle position, so biggest side is rasterized
+        if(N.z > N.x && N.z > N.y){
+            gl_Position = gl_in[i].gl_Position.xyzw;
+        }
+        else if(N.x > N.y){
+            gl_Position = gl_in[i].gl_Position.yzxw;
+        }
+        else{
+            gl_Position = gl_in[i].gl_Position.xzyw;
+        }
+        gl_Position.z = gl_Position.z * 0.5 + 0.5;  // xyz are kept in NDC range [-1, 1] so swizzling works, but vulkan needs final z in range [0, 1]
+        passPos = passPosIn[i];
+        passUV  = passUVIn[i];
+        passN   = passNIn[i];
+        EmitVertex();
+    }
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.vert b/projects/voxelization/resources/shaders/voxelization.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1302a42441b5b9c8ea7d24f97d29b684e4d64993
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.vert
@@ -0,0 +1,22 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out vec3 passN;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 model;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+    passPos     = (model * vec4(inPosition, 1)).xyz;
+    passUV      = inUV;
+    passN       = inNormal;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/triangle/Triangle.bin b/projects/voxelization/resources/triangle/Triangle.bin
new file mode 100644
index 0000000000000000000000000000000000000000..57f26ad96592b64377e6aa93823d96a94e6c5022
--- /dev/null
+++ b/projects/voxelization/resources/triangle/Triangle.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:412ebd5f7242c266b4957e7e26be13aa331dbcb7bbb854ab334a2437ae8ed959
+size 104
diff --git a/projects/voxelization/resources/triangle/Triangle.blend b/projects/voxelization/resources/triangle/Triangle.blend
new file mode 100644
index 0000000000000000000000000000000000000000..2421dc5e1bb029d73a9ec09cc4530c5196851fd7
--- /dev/null
+++ b/projects/voxelization/resources/triangle/Triangle.blend
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:387e544df173219fbf292a64a6656d1d782bbf71a5a9e9fdef0a308f47b05477
+size 758144
diff --git a/projects/voxelization/resources/triangle/Triangle.glb b/projects/voxelization/resources/triangle/Triangle.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4148620cd6af0dadbc791aa1c52bb5431a40884b
--- /dev/null
+++ b/projects/voxelization/resources/triangle/Triangle.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f4be087a605212d139416b5352a018283b26b99260cbcddb7013a1beeb331227
+size 980
diff --git a/projects/voxelization/resources/triangle/Triangle.gltf b/projects/voxelization/resources/triangle/Triangle.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..a188e6ee16a5e8486cf307c7bda8cfd99bdbeea6
--- /dev/null
+++ b/projects/voxelization/resources/triangle/Triangle.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d5fc354e040f79cff329e919677b194c75e3a522c6406f75c1108ad9575f12ec
+size 2202
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a04131cedfdc508e14a3dbd97da642e1af48f8da
--- /dev/null
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -0,0 +1,308 @@
+#include "Voxelization.hpp"
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <algorithm>
+
+vkcv::ShaderProgram loadVoxelizationShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelization.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelization.geom",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelization.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelVisualisationShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelVisualisation.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelVisualisation.geom",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelVisualisation.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelResetShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelReset.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelBufferToImageShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelBufferToImage.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+const uint32_t voxelResolution = 128;
+uint32_t voxelCount = voxelResolution * voxelResolution * voxelResolution;
+const vk::Format voxelizationDummyFormat = vk::Format::eR8Unorm;
+
+Voxelization::Voxelization(
+	vkcv::Core* corePtr,
+	const Dependencies& dependencies,
+	vkcv::BufferHandle  lightInfoBuffer,
+	vkcv::ImageHandle   shadowMap,
+	vkcv::SamplerHandle shadowSampler)
+	:
+	m_corePtr(corePtr), 
+	m_voxelImage(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
+	m_dummyRenderTarget(m_corePtr->createImage(voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, false, true)),
+	m_voxelInfoBuffer(m_corePtr->createBuffer<VoxelizationInfo>(vkcv::BufferType::UNIFORM, 1)),
+	m_voxelBuffer(m_corePtr->createBuffer<VoxelBufferContent>(vkcv::BufferType::STORAGE, voxelCount)){
+
+	const vkcv::ShaderProgram voxelizationShader = loadVoxelizationShader();
+
+	const vkcv::PassConfig voxelizationPassConfig({vkcv::AttachmentDescription(
+		vkcv::AttachmentOperation::DONT_CARE, 
+		vkcv::AttachmentOperation::DONT_CARE, 
+		voxelizationDummyFormat) });
+	m_voxelizationPass = m_corePtr->createPass(voxelizationPassConfig);
+
+	std::vector<vkcv::DescriptorBinding> voxelizationDescriptorBindings = 
+	{ voxelizationShader.getReflectedDescriptors()[0] };
+	m_voxelizationDescriptorSet = m_corePtr->createDescriptorSet(voxelizationDescriptorBindings);
+
+	vkcv::DescriptorSetHandle dummyPerMeshDescriptorSet =
+		m_corePtr->createDescriptorSet({ voxelizationShader.getReflectedDescriptors()[1] });
+
+	const vkcv::PipelineConfig voxelizationPipeConfig{
+		voxelizationShader,
+		voxelResolution,
+		voxelResolution,
+		m_voxelizationPass,
+		dependencies.vertexLayout,
+		{ 
+			m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).layout,
+			m_corePtr->getDescriptorSet(dummyPerMeshDescriptorSet).layout},
+		false,
+		true };
+	m_voxelizationPipe = m_corePtr->createGraphicsPipeline(voxelizationPipeConfig);
+
+	vkcv::DescriptorWrites voxelizationDescriptorWrites;
+	voxelizationDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	voxelizationDescriptorWrites.uniformBufferWrites = { 
+		vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()),
+		vkcv::UniformBufferDescriptorWrite(3, lightInfoBuffer)
+	};
+	voxelizationDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(4, shadowMap) };
+	voxelizationDescriptorWrites.samplerWrites      = { vkcv::SamplerDescriptorWrite(5, shadowSampler) };
+	voxelizationDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, m_voxelImage.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_voxelizationDescriptorSet, voxelizationDescriptorWrites);
+
+	vkcv::ShaderProgram voxelVisualisationShader = loadVoxelVisualisationShader();
+
+	const std::vector<vkcv::DescriptorBinding> voxelVisualisationDescriptorBindings = 
+		{ voxelVisualisationShader.getReflectedDescriptors()[0] };
+	m_visualisationDescriptorSet = m_corePtr->createDescriptorSet(voxelVisualisationDescriptorBindings);
+
+	const vkcv::AttachmentDescription voxelVisualisationColorAttachments(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		dependencies.colorBufferFormat
+	);
+
+	const vkcv::AttachmentDescription voxelVisualisationDepthAttachments(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		dependencies.depthBufferFormat
+	);
+
+	vkcv::PassConfig voxelVisualisationPassDefinition(
+		{ voxelVisualisationColorAttachments, voxelVisualisationDepthAttachments });
+	m_visualisationPass = m_corePtr->createPass(voxelVisualisationPassDefinition);
+
+	const vkcv::PipelineConfig voxelVisualisationPipeConfig{
+		voxelVisualisationShader,
+		0,
+		0,
+		m_visualisationPass,
+		{},
+		{ m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).layout },
+		true,
+		false,
+		vkcv::PrimitiveTopology::PointList };	// points are extended to cubes in the geometry shader
+	m_visualisationPipe = m_corePtr->createGraphicsPipeline(voxelVisualisationPipeConfig);
+
+	std::vector<uint16_t> voxelIndexData;
+	for (int i = 0; i < voxelCount; i++) {
+		voxelIndexData.push_back(i);
+	}
+
+	const vkcv::DescriptorSetUsage voxelizationDescriptorUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle);
+
+	vkcv::ShaderProgram resetVoxelShader = loadVoxelResetShader();
+
+	m_voxelResetDescriptorSet = m_corePtr->createDescriptorSet(resetVoxelShader.getReflectedDescriptors()[0]);
+	m_voxelResetPipe = m_corePtr->createComputePipeline(
+		resetVoxelShader,
+		{ m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).layout });
+
+	vkcv::DescriptorWrites resetVoxelWrites;
+	resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites);
+
+
+	vkcv::ShaderProgram bufferToImageShader = loadVoxelBufferToImageShader();
+
+	m_bufferToImageDescriptorSet = m_corePtr->createDescriptorSet(bufferToImageShader.getReflectedDescriptors()[0]);
+	m_bufferToImagePipe = m_corePtr->createComputePipeline(
+		bufferToImageShader,
+		{ m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).layout });
+
+	vkcv::DescriptorWrites bufferToImageDescriptorWrites;
+	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImage.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites);
+}
+
+void Voxelization::voxelizeMeshes(
+	vkcv::CommandStreamHandle                       cmdStream, 
+	const glm::vec3&                                cameraPosition, 
+	const std::vector<vkcv::Mesh>&                  meshes,
+	const std::vector<glm::mat4>&                   modelMatrices,
+	const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets) {
+
+	VoxelizationInfo voxelizationInfo;
+	voxelizationInfo.extent = m_voxelExtent;
+
+	// move voxel offset with camera in voxel sized steps
+	const float voxelSize = m_voxelExtent / voxelResolution;
+	voxelizationInfo.offset = glm::floor(cameraPosition / voxelSize) * voxelSize;
+
+	m_voxelInfoBuffer.fill({ voxelizationInfo });
+
+	const float voxelizationHalfExtent = 0.5f * m_voxelExtent;
+	const glm::mat4 voxelizationProjection = glm::ortho(
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent,
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent,
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent);
+
+	const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -voxelizationInfo.offset);
+	const glm::mat4 voxelizationViewProjection = voxelizationProjection * voxelizationView;
+
+	std::vector<std::array<glm::mat4, 2>> voxelizationMatrices;
+	for (const auto& m : modelMatrices) {
+		voxelizationMatrices.push_back({ voxelizationViewProjection * m, m });
+	}
+
+	const vkcv::PushConstantData voxelizationPushConstantData((void*)voxelizationMatrices.data(), 2 * sizeof(glm::mat4));
+
+	// reset voxels
+	const uint32_t resetVoxelGroupSize = 64;
+	uint32_t resetVoxelDispatchCount[3];
+	resetVoxelDispatchCount[0] = glm::ceil(voxelCount / float(resetVoxelGroupSize));
+	resetVoxelDispatchCount[1] = 1;
+	resetVoxelDispatchCount[2] = 1;
+
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_voxelResetPipe,
+		resetVoxelDispatchCount,
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) },
+		vkcv::PushConstantData(&voxelCount, sizeof(voxelCount)));
+	m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle());
+
+	// voxelization
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	for (int i = 0; i < meshes.size(); i++) {
+		drawcalls.push_back(vkcv::DrawcallInfo(
+			meshes[i], 
+			{ 
+				vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).vulkanHandle),
+				vkcv::DescriptorSetUsage(1, m_corePtr->getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) 
+			}));
+	}
+
+	m_corePtr->recordDrawcallsToCmdStream(
+		cmdStream,
+		m_voxelizationPass,
+		m_voxelizationPipe,
+		voxelizationPushConstantData,
+		drawcalls,
+		{ m_dummyRenderTarget.getHandle() });
+
+	// buffer to image
+	const uint32_t bufferToImageGroupSize[3] = { 4, 4, 4 };
+	uint32_t bufferToImageDispatchCount[3];
+	for (int i = 0; i < 3; i++) {
+		bufferToImageDispatchCount[i] = glm::ceil(voxelResolution / float(bufferToImageGroupSize[i]));
+	}
+
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_bufferToImagePipe,
+		bufferToImageDispatchCount,
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) },
+		vkcv::PushConstantData(nullptr, 0));
+
+	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
+
+	m_voxelImage.recordMipChainGeneration(cmdStream);
+}
+
+void Voxelization::renderVoxelVisualisation(
+	vkcv::CommandStreamHandle               cmdStream, 
+	const glm::mat4&                        viewProjectin,
+	const std::vector<vkcv::ImageHandle>&   renderTargets,
+	uint32_t                                mipLevel) {
+
+	const vkcv::PushConstantData voxelVisualisationPushConstantData((void*)&viewProjectin, sizeof(glm::mat4));
+
+	mipLevel = std::clamp(mipLevel, (uint32_t)0, m_voxelImage.getMipCount()-1);
+
+	// write descriptor set
+	vkcv::DescriptorWrites voxelVisualisationDescriptorWrite;
+	voxelVisualisationDescriptorWrite.storageImageWrites =
+	{ vkcv::StorageImageDescriptorWrite(0, m_voxelImage.getHandle(), mipLevel) };
+	voxelVisualisationDescriptorWrite.uniformBufferWrites =
+	{ vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_visualisationDescriptorSet, voxelVisualisationDescriptorWrite);
+
+	uint32_t drawVoxelCount = voxelCount / exp2(mipLevel);
+
+	const auto drawcall = vkcv::DrawcallInfo(
+		vkcv::Mesh({}, nullptr, drawVoxelCount),
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle) });
+
+	m_corePtr->recordDrawcallsToCmdStream(
+		cmdStream,
+		m_visualisationPass,
+		m_visualisationPipe,
+		voxelVisualisationPushConstantData,
+		{ drawcall },
+		renderTargets);
+}
+
+void Voxelization::setVoxelExtent(float extent) {
+	m_voxelExtent = extent;
+}
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.hpp b/projects/voxelization/src/Voxelization.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..25830b171edb9154e37b2d597c2bbbf2daea6b2e
--- /dev/null
+++ b/projects/voxelization/src/Voxelization.hpp
@@ -0,0 +1,67 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <glm/glm.hpp>
+
+class Voxelization{
+public:
+	struct Dependencies {
+		vkcv::VertexLayout  vertexLayout;
+		vk::Format          colorBufferFormat;
+		vk::Format          depthBufferFormat;
+	};
+	Voxelization(
+		vkcv::Core*         corePtr, 
+		const Dependencies& dependencies, 
+		vkcv::BufferHandle  lightInfoBuffer,
+		vkcv::ImageHandle   shadowMap,
+		vkcv::SamplerHandle shadowSampler);
+
+	void voxelizeMeshes(
+		vkcv::CommandStreamHandle                       cmdStream, 
+		const glm::vec3&                                cameraPosition, 
+		const std::vector<vkcv::Mesh>&                  meshes,
+		const std::vector<glm::mat4>&                   modelMatrices,
+		const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets);
+
+	void renderVoxelVisualisation(
+		vkcv::CommandStreamHandle               cmdStream,
+		const glm::mat4&                        viewProjectin,
+		const std::vector<vkcv::ImageHandle>&   renderTargets,
+		uint32_t                                mipLevel);
+
+	void setVoxelExtent(float extent);
+
+private:
+	vkcv::Core* m_corePtr;
+
+	struct VoxelBufferContent{
+		uint32_t isFilled;
+	};
+
+	vkcv::Image                         m_voxelImage;
+    vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
+
+	vkcv::Image                 m_dummyRenderTarget;
+	vkcv::PassHandle            m_voxelizationPass;
+	vkcv::PipelineHandle        m_voxelizationPipe;
+	vkcv::DescriptorSetHandle   m_voxelizationDescriptorSet;
+
+	vkcv::PipelineHandle        m_voxelResetPipe;
+	vkcv::DescriptorSetHandle   m_voxelResetDescriptorSet;
+
+	vkcv::PipelineHandle        m_bufferToImagePipe;
+	vkcv::DescriptorSetHandle   m_bufferToImageDescriptorSet;
+
+	vkcv::PassHandle            m_visualisationPass;
+	vkcv::PipelineHandle        m_visualisationPipe;
+
+	vkcv::DescriptorSetHandle   m_visualisationDescriptorSet;
+
+	struct VoxelizationInfo {
+		glm::vec3 offset;
+		float extent;
+	};
+	vkcv::Buffer<VoxelizationInfo> m_voxelInfoBuffer;
+
+	float m_voxelExtent = 20.f;
+};
\ No newline at end of file
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aabed2180f598c9792a76ddcdcf4a2f400d16334
--- /dev/null
+++ b/projects/voxelization/src/main.cpp
@@ -0,0 +1,475 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/Logger.hpp>
+#include "Voxelization.hpp"
+#include <glm/glm.hpp>
+#include "vkcv/gui/GUI.hpp"
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "Voxelization";
+
+	uint32_t windowWidth = 1280;
+	uint32_t windowHeight = 720;
+	
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    
+    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
+    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camIndex).setYaw(180.0f);
+	
+	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::asset::Scene mesh;
+
+	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	vkcv::asset::Scene scene;
+	int result = vkcv::asset::loadScene(path, scene);
+
+	if (result == 1) {
+		std::cout << "Scene loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Scene loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	// build index and vertex buffers
+	assert(!scene.vertexGroups.empty());
+	std::vector<std::vector<uint8_t>> vBuffers;
+	std::vector<std::vector<uint8_t>> iBuffers;
+
+	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
+	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
+	std::vector<vkcv::asset::VertexAttribute> vAttributes;
+
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+
+		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
+		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
+
+		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
+
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+		});
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
+	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
+		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			group.vertexBuffer.data.size()));
+		vertexBuffers.back().fill(group.vertexBuffer.data);
+	}
+
+	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
+	for (const auto& dataBuffer : iBuffers) {
+		indexBuffers.push_back(core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			dataBuffer.size()));
+		indexBuffers.back().fill(dataBuffer);
+	}
+
+	int vertexBufferIndex = 0;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+			vAttributes.push_back(attribute);
+			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
+		}
+		vertexBufferBindings.push_back(vBufferBindings);
+		vBufferBindings.clear();
+		vertexBufferIndex++;
+	}
+
+	const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32;
+	const vkcv::AttachmentDescription color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		colorBufferFormat
+	);
+	
+	const vk::Format depthBufferFormat = vk::Format::eD32Sfloat;
+	const vkcv::AttachmentDescription depth_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		depthBufferFormat
+	);
+
+	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment });
+	vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition);
+
+	vkcv::shader::GLSLCompiler compiler;
+
+	vkcv::ShaderProgram forwardProgram;
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), 
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		forwardProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		forwardProgram.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = forwardProgram.getVertexAttachments();
+
+	std::vector<vkcv::VertexBinding> vertexBindings;
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+	}
+	const vkcv::VertexLayout vertexLayout (vertexBindings);
+
+	// shadow map
+	vkcv::SamplerHandle shadowSampler = core.createSampler(
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerMipmapMode::NEAREST,
+		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+	);
+	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
+	const uint32_t shadowMapResolution = 1024;
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution);
+
+	// light info buffer
+	struct LightInfo {
+		glm::vec3   direction;
+		float       padding;
+		glm::vec3   sunColor    = glm::vec3(1.f);
+		float       sunStrength = 8.f;
+		glm::mat4   lightMatrix;
+	};
+	LightInfo lightInfo;
+	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+
+	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
+		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
+
+	vkcv::DescriptorWrites forwardDescriptorWrites;
+	forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) };
+	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
+	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
+	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
+
+	vkcv::SamplerHandle colorSampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	// create descriptor sets
+	std::vector<vkcv::DescriptorSetHandle> materialDescriptorSets;
+	std::vector<vkcv::Image> sceneImages;
+
+	for (const auto& material : scene.materials) {
+		int baseColorIndex = material.baseColor;
+		if (baseColorIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
+			baseColorIndex = 0;
+		}
+
+		materialDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1]));
+
+		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
+
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h, 1, true));
+		sceneImages.back().fill(sceneTexture.data.data());
+		sceneImages.back().generateMipChainImmediate();
+		sceneImages.back().switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
+
+		vkcv::DescriptorWrites setWrites;
+		setWrites.sampledImageWrites = {
+			vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle())
+		};
+		setWrites.samplerWrites = {
+			vkcv::SamplerDescriptorWrite(1, colorSampler),
+		};
+		core.writeDescriptorSet(materialDescriptorSets.back(), setWrites);
+	}
+
+	std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets;
+	for (const auto& vertexGroup : scene.vertexGroups) {
+		perMeshDescriptorSets.push_back(materialDescriptorSets[vertexGroup.materialIndex]);
+	}
+
+	const vkcv::PipelineConfig forwardPipelineConfig {
+		forwardProgram,
+		windowWidth,
+		windowHeight,
+		forwardPass,
+		vertexLayout,
+		{	core.getDescriptorSet(forwardShadingDescriptorSet).layout, 
+			core.getDescriptorSet(perMeshDescriptorSets[0]).layout },
+		true
+	};
+	
+	vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
+	
+	if (!forwardPipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ImageHandle depthBuffer = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
+	vkcv::ImageHandle colorBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::ShaderProgram shadowShader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+	const vkcv::PipelineConfig shadowPipeConfig{
+		shadowShader,
+		shadowMapResolution,
+		shadowMapResolution,
+		shadowPass,
+		vertexLayout,
+		{},
+		false
+	};
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	bool renderVoxelVis = false;
+	window.e_key.add([&renderVoxelVis](int key ,int scancode, int action, int mods) {
+		if (key == GLFW_KEY_V && action == GLFW_PRESS) {
+			renderVoxelVis = !renderVoxelVis;
+		}
+	});
+
+	// tonemapping compute shader
+	vkcv::ShaderProgram tonemappingProgram;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/tonemapping.comp", 
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		tonemappingProgram.addShader(shaderStage, path);
+	});
+	vkcv::DescriptorSetHandle tonemappingDescriptorSet = core.createDescriptorSet(tonemappingProgram.getReflectedDescriptors()[0]);
+	vkcv::PipelineHandle tonemappingPipeline = core.createComputePipeline(tonemappingProgram,
+		{ core.getDescriptorSet(tonemappingDescriptorSet).layout });
+
+	// model matrices per mesh
+	std::vector<glm::mat4> modelMatrices;
+	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
+	for (const auto& mesh : scene.meshes) {
+		const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]);
+		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
+			modelMatrices[vertexGroupIndex] = m;
+		}
+	}
+
+	// prepare drawcalls
+	std::vector<vkcv::Mesh> meshes;
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+		vkcv::Mesh mesh(
+			vertexBufferBindings[i], 
+			indexBuffers[i].getVulkanHandle(), 
+			scene.vertexGroups[i].numIndices);
+		meshes.push_back(mesh);
+	}
+
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
+	for (int i = 0; i < meshes.size(); i++) {
+
+		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
+			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
+			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
+	}
+
+	Voxelization::Dependencies voxelDependencies;
+	voxelDependencies.colorBufferFormat = colorBufferFormat;
+	voxelDependencies.depthBufferFormat = depthBufferFormat;
+	voxelDependencies.vertexLayout = vertexLayout;
+	Voxelization voxelization(
+		&core,
+		voxelDependencies,
+		lightBuffer.getHandle(),
+		shadowMap.getHandle(),
+		shadowSampler);
+
+	vkcv::gui::GUI gui(core, window);
+
+	glm::vec2 lightAngles(90.f, 0.f);
+	int voxelVisualisationMip = 0;
+	float voxelizationExtent = 20.f;
+
+	auto start = std::chrono::system_clock::now();
+	const auto appStartTime = start;
+	while (window.isWindowOpen()) {
+		vkcv::Window::pollEvents();
+
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
+			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
+
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+
+		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
+		vkcv::DescriptorWrites tonemappingDescriptorWrites;
+		tonemappingDescriptorWrites.storageImageWrites = {
+			vkcv::StorageImageDescriptorWrite(0, colorBuffer),
+			vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
+		core.writeDescriptorSet(tonemappingDescriptorSet, tonemappingDescriptorWrites);
+
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		glm::vec2 lightAngleRadian = glm::radians(lightAngles);
+		lightInfo.direction = glm::normalize(glm::vec3(
+			std::cos(lightAngleRadian.x) * std::cos(lightAngleRadian.y),
+			std::sin(lightAngleRadian.x),
+			std::cos(lightAngleRadian.x) * std::sin(lightAngleRadian.y)));
+
+		const float shadowProjectionSize = 20.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
+		for (const auto& m : modelMatrices) {
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix * m);
+		}
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
+
+		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		// shadow map
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
+		voxelization.setVoxelExtent(voxelizationExtent);
+		voxelization.voxelizeMeshes(
+			cmdStream, 
+			cameraManager.getActiveCamera().getPosition(), 
+			meshes, 
+			modelMatrices,
+			perMeshDescriptorSets);
+
+		// main pass
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+            forwardPass,
+            forwardPipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+
+		if (renderVoxelVis) {
+			voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip);
+		}
+
+		const uint32_t tonemappingLocalGroupSize = 8;
+		const uint32_t tonemappingDispatchCount[3] = {
+			static_cast<uint32_t>(glm::ceil(windowWidth / static_cast<float>(tonemappingLocalGroupSize))),
+			static_cast<uint32_t>(glm::ceil(windowHeight / static_cast<float>(tonemappingLocalGroupSize))),
+			1
+		};
+
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+		core.prepareImageForStorage(cmdStream, colorBuffer);
+
+		core.recordComputeDispatchToCmdStream(
+			cmdStream, 
+			tonemappingPipeline, 
+			tonemappingDispatchCount,
+			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptorSet).vulkanHandle) },
+			vkcv::PushConstantData(nullptr, 0));
+
+		// present and end
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		gui.beginGUI();
+
+		ImGui::Begin("Settings");
+		ImGui::DragFloat2("Light angles",   &lightAngles.x);
+		ImGui::ColorEdit3("Sun color",      &lightInfo.sunColor.x);
+		ImGui::DragFloat("Sun strength",    &lightInfo.sunStrength);
+		ImGui::Checkbox("Draw voxel visualisation", &renderVoxelVis);
+		ImGui::SliderInt("Visualisation mip",       &voxelVisualisationMip, 0, 7);
+		ImGui::DragFloat("Voxelization extent",     &voxelizationExtent, 1.f, 0.f);
+		voxelVisualisationMip = std::max(voxelVisualisationMip, 0);
+		ImGui::End();
+
+		gui.endGUI();
+
+		core.endFrame();
+	}
+	
+	return 0;
+}
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index ab34a3df149c96d30c9ee4e0665ba47765d19751..aec96411c5d9e07f200b24fbdcf9fa69e2af53d5 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -5,6 +5,7 @@
 
 #include "vkcv/BufferManager.hpp"
 #include "vkcv/Core.hpp"
+#include <vkcv/Logger.hpp>
 
 namespace vkcv {
 	
@@ -23,7 +24,7 @@ namespace vkcv {
 	
 	BufferManager::~BufferManager() noexcept {
 		for (uint64_t id = 0; id < m_buffers.size(); id++) {
-			destroyBuffer(BufferHandle(id));
+			destroyBufferById(id);
 		}
 	}
 	
@@ -119,7 +120,7 @@ namespace vkcv {
 		
 		const uint64_t id = m_buffers.size();
 		m_buffers.push_back({ buffer, memory, size, nullptr, mappable });
-		return BufferHandle{ id };
+		return BufferHandle(id, [&](uint64_t id) { destroyBufferById(id); });
 	}
 	
 	struct StagingStepInfo {
@@ -158,7 +159,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		core->submitCommands(
+		core->recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
 					const vk::BufferCopy region (
@@ -315,9 +316,7 @@ namespace vkcv {
 		buffer.m_mapped = nullptr;
 	}
 	
-	void BufferManager::destroyBuffer(const BufferHandle& handle) {
-		const uint64_t id = handle.getId();
-		
+	void BufferManager::destroyBufferById(uint64_t id) {
 		if (id >= m_buffers.size()) {
 			return;
 		}
@@ -337,4 +336,33 @@ namespace vkcv {
 		}
 	}
 
+	void BufferManager ::recordBufferMemoryBarrier(const BufferHandle& handle, vk::CommandBuffer cmdBuffer) {
+
+		const uint64_t id = handle.getId();
+
+		if (id >= m_buffers.size()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "Invalid buffer handle");
+			return;
+		}
+
+		auto& buffer = m_buffers[id];
+		
+		vk::BufferMemoryBarrier memoryBarrier(
+			vk::AccessFlagBits::eMemoryWrite, 
+			vk::AccessFlagBits::eMemoryRead,
+			0,
+			0,
+			buffer.m_handle,
+			0,
+			buffer.m_size);
+
+		cmdBuffer.pipelineBarrier(
+			vk::PipelineStageFlagBits::eTopOfPipe,
+			vk::PipelineStageFlagBits::eBottomOfPipe,
+			{},
+			nullptr,
+			memoryBarrier,
+			nullptr);
+	}
+
 }
diff --git a/src/vkcv/CommandResources.cpp b/src/vkcv/CommandResources.cpp
index 71c990c3c222f2318c2f5744ff6295f667d9e6f8..a31e6967d85bd099fe5cbbc865b0e062212ca16e 100644
--- a/src/vkcv/CommandResources.cpp
+++ b/src/vkcv/CommandResources.cpp
@@ -1,6 +1,7 @@
 #include "vkcv/CommandResources.hpp"
 #include <iostream>
 
+#include "vkcv/Logger.hpp"
 
 namespace vkcv {
 
@@ -62,7 +63,7 @@ namespace vkcv {
 			return queueManager.getPresentQueue();
 		}
 		else {
-			std::cerr << "getQueueForSubmit error: unknown queue type" << std::endl;
+			vkcv_log(LogLevel::ERROR, "Unknown queue type");
 			return queueManager.getGraphicsQueues().front();	// graphics is the most general queue
 		}
 	}
diff --git a/src/vkcv/CommandStreamManager.cpp b/src/vkcv/CommandStreamManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a5b359b912d9cef36e0b03379d7f0f6f0951381
--- /dev/null
+++ b/src/vkcv/CommandStreamManager.cpp
@@ -0,0 +1,121 @@
+#include "vkcv/CommandStreamManager.hpp"
+#include "vkcv/Core.hpp"
+
+#include "vkcv/Logger.hpp"
+
+namespace vkcv {
+	CommandStreamManager::CommandStreamManager() noexcept : m_core(nullptr){}
+
+	CommandStreamManager::~CommandStreamManager() noexcept {
+		for (const auto& stream : m_commandStreams) {
+			if (stream.cmdBuffer && stream.cmdBuffer) {
+				m_core->getContext().getDevice().freeCommandBuffers(stream.cmdPool, stream.cmdBuffer);
+			}
+		}
+	}
+
+	void CommandStreamManager::init(Core* core) {
+		if (!core) {
+			vkcv_log(LogLevel::ERROR, "Requires valid core pointer");
+		}
+		m_core = core;
+	}
+
+	CommandStreamHandle CommandStreamManager::createCommandStream(
+		const vk::Queue queue, 
+		vk::CommandPool cmdPool) {
+
+		const vk::CommandBuffer cmdBuffer = allocateCommandBuffer(m_core->getContext().getDevice(), cmdPool);
+
+		CommandStream stream(cmdBuffer, queue, cmdPool);
+		beginCommandBuffer(stream.cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
+
+		// find unused stream
+		int unusedStreamIndex = -1;
+		for (int i = 0; i < m_commandStreams.size(); i++) {
+			if (m_commandStreams[i].cmdBuffer) {
+				// still in use
+			}
+			else {
+				unusedStreamIndex = i;
+				break;
+			}
+		}
+
+        const bool foundUnusedStream = unusedStreamIndex >= 0;
+        if (foundUnusedStream) {
+            m_commandStreams[unusedStreamIndex] = stream;
+            return CommandStreamHandle(unusedStreamIndex);
+        }
+
+        CommandStreamHandle handle(m_commandStreams.size());
+        m_commandStreams.push_back(stream);
+        return handle;
+    }
+
+	void CommandStreamManager::recordCommandsToStream(
+		const CommandStreamHandle   handle, 
+		const RecordCommandFunction record) {
+
+		const size_t id = handle.getId();
+		if (id >= m_commandStreams.size()) {
+			vkcv_log(LogLevel::ERROR, "Requires valid handle");
+			return;
+		}
+
+		CommandStream& stream = m_commandStreams[id];
+		record(stream.cmdBuffer);
+	}
+
+	void CommandStreamManager::addFinishCallbackToStream(
+		const CommandStreamHandle   handle, 
+		const FinishCommandFunction finish) {
+
+		const size_t id = handle.getId();
+		if (id >= m_commandStreams.size()) {
+			vkcv_log(LogLevel::ERROR, "Requires valid handle");
+			return;
+		}
+
+		CommandStream& stream = m_commandStreams[id];
+		stream.callbacks.push_back(finish);
+	}
+
+	void CommandStreamManager::submitCommandStreamSynchronous(
+		const CommandStreamHandle   handle,
+		std::vector<vk::Semaphore>  &waitSemaphores,
+		std::vector<vk::Semaphore>  &signalSemaphores) {
+
+		const size_t id = handle.getId();
+		if (id >= m_commandStreams.size()) {
+			vkcv_log(LogLevel::ERROR, "Requires valid handle");
+			return;
+		}
+		CommandStream& stream = m_commandStreams[id];
+		stream.cmdBuffer.end();
+
+		const auto device = m_core->getContext().getDevice();
+		const vk::Fence waitFence = createFence(device);
+		submitCommandBufferToQueue(stream.queue, stream.cmdBuffer, waitFence, waitSemaphores, signalSemaphores);
+		waitForFence(device, waitFence);
+		device.destroyFence(waitFence);
+
+		device.freeCommandBuffers(stream.cmdPool, stream.cmdBuffer);
+		stream.cmdBuffer    = nullptr;
+		stream.cmdPool      = nullptr;
+		stream.queue        = nullptr;
+
+		for (const auto& finishCallback : stream.callbacks) {
+			finishCallback();
+		}
+	}
+
+	vk::CommandBuffer CommandStreamManager::getStreamCommandBuffer(const CommandStreamHandle handle) {
+		const size_t id = handle.getId();
+		if (id >= m_commandStreams.size()) {
+			vkcv_log(LogLevel::ERROR, "Requires valid handle");
+			return nullptr;
+		}
+		return m_commandStreams[id].cmdBuffer;
+	}
+}
\ No newline at end of file
diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp
index b53a1a2c2db1008e7c69c880ef1c5a608d879021..ac133d1affc81702ee1a19b3f66810e606bec58d 100644
--- a/src/vkcv/Context.cpp
+++ b/src/vkcv/Context.cpp
@@ -275,7 +275,13 @@ namespace vkcv
 		deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		deviceCreateInfo.ppEnabledLayerNames = validationLayers.data();
 #endif
-		
+
+		// FIXME: check if device feature is supported
+		vk::PhysicalDeviceFeatures deviceFeatures;
+		deviceFeatures.fragmentStoresAndAtomics = true;
+		deviceFeatures.geometryShader = true;
+		deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
+
 		// Ablauf
 		// qCreateInfos erstellen --> braucht das Device
 		// device erstellen
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 0b0d3a24f0398a5efa17a4c33110adabbb97c15e..59f2cf3b652a88854b9ea4a4077f18749b9e6d51 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -13,8 +13,10 @@
 #include "SamplerManager.hpp"
 #include "ImageManager.hpp"
 #include "DescriptorManager.hpp"
-#include "Surface.hpp"
 #include "ImageLayoutTransitions.hpp"
+#include "vkcv/CommandStreamManager.hpp"
+#include <cmath>
+#include "vkcv/Logger.hpp"
 
 namespace vkcv
 {
@@ -32,40 +34,10 @@ namespace vkcv
         		instanceExtensions,
         		deviceExtensions
 		);
-	
-		const vk::SurfaceKHR surface = createSurface(
-				window.getWindow(),
-				context.getInstance(),
-				context.getPhysicalDevice()
-		);
-
-        SwapChain swapChain = SwapChain::create(window, context, surface);
-
-        std::vector<vk::Image> swapChainImages = context.getDevice().getSwapchainImagesKHR(swapChain.getSwapchain());
-        std::vector<vk::ImageView> imageViews;
-        imageViews.reserve( swapChainImages.size() );
-        //here can be swizzled with vk::ComponentSwizzle if needed
-        vk::ComponentMapping componentMapping(
-                vk::ComponentSwizzle::eR,
-                vk::ComponentSwizzle::eG,
-                vk::ComponentSwizzle::eB,
-                vk::ComponentSwizzle::eA );
-
-        vk::ImageSubresourceRange subResourceRange( vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 );
 
-        for ( auto image : swapChainImages )
-        {
-            vk::ImageViewCreateInfo imageViewCreateInfo(
-                    vk::ImageViewCreateFlags(),
-                    image,
-                    vk::ImageViewType::e2D,
-                    swapChain.getSurfaceFormat().format,
-                    componentMapping,
-                    subResourceRange
-            );
+        Swapchain swapChain = Swapchain::create(window, context);
 
-            imageViews.push_back(context.getDevice().createImageView(imageViewCreateInfo));
-        }
+		 std::vector<vk::ImageView> swapchainImageViews = createSwapchainImageViews( context, swapChain);
 
         const auto& queueManager = context.getQueueManager();
         
@@ -74,48 +46,59 @@ namespace vkcv
 		const auto						commandResources		= createCommandResources(context.getDevice(), queueFamilySet);
 		const auto						defaultSyncResources	= createSyncResources(context.getDevice());
 
-        window.e_resize.add([&](int width, int height){
-            recreateSwapchain(width,height);
-        });
-
-        return Core(std::move(context) , window, swapChain, imageViews, commandResources, defaultSyncResources);
+        return Core(std::move(context) , window, swapChain, swapchainImageViews, commandResources, defaultSyncResources);
     }
 
     const Context &Core::getContext() const
     {
         return m_Context;
     }
+    
+    const Swapchain& Core::getSwapchain() const {
+    	return m_swapchain;
+    }
 
-	Core::Core(Context &&context, Window &window , SwapChain swapChain,  std::vector<vk::ImageView> imageViews,
-		const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
+    Core::Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> swapchainImageViews,
+        const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
             m_Context(std::move(context)),
             m_window(window),
             m_swapchain(swapChain),
-            m_swapchainImageViews(imageViews),
             m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)},
             m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)},
             m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)),
-			m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())},
-			m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))),
-			m_ImageManager{std::unique_ptr<ImageManager>(new ImageManager(*m_BufferManager))},
+            m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())},
+            m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))),
+            m_ImageManager{std::unique_ptr<ImageManager>(new ImageManager(*m_BufferManager))},
+            m_CommandStreamManager{std::unique_ptr<CommandStreamManager>(new CommandStreamManager)},
             m_CommandResources(commandResources),
             m_SyncResources(syncResources)
 	{
-    	m_BufferManager->m_core = this;
-    	m_BufferManager->init();
-    	
-    	m_ImageManager->m_core = this;
+		m_BufferManager->m_core = this;
+		m_BufferManager->init();
+		m_CommandStreamManager->init(this);
+
+		m_ImageManager->m_core = this;
+		
+		e_resizeHandle = m_window.e_resize.add( [&](int width, int height) {
+			m_swapchain.signalSwapchainRecreation();
+		});
+
+		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+		m_ImageManager->setSwapchainImages(
+			swapchainImages, 
+			swapchainImageViews, 
+			swapChain.getExtent().width,
+			swapChain.getExtent().height,
+			swapChain.getFormat());
 	}
 
 	Core::~Core() noexcept {
+    	m_window.e_resize.remove(e_resizeHandle);
+    	
 		m_Context.getDevice().waitIdle();
-		for (auto image : m_swapchainImageViews) {
-			m_Context.m_Device.destroyImageView(image);
-		}
 
 		destroyCommandResources(m_Context.getDevice(), m_CommandResources);
 		destroySyncResources(m_Context.getDevice(), m_SyncResources);
-		destroyTemporaryFramebuffers();
 
 		m_Context.m_Device.destroySwapchainKHR(m_swapchain.getSwapchain());
 		m_Context.m_Instance.destroySurfaceKHR(m_swapchain.getSurface());
@@ -126,6 +109,13 @@ namespace vkcv
         return m_PipelineManager->createPipeline(config, *m_PassManager);
     }
 
+    PipelineHandle Core::createComputePipeline(
+        const ShaderProgram &shaderProgram, 
+        const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts)
+    {
+        return m_PipelineManager->createComputePipeline(shaderProgram, descriptorSetLayouts);
+    }
+
 
     PassHandle Core::createPass(const PassConfig &config)
     {
@@ -134,16 +124,23 @@ namespace vkcv
 
 	Result Core::acquireSwapchainImage() {
     	uint32_t imageIndex;
+		
+    	vk::Result result;
     	
-		const auto& acquireResult = m_Context.getDevice().acquireNextImageKHR(
-			m_swapchain.getSwapchain(), 
-			std::numeric_limits<uint64_t>::max(), 
-			m_SyncResources.swapchainImageAcquired,
-			nullptr, 
-			&imageIndex, {}
-		);
+		try {
+			result = m_Context.getDevice().acquireNextImageKHR(
+					m_swapchain.getSwapchain(),
+					std::numeric_limits<uint64_t>::max(),
+					m_SyncResources.swapchainImageAcquired,
+					nullptr,
+					&imageIndex, {}
+			);
+		} catch (vk::OutOfDateKHRError e) {
+			result = vk::Result::eErrorOutOfDateKHR;
+		}
 		
-		if (acquireResult != vk::Result::eSuccess) {
+		if (result != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::ERROR, "%s", vk::to_string(result).c_str());
 			return Result::ERROR;
 		}
 		
@@ -151,80 +148,111 @@ namespace vkcv
 		return Result::SUCCESS;
 	}
 
-	void Core::destroyTemporaryFramebuffers() {
-		for (const vk::Framebuffer f : m_TemporaryFramebuffers) {
-			m_Context.getDevice().destroyFramebuffer(f);
-		}
-		m_TemporaryFramebuffers.clear();
-	}
+	bool Core::beginFrame(uint32_t& width, uint32_t& height) {
+		if (m_swapchain.shouldUpdateSwapchain()) {
+			m_Context.getDevice().waitIdle();
 
-	void Core::beginFrame() {
+			m_swapchain.updateSwapchain(m_Context, m_window);
+			const auto swapchainViews = createSwapchainImageViews(m_Context, m_swapchain);
+			const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+
+			m_ImageManager->setSwapchainImages(swapchainImages, swapchainViews, width, height, m_swapchain.getFormat());
+		}
+		
     	if (acquireSwapchainImage() != Result::SUCCESS) {
-    		return;
+			vkcv_log(LogLevel::ERROR, "Acquire failed");
+    		
+    		m_currentSwapchainImageIndex = std::numeric_limits<uint32_t>::max();
     	}
-		m_Context.getDevice().waitIdle();	// FIMXE: this is a sin against graphics programming, but its getting late - Alex
-		destroyTemporaryFramebuffers();
-	}
-	
-	vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass& renderpass,
-									  const int width, const int height, const std::vector<vk::ImageView>& attachments) {
-		const vk::FramebufferCreateFlags flags = {};
-		const vk::FramebufferCreateInfo createInfo(flags, renderpass, attachments.size(), attachments.data(), width, height, 1);
-		return device.createFramebuffer(createInfo);
+		
+		m_Context.getDevice().waitIdle(); // TODO: this is a sin against graphics programming, but its getting late - Alex
+		
+		const auto& extent = m_swapchain.getExtent();
+		
+		width = extent.width;
+		height = extent.height;
+		
+		m_ImageManager->setCurrentSwapchainImageIndex(m_currentSwapchainImageIndex);
+
+		return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max());
 	}
 
-	void Core::renderMesh(
-		const PassHandle						renderpassHandle, 
-		const PipelineHandle					pipelineHandle, 
-		const int								width, 
-		const int								height, 
-		const size_t							pushConstantSize, 
-		const void								*pushConstantData,
-		const std::vector<VertexBufferBinding>& vertexBufferBindings, 
-		const BufferHandle						indexBuffer, 
-		const size_t							indexCount,
-		const vkcv::ResourcesHandle				resourceHandle,
-		const size_t							resourceDescriptorSetIndex
-		) {
+	void Core::recordDrawcallsToCmdStream(
+		const CommandStreamHandle       cmdStreamHandle,
+		const PassHandle                renderpassHandle, 
+		const PipelineHandle            pipelineHandle, 
+        const PushConstantData          &pushConstantData,
+        const std::vector<DrawcallInfo> &drawcalls,
+		const std::vector<ImageHandle>  &renderTargets) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
 		}
 
-		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
-		const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle);
-		
-		ImageHandle depthImage;
-		
-		for (const auto& attachment : passConfig.attachments) {
-			if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
-				depthImage = m_ImageManager->createImage(width, height, 1, attachment.format);
-				break;
+		uint32_t width;
+		uint32_t height;
+		if (renderTargets.size() > 0) {
+			const vkcv::ImageHandle firstImage = renderTargets[0];
+			if (firstImage.isSwapchainImage()) {
+				const auto& swapchainExtent = m_swapchain.getExtent();
+				width = swapchainExtent.width;
+				height = swapchainExtent.height;
+			}
+			else {
+				width = m_ImageManager->getImageWidth(firstImage);
+				height = m_ImageManager->getImageHeight(firstImage);
 			}
 		}
-		
-		const vk::ImageView imageView	= m_swapchainImageViews[m_currentSwapchainImageIndex];
+		else {
+			width = 1;
+			height = 1;
+		}
+		// TODO: validate that width/height match for all attachments
+
+		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
+		const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle);
+
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
-        const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
+		const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
-		const vk::Buffer vulkanIndexBuffer	= m_BufferManager->getBuffer(indexBuffer);
 
-		std::vector<vk::ImageView> attachments;
-		attachments.push_back(imageView);
-		
-		if (depthImage) {
-			attachments.push_back(m_ImageManager->getVulkanImageView(depthImage));
+		std::vector<vk::ImageView> attachmentsViews;
+		for (const ImageHandle handle : renderTargets) {
+			vk::ImageView targetHandle;
+			const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
+
+			targetHandle = m_ImageManager->getVulkanImageView(handle);
+			const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle));
+			const vk::ImageLayout targetLayout = 
+				isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal;
+			m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer);
+			attachmentsViews.push_back(targetHandle);
 		}
 		
-		const vk::Framebuffer framebuffer = createFramebuffer(
-				m_Context.getDevice(),
-				renderpass,
-				width,
-				height,
-				attachments
+        const vk::FramebufferCreateInfo createInfo(
+            {},
+            renderpass,
+            static_cast<uint32_t>(attachmentsViews.size()),
+            attachmentsViews.data(),
+            width,
+            height,
+            1
 		);
 		
-		m_TemporaryFramebuffers.push_back(framebuffer);
+		vk::Framebuffer framebuffer = m_Context.m_Device.createFramebuffer(createInfo);
+        
+        if (!framebuffer) {
+			vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer");
+            return;
+        }
+
+        vk::Viewport dynamicViewport(
+        		0.0f, 0.0f,
+            	static_cast<float>(width), static_cast<float>(height),
+            0.0f, 1.0f
+		);
+
+        vk::Rect2D dynamicScissor({0, 0}, {width, height});
 
 		auto &bufferManager = m_BufferManager;
 
@@ -232,50 +260,86 @@ namespace vkcv
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
 
-		submitCommands(submitInfo, [&](const vk::CommandBuffer& cmdBuffer) {
-			std::vector<vk::ClearValue> clearValues;
-			
-			for (const auto& attachment : passConfig.attachments) {
-				if (attachment.load_operation == AttachmentOperation::CLEAR) {
-					float clear = 0.0f;
-					
-					if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
-						clear = 1.0f;
-					}
-					
-					clearValues.emplace_back(std::array<float, 4>{
-							clear,
-							clear,
-							clear,
-							1.f
-					});
-				}
-			}
+		auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) {
+            std::vector<vk::ClearValue> clearValues;
+
+            for (const auto& attachment : passConfig.attachments) {
+                if (attachment.load_operation == AttachmentOperation::CLEAR) {
+                    float clear = 0.0f;
+
+                    if (isDepthFormat(attachment.format)) {
+                        clear = 1.0f;
+                    }
+
+                    clearValues.emplace_back(std::array<float, 4>{
+                            clear,
+                            clear,
+                            clear,
+                            1.f
+                    });
+                }
+            }
+
+            const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data());
+            const vk::SubpassContents subpassContents = {};
+            cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
 
-			const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data());
-			const vk::SubpassContents subpassContents = {};
-			cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
+            cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
+            const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
+            if(pipeConfig.m_UseDynamicViewport)
+            {
+                cmdBuffer.setViewport(0, 1, &dynamicViewport);
+                cmdBuffer.setScissor(0, 1, &dynamicScissor);
+            }
 
-			for (uint32_t i = 0; i < vertexBufferBindings.size(); i++) {
-				const auto &vertexBinding = vertexBufferBindings[i];
-				const auto vertexBuffer = bufferManager->getBuffer(vertexBinding.buffer);
-				cmdBuffer.bindVertexBuffers(i, (vertexBuffer), (vertexBinding.offset));
+            for (int i = 0; i < drawcalls.size(); i++) {
+                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
+            }
+
+            cmdBuffer.endRenderPass();
+        };
+
+        auto finishFunction = [framebuffer, this]()
+        {
+            m_Context.m_Device.destroy(framebuffer);
+        };
+
+		recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction);
+	}
+
+	void Core::recordComputeDispatchToCmdStream(
+		CommandStreamHandle cmdStreamHandle,
+		PipelineHandle computePipeline,
+		const uint32_t dispatchCount[3],
+		const std::vector<DescriptorSetUsage>& descriptorSetUsages,
+		const PushConstantData& pushConstantData) {
+
+		auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) {
+
+			const auto pipelineLayout = m_PipelineManager->getVkPipelineLayout(computePipeline);
+
+			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, m_PipelineManager->getVkPipeline(computePipeline));
+			for (const auto& usage : descriptorSetUsages) {
+				cmdBuffer.bindDescriptorSets(
+					vk::PipelineBindPoint::eCompute,
+					pipelineLayout,
+					usage.setLocation,
+					{ usage.vulkanHandle },
+					{});
 			}
-			
-			if (resourceHandle) {
-				const vk::DescriptorSet descriptorSet = m_DescriptorManager->getDescriptorSet(resourceHandle, resourceDescriptorSetIndex);
-				cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, descriptorSet, nullptr);
+			if (pushConstantData.sizePerDrawcall > 0) {
+				cmdBuffer.pushConstants(
+					pipelineLayout,
+					vk::ShaderStageFlagBits::eCompute,
+					0,
+					pushConstantData.sizePerDrawcall,
+					pushConstantData.data);
 			}
-			
-			cmdBuffer.bindIndexBuffer(vulkanIndexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
-			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
-			cmdBuffer.drawIndexed(indexCount, 1, 0, 0, {});
-			cmdBuffer.endRenderPass();
-		}, [&]() {
-			m_ImageManager->destroyImage(depthImage);
-		});
+			cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]);
+		};
+
+		recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr);
 	}
 
 	void Core::endFrame() {
@@ -284,36 +348,37 @@ namespace vkcv
 		}
   
 		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
-		const vk::Image presentImage = swapchainImages[m_currentSwapchainImageIndex];
-		
+
 		const auto& queueManager = m_Context.getQueueManager();
-		std::array<vk::Semaphore, 2> waitSemaphores{ 
-			m_SyncResources.renderFinished, 
-			m_SyncResources.swapchainImageAcquired };
+		std::array<vk::Semaphore, 2> waitSemaphores{
+			m_SyncResources.renderFinished,
+			m_SyncResources.swapchainImageAcquired
+		};
 
-		vk::Result presentResult;
 		const vk::SwapchainKHR& swapchain = m_swapchain.getSwapchain();
 		const vk::PresentInfoKHR presentInfo(
 			waitSemaphores,
 			swapchain,
-			m_currentSwapchainImageIndex, 
-			presentResult);
-        queueManager.getPresentQueue().handle.presentKHR(presentInfo);
-		if (presentResult != vk::Result::eSuccess) {
-			std::cout << "Error: swapchain present failed" << std::endl;
+			m_currentSwapchainImageIndex
+		);
+		
+		vk::Result result;
+		
+		try {
+			result = queueManager.getPresentQueue().handle.presentKHR(presentInfo);
+		} catch (vk::OutOfDateKHRError e) {
+			result = vk::Result::eErrorOutOfDateKHR;
+		}
+		
+		if (result != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::ERROR, "Swapchain present failed (%s)", vk::to_string(result).c_str());
 		}
 	}
-
-	vk::Format Core::getSwapchainImageFormat() {
-		return m_swapchain.getSurfaceFormat().format;
-	}
-
-    void Core::recreateSwapchain(int width, int height) {
-        /* boilerplate for #34 */
-        std::cout << "Resized to : " << width << " , " << height << std::endl;
-    }
 	
-	void Core::submitCommands(const SubmitInfo &submitInfo, const RecordCommandFunction& record, const FinishCommandFunction& finish)
+	void Core::recordAndSubmitCommandsImmediate(
+		const SubmitInfo &submitInfo, 
+		const RecordCommandFunction &record, 
+		const FinishCommandFunction &finish)
 	{
 		const vk::Device& device = m_Context.getDevice();
 
@@ -324,10 +389,12 @@ namespace vkcv
 		beginCommandBuffer(cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
 		record(cmdBuffer);
 		cmdBuffer.end();
+		
+		vk::Fence waitFence = createFence(device);
 
-		const vk::Fence waitFence = createFence(device);
 		submitCommandBufferToQueue(queue.handle, cmdBuffer, waitFence, submitInfo.waitSemaphores, submitInfo.signalSemaphores);
 		waitForFence(device, waitFence);
+		
 		device.destroyFence(waitFence);
 		
 		device.freeCommandBuffers(cmdPool, cmdBuffer);
@@ -336,33 +403,154 @@ namespace vkcv
 			finish();
 		}
 	}
-	
+
+	CommandStreamHandle Core::createCommandStream(QueueType queueType) {
+
+		const vk::Device&       device  = m_Context.getDevice();
+		const vkcv::Queue       queue   = getQueueForSubmit(queueType, m_Context.getQueueManager());
+		const vk::CommandPool   cmdPool = chooseCmdPool(queue, m_CommandResources);
+
+		return m_CommandStreamManager->createCommandStream(queue.handle, cmdPool);
+	}
+
+    void Core::recordCommandsToStream(
+		const CommandStreamHandle   cmdStreamHandle,
+		const RecordCommandFunction &record, 
+		const FinishCommandFunction &finish) {
+
+		m_CommandStreamManager->recordCommandsToStream(cmdStreamHandle, record);
+		if (finish) {
+			m_CommandStreamManager->addFinishCallbackToStream(cmdStreamHandle, finish);
+		}
+	}
+
+	void Core::submitCommandStream(const CommandStreamHandle handle) {
+		std::vector<vk::Semaphore> waitSemaphores;
+		// FIXME: add proper user controllable sync
+		std::vector<vk::Semaphore> signalSemaphores = { m_SyncResources.renderFinished };
+		m_CommandStreamManager->submitCommandStreamSynchronous(handle, waitSemaphores, signalSemaphores);
+	}
+
 	SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
 									  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode) {
-    	return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
-    }
-    
-	Image Core::createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
+	}
+
+	Image Core::createImage(
+		vk::Format  format,
+		uint32_t    width,
+		uint32_t    height,
+		uint32_t    depth,
+		bool        createMipChain,
+		bool        supportStorage,
+		bool        supportColorAttachment)
+	{
+
+		uint32_t mipCount = 1;
+		if (createMipChain) {
+			mipCount = 1 + (uint32_t)std::floor(std::log2(std::max(width, std::max(height, depth))));
+		}
+
+		return Image::create(
+			m_ImageManager.get(), 
+			format,
+			width,
+			height,
+			depth,
+			mipCount,
+			supportStorage,
+			supportColorAttachment);
+	}
+
+	const uint32_t Core::getImageWidth(ImageHandle imageHandle)
 	{
-    	return Image::create(m_ImageManager.get(), format, width, height, depth);
+		return m_ImageManager->getImageWidth(imageHandle);
 	}
 
-    ResourcesHandle Core::createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets)
+	const uint32_t Core::getImageHeight(ImageHandle imageHandle)
+	{
+		return m_ImageManager->getImageHeight(imageHandle);
+	}
+
+    DescriptorSetHandle Core::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
     {
-        return m_DescriptorManager->createResourceDescription(descriptorSets);
+        return m_DescriptorManager->createDescriptorSet(bindings);
     }
 
-	void Core::writeResourceDescription(ResourcesHandle handle, size_t setIndex, const DescriptorWrites &writes) {
-		m_DescriptorManager->writeResourceDescription(
-			handle, 
-			setIndex, 
+	void Core::writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes) {
+		m_DescriptorManager->writeDescriptorSet(
+			handle,
 			writes, 
 			*m_ImageManager, 
 			*m_BufferManager, 
 			*m_SamplerManager);
 	}
 
-	vk::DescriptorSetLayout Core::getDescritorSetLayout(ResourcesHandle handle, size_t setIndex) {
-		return m_DescriptorManager->getDescriptorSetLayout(handle, setIndex);
+	DescriptorSet Core::getDescriptorSet(const DescriptorSetHandle handle) const {
+		return m_DescriptorManager->getDescriptorSet(handle);
 	}
+
+    std::vector<vk::ImageView> Core::createSwapchainImageViews( Context &context, Swapchain& swapChain){
+        std::vector<vk::ImageView> imageViews;
+        std::vector<vk::Image> swapChainImages = context.getDevice().getSwapchainImagesKHR(swapChain.getSwapchain());
+        imageViews.reserve( swapChainImages.size() );
+        //here can be swizzled with vk::ComponentSwizzle if needed
+        vk::ComponentMapping componentMapping(
+                vk::ComponentSwizzle::eR,
+                vk::ComponentSwizzle::eG,
+                vk::ComponentSwizzle::eB,
+                vk::ComponentSwizzle::eA );
+
+        vk::ImageSubresourceRange subResourceRange( vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 );
+
+        for ( auto image : swapChainImages )
+        {
+            vk::ImageViewCreateInfo imageViewCreateInfo(
+                    vk::ImageViewCreateFlags(),
+                    image,
+                    vk::ImageViewType::e2D,
+                    swapChain.getFormat(),
+                    componentMapping,
+                    subResourceRange);
+
+            imageViews.push_back(context.getDevice().createImageView(imageViewCreateInfo));
+        }
+        return imageViews;
+    }
+
+	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle cmdStream) {
+		auto swapchainHandle = ImageHandle::createSwapchainImageHandle();
+		recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(swapchainHandle, vk::ImageLayout::ePresentSrcKHR, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eGeneral, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer) {
+		recordCommandsToStream(cmdStream, [buffer, this](const vk::CommandBuffer cmdBuffer) {
+			m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer);
+		}, nullptr);
+	}
+	
+	vk::ImageView Core::getSwapchainImageView() const {
+    	return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle());
+    }
+	
 }
diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorConfig.cpp
index c4f6e326560e91747d206fecc983525a5b7bb6dc..54e879ac7e6ec7825a4c003899e3c264454c547f 100644
--- a/src/vkcv/DescriptorConfig.cpp
+++ b/src/vkcv/DescriptorConfig.cpp
@@ -1,20 +1,15 @@
 #include "vkcv/DescriptorConfig.hpp"
 
-#include <utility>
-
 namespace vkcv {
-
-    DescriptorBinding::DescriptorBinding(
-        DescriptorType descriptorType,
-        uint32_t descriptorCount,
-        ShaderStage shaderStage
-    ) noexcept :
-        descriptorType{descriptorType},
-        descriptorCount{descriptorCount},
-        shaderStage{shaderStage}
-    {};
-
-    DescriptorSetConfig::DescriptorSetConfig(std::vector<DescriptorBinding> bindings) noexcept :
-        bindings{std::move(bindings)}
-    {};
+	DescriptorBinding::DescriptorBinding(
+		uint32_t bindingID,
+		DescriptorType descriptorType,
+		uint32_t descriptorCount,
+		ShaderStage shaderStage) noexcept
+		:
+		bindingID(bindingID),
+		descriptorType(descriptorType),
+		descriptorCount(descriptorCount),
+		shaderStage(shaderStage) {}
+	
 }
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index cf689b44a3865585fcc2d52620badbb4919da644..8e565a766cd407dc33c0291d3d07b01d6d3066e7 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -1,89 +1,88 @@
 #include "DescriptorManager.hpp"
 
+#include "vkcv/Logger.hpp"
+
 namespace vkcv
 {
-    DescriptorManager::ResourceDescription::ResourceDescription(std::vector<vk::DescriptorSet> sets,
-                                                                std::vector<vk::DescriptorSetLayout> layouts) noexcept :
-    descriptorSets{std::move(sets)},
-    descriptorSetLayouts{std::move(layouts)}
-    {}
     DescriptorManager::DescriptorManager(vk::Device device) noexcept:
-        m_Device{ device }, m_NextResourceDescriptionID{ 0 }
+        m_Device{ device }
     {
         /**
-         * Allocate a set size for the initial pool, namely 1000 units of each descriptor type below.
+         * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type below.
+		 * Finally, create an initial pool.
          */
-        const std::vector<vk::DescriptorPoolSize> poolSizes = {vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
-                                                    vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
-                                                    vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
-                                                    vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000)};
+		m_PoolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
+													vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
+													vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
+													vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000) };
 
-        vk::DescriptorPoolCreateInfo poolInfo({},
-                                              1000,
-                                              static_cast<uint32_t>(poolSizes.size()),
-                                              poolSizes.data());
+		m_PoolInfo = vk::DescriptorPoolCreateInfo({},
+			1000,
+			static_cast<uint32_t>(m_PoolSizes.size()),
+			m_PoolSizes.data());
 
-        if(m_Device.createDescriptorPool(&poolInfo, nullptr, &m_Pool) != vk::Result::eSuccess)
-        {
-            std::cout << "FAILED TO ALLOCATED DESCRIPTOR POOL." << std::endl;
-            m_Pool = nullptr;
-        };
+		allocateDescriptorPool();
     }
 
     DescriptorManager::~DescriptorManager() noexcept
     {
-        for(const auto &resource : m_ResourceDescriptions)
-        {
-            for(const auto &layout : resource.descriptorSetLayouts)
-                m_Device.destroyDescriptorSetLayout(layout);
+        for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) {
+			destroyDescriptorSetById(id);
         }
-        m_Device.destroy(m_Pool);
+		m_DescriptorSets.clear();
+		for (const auto &pool : m_Pools) {
+			m_Device.destroy(pool);
+		}
     }
 
-    ResourcesHandle DescriptorManager::createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets)
+    DescriptorSetHandle DescriptorManager::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
     {
-        std::vector<vk::DescriptorSet> vk_sets;
-        std::vector<vk::DescriptorSetLayout> vk_setLayouts;
+        std::vector<vk::DescriptorSetLayoutBinding> setBindings = {};
 
-        for (const auto &set : descriptorSets) {
-            std::vector<vk::DescriptorSetLayoutBinding> setBindings = {};
+        //create each set's binding
+        for (uint32_t i = 0; i < bindings.size(); i++) {
+            vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding(
+                bindings[i].bindingID,
+                convertDescriptorTypeFlag(bindings[i].descriptorType),
+                bindings[i].descriptorCount,
+                convertShaderStageFlag(bindings[i].shaderStage));
+            setBindings.push_back(descriptorSetLayoutBinding);
+        }
 
-            //create each set's binding
-            for (uint32_t j = 0; j < set.bindings.size(); j++) {
-                vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding(
-                        j,
-                        convertDescriptorTypeFlag(set.bindings[j].descriptorType),
-                        set.bindings[j].descriptorCount,
-                        convertShaderStageFlag(set.bindings[j].shaderStage));
-                setBindings.push_back(descriptorSetLayoutBinding);
-            }
+        DescriptorSet set;
 
-            //create the descriptor set's layout from the bindings gathered above
-            vk::DescriptorSetLayoutCreateInfo layoutInfo({}, setBindings);
-            vk::DescriptorSetLayout layout = nullptr;
-            if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &layout) != vk::Result::eSuccess)
-            {
-                std::cout << "FAILED TO CREATE DESCRIPTOR SET LAYOUT" << std::endl;
-                return ResourcesHandle();
-            };
-            vk_setLayouts.push_back(layout);
-        }
-        //create and allocate the set(s) based on the layouts that have been gathered above
-        vk_sets.resize(vk_setLayouts.size());
-        vk::DescriptorSetAllocateInfo allocInfo(m_Pool, vk_sets.size(), vk_setLayouts.data());
-        auto result = m_Device.allocateDescriptorSets(&allocInfo, vk_sets.data());
+        //create the descriptor set's layout from the bindings gathered above
+        vk::DescriptorSetLayoutCreateInfo layoutInfo({}, setBindings);
+        if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess)
+        {
+			vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
+            return DescriptorSetHandle();
+        };
+        
+        //create and allocate the set based on the layout that have been gathered above
+        vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &set.layout);
+        auto result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle);
         if(result != vk::Result::eSuccess)
         {
-            std::cout << "FAILED TO ALLOCATE DESCRIPTOR SET" << std::endl;
-            std::cout << vk::to_string(result) << std::endl;
-            for(const auto &layout : vk_setLayouts)
-                m_Device.destroy(layout);
-
-            return ResourcesHandle();
+			//create a new descriptor pool if the previous one ran out of memory
+			if (result == vk::Result::eErrorOutOfPoolMemory) {
+				allocateDescriptorPool();
+				allocInfo.setDescriptorPool(m_Pools.back());
+				result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle);
+			}
+			if (result != vk::Result::eSuccess) {
+				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
+						 vk::to_string(result).c_str());
+				
+				m_Device.destroy(set.layout);
+				return DescriptorSetHandle();
+			}
         };
 
-        m_ResourceDescriptions.emplace_back(vk_sets, vk_setLayouts);
-        return ResourcesHandle(m_NextResourceDescriptionID++);
+        const uint64_t id = m_DescriptorSets.size();
+
+        m_DescriptorSets.push_back(set);
+        return DescriptorSetHandle(id, [&](uint64_t id) { destroyDescriptorSetById(id); });
     }
     
     struct WriteDescriptorSetInfo {
@@ -93,15 +92,14 @@ namespace vkcv
 		vk::DescriptorType type;
     };
 
-	void DescriptorManager::writeResourceDescription(
-		ResourcesHandle			handle, 
-		size_t					setIndex,
+	void DescriptorManager::writeDescriptorSet(
+		const DescriptorSetHandle	&handle,
 		const DescriptorWrites	&writes,
 		const ImageManager		&imageManager, 
 		const BufferManager		&bufferManager,
 		const SamplerManager	&samplerManager) {
 
-		vk::DescriptorSet set = m_ResourceDescriptions[handle.getId()].descriptorSets[setIndex];
+		vk::DescriptorSet set = m_DescriptorSets[handle.getId()].vulkanHandle;
 
 		std::vector<vk::DescriptorImageInfo> imageInfos;
 		std::vector<vk::DescriptorBufferInfo> bufferInfos;
@@ -109,10 +107,11 @@ namespace vkcv
 		std::vector<WriteDescriptorSetInfo> writeInfos;
 
 		for (const auto& write : writes.sampledImageWrites) {
+		    vk::ImageLayout layout = write.useGeneralLayout ? vk::ImageLayout::eGeneral : vk::ImageLayout::eShaderReadOnlyOptimal;
 			const vk::DescriptorImageInfo imageInfo(
 				nullptr,
-				imageManager.getVulkanImageView(write.image),
-				vk::ImageLayout::eShaderReadOnlyOptimal
+				imageManager.getVulkanImageView(write.image, write.mipLevel),
+                layout
 			);
 			
 			imageInfos.push_back(imageInfo);
@@ -130,7 +129,7 @@ namespace vkcv
 		for (const auto& write : writes.storageImageWrites) {
 			const vk::DescriptorImageInfo imageInfo(
 				nullptr,
-				imageManager.getVulkanImageView(write.image),
+				imageManager.getVulkanImageView(write.image, write.mipLevel),
 				vk::ImageLayout::eGeneral
 			);
 			
@@ -224,12 +223,8 @@ namespace vkcv
 		m_Device.updateDescriptorSets(vulkanWrites, nullptr);
 	}
 
-	vk::DescriptorSet DescriptorManager::getDescriptorSet(ResourcesHandle handle, size_t index) {
-		return m_ResourceDescriptions[handle.getId()].descriptorSets[index];
-	}
-
-	vk::DescriptorSetLayout DescriptorManager::getDescriptorSetLayout(ResourcesHandle handle, size_t index) {
-		return m_ResourceDescriptions[handle.getId()].descriptorSetLayouts[index];
+	DescriptorSet DescriptorManager::getDescriptorSet(const DescriptorSetHandle handle) const {
+		return m_DescriptorSets[handle.getId()];
 	}
 
     vk::DescriptorType DescriptorManager::convertDescriptorTypeFlag(DescriptorType type) {
@@ -246,7 +241,7 @@ namespace vkcv
 			case DescriptorType::IMAGE_STORAGE:
 				return vk::DescriptorType::eStorageImage;
             default:
-				std::cerr << "Error: DescriptorManager::convertDescriptorTypeFlag, unknown DescriptorType" << std::endl;
+				vkcv_log(LogLevel::ERROR, "Unknown DescriptorType");
                 return vk::DescriptorType::eUniformBuffer;
         }
     }
@@ -270,5 +265,30 @@ namespace vkcv
                 return vk::ShaderStageFlagBits::eAll;
         }
     }
+    
+    void DescriptorManager::destroyDescriptorSetById(uint64_t id) {
+		if (id >= m_DescriptorSets.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid id");
+			return;
+		}
+		
+		auto& set = m_DescriptorSets[id];
+		if (set.layout) {
+			m_Device.destroyDescriptorSetLayout(set.layout);
+			set.layout = nullptr;
+		}
+		// FIXME: descriptor set itself not destroyed
+	}
+
+	vk::DescriptorPool DescriptorManager::allocateDescriptorPool() {
+		vk::DescriptorPool pool;
+		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess)
+		{
+			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
+			pool = nullptr;
+		};
+		m_Pools.push_back(pool);
+		return pool;
+	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp
index 937fb278cda241e9153881d5c27662ac929781cd..d18be64f3b069af68cecce68f6fa623c81f8dfa4 100644
--- a/src/vkcv/DescriptorManager.hpp
+++ b/src/vkcv/DescriptorManager.hpp
@@ -1,3 +1,8 @@
+/**
+ * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann
+ * @file src/vkcv/DescriptorManager.cpp
+ * @brief Creation and handling of descriptor sets and the respective descriptor pools
+ */
 #include <vulkan/vulkan.hpp>
 
 #include "vkcv/Handles.hpp"
@@ -16,50 +21,29 @@ namespace vkcv
 	    explicit DescriptorManager(vk::Device device) noexcept;
 	    ~DescriptorManager() noexcept;
 
-		/**
-		* Creates all vk::DescriptorSets and allocates them from the pool. 
-		* DescriptorSets are put inside a ResourceDescription struct. 
-		* Structs are then put into m_ResourceDescriptions.
-		* @param[in] vector of filled vkcv::DescriptorSet structs
-		* @return index into that objects a resource handle
-		*/
-        ResourcesHandle createResourceDescription(const std::vector<DescriptorSetConfig> & descriptorSets);
+        DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &descriptorBindings);
 
-		void writeResourceDescription(
-			ResourcesHandle			handle,
-			size_t					setIndex,
-			const DescriptorWrites& writes,
-			const ImageManager& imageManager,
-			const BufferManager& bufferManager,
-			const SamplerManager& samplerManager);
+		void writeDescriptorSet(
+			const DescriptorSetHandle	&handle,
+			const DescriptorWrites  &writes,
+			const ImageManager      &imageManager,
+			const BufferManager     &bufferManager,
+			const SamplerManager    &samplerManager);
 
-		vk::DescriptorSet		getDescriptorSet(ResourcesHandle handle, size_t index);
-		vk::DescriptorSetLayout getDescriptorSetLayout(ResourcesHandle handle, size_t index);
+		[[nodiscard]]
+		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
 
 	private:
-		vk::Device			m_Device;
-        vk::DescriptorPool	m_Pool;
+		vk::Device m_Device;
+		std::vector<vk::DescriptorPool>	m_Pools;
+		std::vector<vk::DescriptorPoolSize> m_PoolSizes;
+		vk::DescriptorPoolCreateInfo m_PoolInfo;
 
 
-		/**
-		* Container for all resources requested by the user in one call of createResourceDescription.
-		* Includes descriptor sets and the respective descriptor set layouts.
-		*/
-        struct ResourceDescription
-        {
-            ResourceDescription() = delete;
-            ResourceDescription(std::vector<vk::DescriptorSet> sets, std::vector<vk::DescriptorSetLayout> layouts) noexcept;
-
-            std::vector<vk::DescriptorSet> descriptorSets;
-            std::vector<vk::DescriptorSetLayout> descriptorSetLayouts;
-        };
-
 		/**
 		* Contains all the resource descriptions that were requested by the user in calls of createResourceDescription.
 		*/
-        std::vector<ResourceDescription> m_ResourceDescriptions;
-        // Counter for the vector above
-        uint64_t m_NextResourceDescriptionID;
+        std::vector<DescriptorSet> m_DescriptorSets;
 		
 		/**
 		* Converts the flags of the descriptor types from VulkanCV (vkcv) to Vulkan (vk).
@@ -73,5 +57,19 @@ namespace vkcv
 		* @return vk flag of the ShaderStage
 		*/
 		static vk::ShaderStageFlagBits convertShaderStageFlag(ShaderStage stage);
+		
+		/**
+		* Destroys a specific resource description
+		* @param[in] the handle id of the respective resource description
+		*/
+		void destroyDescriptorSetById(uint64_t id);
+
+		/**
+		* creates a descriptor pool based on the poolSizes and poolInfo defined in the constructor
+		* is called initially in the constructor and then every time the pool runs out memory
+		* @return a DescriptorPool object
+		*/
+		vk::DescriptorPool allocateDescriptorPool();
+		
 	};
 }
\ No newline at end of file
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df7b7bbcb3fe278622cd160593eb750db00ec7b1
--- /dev/null
+++ b/src/vkcv/DrawcallRecording.cpp
@@ -0,0 +1,45 @@
+#include <vkcv/DrawcallRecording.hpp>
+
+namespace vkcv {
+
+    void recordDrawcall(
+        const DrawcallInfo      &drawcall,
+        vk::CommandBuffer       cmdBuffer,
+        vk::PipelineLayout      pipelineLayout,
+        const PushConstantData  &pushConstantData,
+        const size_t            drawcallIndex) {
+
+        for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) {
+            const auto& vertexBinding = drawcall.mesh.vertexBufferBindings[i];
+            cmdBuffer.bindVertexBuffers(i, vertexBinding.buffer, vertexBinding.offset);
+        }
+
+        for (const auto& descriptorUsage : drawcall.descriptorSets) {
+            cmdBuffer.bindDescriptorSets(
+                vk::PipelineBindPoint::eGraphics,
+                pipelineLayout,
+                descriptorUsage.setLocation,
+                descriptorUsage.vulkanHandle,
+                nullptr);
+        }
+
+        const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall;
+        // char* cast because void* does not support pointer arithmetic
+        const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data;
+
+        cmdBuffer.pushConstants(
+            pipelineLayout,
+            vk::ShaderStageFlagBits::eAll,
+            0,
+            pushConstantData.sizePerDrawcall,
+            drawcallPushConstantData);
+
+        if (drawcall.mesh.indexBuffer) {
+            cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
+            cmdBuffer.drawIndexed(drawcall.mesh.indexCount, 1, 0, 0, {});
+        }
+        else {
+            cmdBuffer.draw(drawcall.mesh.indexCount, 1, 0, 0, {});
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp
index bd465d4e71da31c554d82ceb4b8bce7b0ee04129..020489418c8e2db6ce2062d6fd20f06f90a05c37 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -3,17 +3,75 @@
 namespace vkcv {
 	
 	Handle::Handle() :
-		m_id(UINT64_MAX)
+	m_id(UINT64_MAX), m_rc(nullptr), m_destroy(nullptr)
 	{}
 	
-	Handle::Handle(uint64_t id) :
-		m_id(id)
+	Handle::Handle(uint64_t id, const HandleDestroyFunction& destroy) :
+		m_id(id), m_rc(new uint64_t(1)), m_destroy(destroy)
 	{}
 	
+	Handle::~Handle() {
+		if ((m_rc) && (--(*m_rc) == 0)) {
+			if (m_destroy) {
+				m_destroy(m_id);
+			}
+			
+			delete m_rc;
+		}
+	}
+	
+	Handle::Handle(const Handle &other) :
+		m_id(other.m_id),
+		m_rc(other.m_rc),
+		m_destroy(other.m_destroy)
+	{
+		if (m_rc) {
+			++(*m_rc);
+		}
+	}
+	
+	Handle::Handle(Handle &&other) noexcept :
+		m_id(other.m_id),
+		m_rc(other.m_rc),
+		m_destroy(other.m_destroy)
+	{
+		other.m_rc = nullptr;
+	}
+	
+	Handle &Handle::operator=(const Handle &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_id = other.m_id;
+		m_rc = other.m_rc;
+		m_destroy = other.m_destroy;
+		
+		if (m_rc) {
+			++(*m_rc);
+		}
+		
+		return *this;
+	}
+	
+	Handle &Handle::operator=(Handle &&other) noexcept {
+		m_id = other.m_id;
+		m_rc = other.m_rc;
+		m_destroy = other.m_destroy;
+		
+		other.m_rc = nullptr;
+		
+		return *this;
+	}
+	
 	uint64_t Handle::getId() const {
 		return m_id;
 	}
 	
+	uint64_t Handle::getRC() const {
+		return m_rc? *m_rc : 0;
+	}
+	
 	Handle::operator bool() const {
 		return (m_id < UINT64_MAX);
 	}
@@ -24,10 +82,18 @@ namespace vkcv {
 	
 	std::ostream& operator << (std::ostream& out, const Handle& handle) {
 		if (handle) {
-			return out << "[Handle: " << handle.getId() << "]";
+			return out << "[Handle: " << handle.getId() << ":" << handle.getRC() << "]";
 		} else {
 			return out << "[Handle: none]";
 		}
 	}
 	
+	bool ImageHandle::isSwapchainImage() const {
+		return (getId() == UINT64_MAX - 1);
+	}
+	
+	ImageHandle ImageHandle::createSwapchainImageHandle(const HandleDestroyFunction &destroy) {
+		return ImageHandle(uint64_t(UINT64_MAX - 1), destroy);
+	}
+	
 }
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index 7104f784231418da4f1c26364459dbf735fdde97..c48b015335e00f23a892bb96d3e89a2c0877ae61 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -8,55 +8,74 @@
 
 namespace vkcv{
 	
-	Image Image::create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+	bool isDepthFormat(const vk::Format format) {
+		switch (format) {
+			case(vk::Format::eD16Unorm):        return true;
+			case(vk::Format::eD16UnormS8Uint):  return true;
+			case(vk::Format::eD24UnormS8Uint):  return true;
+			case(vk::Format::eD32Sfloat):       return true;
+			case(vk::Format::eD32SfloatS8Uint): return true;
+			default:                            return false;
+		}
+	}
+
+	Image Image::create(
+		ImageManager*   manager,
+		vk::Format      format,
+		uint32_t        width,
+		uint32_t        height,
+		uint32_t        depth,
+		uint32_t        mipCount,
+		bool            supportStorage,
+		bool            supportColorAttachment)
 	{
-		return Image(manager, manager->createImage(width, height, depth, format), format, width, height, depth);
+		return Image(manager, manager->createImage(width, height, depth, format, mipCount, supportStorage, supportColorAttachment));
 	}
 	
 	vk::Format Image::getFormat() const {
-		return m_format;
+		return m_manager->getImageFormat(m_handle);
 	}
 	
 	uint32_t Image::getWidth() const {
-		return m_width;
+		return m_manager->getImageWidth(m_handle);
 	}
 	
 	uint32_t Image::getHeight() const {
-		return m_height;
+		return m_manager->getImageHeight(m_handle);
 	}
 	
 	uint32_t Image::getDepth() const {
-		return m_depth;
-	}
-	
-	vk::ImageLayout Image::getLayout() const {
-		return m_layout;
+		return m_manager->getImageDepth(m_handle);
 	}
 
 	void Image::switchLayout(vk::ImageLayout newLayout)
 	{
-		m_manager->switchImageLayout(m_handle, m_layout, newLayout);
-		m_layout = newLayout;
+		m_manager->switchImageLayoutImmediate(m_handle, newLayout);
 	}
 
 	vkcv::ImageHandle Image::getHandle() const {
 		return m_handle;
 	}
-	
+
+	uint32_t Image::getMipCount() const {
+		return m_manager->getImageMipCount(m_handle);
+	}
+
 	void Image::fill(void *data, size_t size) {
 		m_manager->fillImage(m_handle, data, size);
 	}
+
+	void Image::generateMipChainImmediate() {
+		m_manager->generateImageMipChainImmediate(m_handle);
+	}
+
+	void Image::recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream) {
+		m_manager->recordImageMipChainGenerationToCmdStream(cmdStream, m_handle);
+	}
 	
-	Image::Image(ImageManager* manager, const ImageHandle& handle,
-			  	 vk::Format format, uint32_t width, uint32_t height, uint32_t depth) :
+	Image::Image(ImageManager* manager, const ImageHandle& handle) :
 		m_manager(manager),
-		m_handle(handle),
-		m_format(format),
-		m_width(width),
-		m_height(height),
-		m_depth(depth),
-		m_layout(vk::ImageLayout::eUndefined)
-	{
-	}
+		m_handle(handle)
+	{}
 
 }
diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp
index 0b08819489c41c5cde3ceddbb0629a5d2ae3cd30..8d31c64ccbcbf33e259714f8c581c920738190b4 100644
--- a/src/vkcv/ImageLayoutTransitions.cpp
+++ b/src/vkcv/ImageLayoutTransitions.cpp
@@ -1,24 +1,68 @@
 #include "ImageLayoutTransitions.hpp"
+#include "vkcv/Image.hpp"
 
 namespace vkcv {
-	void transitionImageLayoutImmediate(const vk::CommandBuffer cmdBuffer, const vk::Image image,
-		const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout) {
+	vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageManager::Image &image, vk::ImageLayout newLayout) {
 
-		// TODO: proper src and dst masks
-		const vk::PipelineStageFlags srcStageMask = vk::PipelineStageFlagBits::eAllCommands;
-		const vk::PipelineStageFlags dstStageMask = vk::PipelineStageFlagBits::eAllCommands;
-		const vk::DependencyFlags dependecyFlags = {};
+		vk::ImageAspectFlags aspectFlags;
+		if (isDepthFormat(image.m_format)) {
+			aspectFlags = vk::ImageAspectFlagBits::eDepth;
+		}
+		else {
+			aspectFlags = vk::ImageAspectFlagBits::eColor;
+		}
 
-		// TODO: proper src and dst masks
-		const vk::AccessFlags srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
-		const vk::AccessFlags dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
+		vk::ImageSubresourceRange imageSubresourceRange(
+			aspectFlags,
+			0,
+			image.m_viewPerMip.size(),
+			0,
+			image.m_layers
+		);
 
-		// TODO: proper aspect flags
-		const vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor;
+		// TODO: precise AccessFlagBits, will require a lot of context
+		return vk::ImageMemoryBarrier(
+			vk::AccessFlagBits::eMemoryWrite,
+			vk::AccessFlagBits::eMemoryRead,
+			image.m_layout,
+			newLayout,
+			VK_QUEUE_FAMILY_IGNORED,
+			VK_QUEUE_FAMILY_IGNORED,
+			image.m_handle,
+			imageSubresourceRange);
+	}
+
+	vk::ImageMemoryBarrier createSwapchainImageLayoutTransitionBarrier(
+		vk::Image       vulkanHandle, 
+		vk::ImageLayout oldLayout, 
+		vk::ImageLayout newLayout) {
 
-		const vk::ImageSubresourceRange subresourceRange(aspectFlags, 0, 1, 0, 1);
-		vk::ImageMemoryBarrier imageBarrier(srcAccessMask, dstAccessMask, oldLayout, newLayout, 0, 0, image, subresourceRange);
+		vk::ImageSubresourceRange imageSubresourceRange(
+			vk::ImageAspectFlagBits::eColor,
+			0,
+			1,
+			0,
+			1);
+
+		// TODO: precise AccessFlagBits, will require a lot of context
+		return vk::ImageMemoryBarrier(
+			vk::AccessFlagBits::eMemoryWrite,
+			vk::AccessFlagBits::eMemoryRead,
+			oldLayout,
+			newLayout,
+			VK_QUEUE_FAMILY_IGNORED,
+			VK_QUEUE_FAMILY_IGNORED,
+			vulkanHandle,
+			imageSubresourceRange);
+	}
 
-		cmdBuffer.pipelineBarrier(srcStageMask, dstStageMask, dependecyFlags, 0, nullptr, 0, nullptr, 1, &imageBarrier, {});
+	void recordImageBarrier(vk::CommandBuffer cmdBuffer, vk::ImageMemoryBarrier barrier) {
+		cmdBuffer.pipelineBarrier(
+			vk::PipelineStageFlagBits::eTopOfPipe,
+			vk::PipelineStageFlagBits::eBottomOfPipe,
+			{},
+			nullptr,
+			nullptr,
+			barrier);
 	}
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageLayoutTransitions.hpp b/src/vkcv/ImageLayoutTransitions.hpp
index 3dbfbdf6690a0683b30a96f400e7e4b6ec25c379..5c147f133a6492746ad410367e5e627be000d7be 100644
--- a/src/vkcv/ImageLayoutTransitions.hpp
+++ b/src/vkcv/ImageLayoutTransitions.hpp
@@ -1,7 +1,13 @@
 #pragma once
 #include <vulkan/vulkan.hpp>
+#include "ImageManager.hpp"
 
 namespace vkcv {
-	void transitionImageLayoutImmediate(const vk::CommandBuffer cmdBuffer, const vk::Image image,
-		const vk::ImageLayout oldLayout, const vk::ImageLayout newLayout);
+	vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageManager::Image& image, vk::ImageLayout newLayout);
+	vk::ImageMemoryBarrier createSwapchainImageLayoutTransitionBarrier(
+		vk::Image       vulkanHandle,
+		vk::ImageLayout oldLayout,
+		vk::ImageLayout newLayout);
+
+	void recordImageBarrier(vk::CommandBuffer cmdBuffer, vk::ImageMemoryBarrier barrier);
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index 3896d6bc4abdd24264ad5d468b49ebf08bd20be7..a3364ce0dfd6f59dc78c85b43570eea25cfc052d 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -5,11 +5,33 @@
  */
 #include "ImageManager.hpp"
 #include "vkcv/Core.hpp"
+#include "ImageLayoutTransitions.hpp"
+#include "vkcv/Logger.hpp"
 
 #include <algorithm>
 
 namespace vkcv {
 
+	ImageManager::Image::Image(
+		vk::Image                   handle,
+		vk::DeviceMemory            memory,
+		std::vector<vk::ImageView>  views,
+		uint32_t                    width,
+		uint32_t                    height,
+		uint32_t                    depth,
+		vk::Format                  format,
+		uint32_t                    layers)
+		:
+		m_handle(handle),
+		m_memory(memory),
+        m_viewPerMip(views),
+		m_width(width),
+		m_height(height),
+		m_depth(depth),
+		m_format(format),
+		m_layers(layers)
+	{}
+
 	/**
 	 * @brief searches memory type index for image allocation, combines requirements of image and application
 	 * @param physicalMemoryProperties Memory Properties of physical device
@@ -43,7 +65,12 @@ namespace vkcv {
 
 	ImageManager::~ImageManager() noexcept {
 		for (uint64_t id = 0; id < m_images.size(); id++) {
-			destroyImage(ImageHandle(id));
+			destroyImageById(id);
+		}
+		for (const auto swapchainImage : m_swapchainImages) {
+			for (const auto view : swapchainImage.m_viewPerMip) {
+				m_core->getContext().getDevice().destroy(view);
+			}
 		}
 	}
 	
@@ -57,7 +84,14 @@ namespace vkcv {
 		}
 	}
 
-	ImageHandle ImageManager::createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format)
+	ImageHandle ImageManager::createImage(
+		uint32_t    width, 
+		uint32_t    height, 
+		uint32_t    depth, 
+		vk::Format  format, 
+		uint32_t    mipCount,
+		bool        supportStorage, 
+		bool        supportColorAttachment)
 	{
 		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
 		
@@ -65,8 +99,14 @@ namespace vkcv {
 		
 		vk::ImageCreateFlags createFlags;
 		vk::ImageUsageFlags imageUsageFlags = (
-				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst
+				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc
 		);
+		if (supportStorage) {
+			imageUsageFlags |= vk::ImageUsageFlagBits::eStorage;
+		}
+		if (supportColorAttachment) {
+			imageUsageFlags |= vk::ImageUsageFlagBits::eColorAttachment;
+		}
 		
 		const bool isDepthFormat = isDepthImageFormat(format);
 		
@@ -89,6 +129,11 @@ namespace vkcv {
 			}
 		}
 		
+		if (isDepthFormat) {
+			imageType = vk::ImageType::e2D;
+			imageViewType = vk::ImageViewType::e2D;
+		}
+		
 		vk::ImageTiling imageTiling = vk::ImageTiling::eOptimal;
 		
 		if (!formatProperties.optimalTilingFeatures) {
@@ -98,11 +143,9 @@ namespace vkcv {
 			imageTiling = vk::ImageTiling::eLinear;
 		}
 		
-		const vk::ImageFormatProperties imageFormatProperties = physicalDevice.getImageFormatProperties(
-				format, imageType, imageTiling, imageUsageFlags
-		);
+		const vk::ImageFormatProperties imageFormatProperties = 
+			physicalDevice.getImageFormatProperties(format, imageType, imageTiling, imageUsageFlags);
 		
-		const uint32_t mipLevels = std::min<uint32_t>(1, imageFormatProperties.maxMipLevels);
 		const uint32_t arrayLayers = std::min<uint32_t>(1, imageFormatProperties.maxArrayLayers);
 		
 		const vk::ImageCreateInfo imageCreateInfo(
@@ -110,7 +153,7 @@ namespace vkcv {
 			imageType,
 			format,
 			vk::Extent3D(width, height, depth),
-			mipLevels,
+			mipCount,
 			arrayLayers,
 			vk::SampleCountFlagBits::e1,
 			imageTiling,
@@ -143,37 +186,49 @@ namespace vkcv {
 			aspectFlags = vk::ImageAspectFlagBits::eColor;
 		}
 		
-		const vk::ImageViewCreateInfo imageViewCreateInfo (
+		std::vector<vk::ImageView> views;
+		for (int mip = 0; mip < mipCount; mip++) {
+			const vk::ImageViewCreateInfo imageViewCreateInfo(
 				{},
 				image,
 				imageViewType,
 				format,
 				vk::ComponentMapping(
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity
 				),
 				vk::ImageSubresourceRange(
-						aspectFlags,
-						0,
-						mipLevels,
-						0,
-						arrayLayers
+					aspectFlags,
+					mip,
+					mipCount - mip,
+					0,
+					arrayLayers
 				)
-		);
-		
-		vk::ImageView view = device.createImageView(imageViewCreateInfo);
+			);
+
+			views.push_back(device.createImageView(imageViewCreateInfo));
+		}
 		
 		const uint64_t id = m_images.size();
-		m_images.push_back({ image, memory, view, width, height, depth, format, arrayLayers, mipLevels });
-		return ImageHandle(id);
+		m_images.push_back(Image(image, memory, views, width, height, depth, format, arrayLayers));
+		return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); });
+	}
+	
+	ImageHandle ImageManager::createSwapchainImage() {
+		return ImageHandle::createSwapchainImageHandle();
 	}
 	
 	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
+
+		if (handle.isSwapchainImage()) {
+			m_swapchainImages[m_currentSwapchainInputImage].m_handle;
+		}
+
 		const uint64_t id = handle.getId();
-		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
@@ -183,9 +238,15 @@ namespace vkcv {
 	}
 	
 	vk::DeviceMemory ImageManager::getVulkanDeviceMemory(const ImageHandle &handle) const {
+
+		if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::ERROR, "Swapchain image has no memory");
+			return nullptr;
+		}
+
 		const uint64_t id = handle.getId();
-		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
@@ -194,115 +255,130 @@ namespace vkcv {
 		return image.m_memory;
 	}
 	
-	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
+	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, const size_t mipLevel) const {
 		
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages[m_currentSwapchainInputImage].m_viewPerMip[0];
+		}
+
+		const uint64_t id = handle.getId();
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
-		auto& image = m_images[id];
-		
-		return image.m_view;
+		const auto& image = m_images[id];
+
+		if (mipLevel >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Image does not have requested mipLevel");
+			return nullptr;
+		}
+
+		return image.m_viewPerMip[mipLevel];
 	}
 	
-	void ImageManager::switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) {
-		const uint64_t id = handle.getId();
+	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
+		uint64_t id = handle.getId();
 		
-		if (id >= m_images.size()) {
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 		
-		auto& image = m_images[id];
-		
-		//alternativly we could use switch case for every variable to set
-		vk::AccessFlags sourceAccessMask;
-		vk::PipelineStageFlags sourceStage;
-		
-		vk::AccessFlags destinationAccessMask;
-		vk::PipelineStageFlags destinationStage;
-		
-		if ((oldLayout == vk::ImageLayout::eUndefined) &&
-			(newLayout == vk::ImageLayout::eTransferDstOptimal))
-		{
-			destinationAccessMask = vk::AccessFlagBits::eTransferWrite;
-			
-			sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
-			destinationStage = vk::PipelineStageFlagBits::eTransfer;
-		}
-		else if ((oldLayout == vk::ImageLayout::eTransferDstOptimal) &&
-				 (newLayout == vk::ImageLayout::eShaderReadOnlyOptimal))
-		{
-			sourceAccessMask = vk::AccessFlagBits::eTransferWrite;
-			destinationAccessMask = vk::AccessFlagBits::eShaderRead;
-			
-			sourceStage = vk::PipelineStageFlagBits::eTransfer;
-			destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
-		}
-		
-		vk::ImageAspectFlags aspectFlags;
-		
-		if (isDepthImageFormat(image.m_format)) {
-			aspectFlags = vk::ImageAspectFlagBits::eDepth;
-		} else {
-			aspectFlags = vk::ImageAspectFlagBits::eColor;
-		}
-		
-		vk::ImageSubresourceRange imageSubresourceRange(
-				aspectFlags,
-				0,
-				image.m_levels,
-				0,
-				image.m_layers
-		);
-		
-		vk::ImageMemoryBarrier imageMemoryBarrier(
-			sourceAccessMask,
-			destinationAccessMask,
-			oldLayout,
-			newLayout,
-			VK_QUEUE_FAMILY_IGNORED,
-			VK_QUEUE_FAMILY_IGNORED,
-			image.m_handle,
-			imageSubresourceRange
-		);
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
 		
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		
-		m_core->submitCommands(
+		m_core->recordAndSubmitCommandsImmediate(
 			submitInfo,
-			[sourceStage, destinationStage, imageMemoryBarrier](const vk::CommandBuffer& commandBuffer) {
-				commandBuffer.pipelineBarrier(
-					sourceStage,
-					destinationStage,
-					{},
-					nullptr,
-					nullptr,
-					imageMemoryBarrier
+			[transitionBarrier](const vk::CommandBuffer& commandBuffer) {
+			// TODO: precise PipelineStageFlagBits, will require a lot of context
+			commandBuffer.pipelineBarrier(
+				vk::PipelineStageFlagBits::eTopOfPipe,
+				vk::PipelineStageFlagBits::eBottomOfPipe,
+				{},
+				nullptr,
+				nullptr,
+				transitionBarrier
 				);
 			},
-			nullptr
-		);
+			nullptr);
+		image.m_layout = newLayout;
+	}
+
+	void ImageManager::recordImageLayoutTransition(
+		const ImageHandle& handle, 
+		vk::ImageLayout newLayout, 
+		vk::CommandBuffer cmdBuffer) {
+
+		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return;
+		}
+
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
+		recordImageBarrier(cmdBuffer, transitionBarrier);
+		image.m_layout = newLayout;
+	}
+
+	void ImageManager::recordImageMemoryBarrier(
+		const ImageHandle& handle,
+		vk::CommandBuffer cmdBuffer) {
+
+		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			std::cerr << "Error: ImageManager::recordImageMemoryBarrier invalid handle" << std::endl;
+			return;
+		}
+
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, image.m_layout);
+		recordImageBarrier(cmdBuffer, transitionBarrier);
+	}
+	
+	constexpr uint32_t getChannelsByFormat(vk::Format format) {
+		switch (format) {
+			case vk::Format::eR8Unorm:
+				return 1;
+			case vk::Format::eR8G8B8A8Srgb:
+				return 4;
+			default:
+				std::cerr << "Check format instead of guessing, please!" << std::endl;
+				return 4;
+		}
 	}
 	
 	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
 	{
 		const uint64_t id = handle.getId();
 		
+		if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
+			return;
+		}
+
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 		
 		auto& image = m_images[id];
 		
-		switchImageLayout(
+		switchImageLayoutImmediate(
 				handle,
-				vk::ImageLayout::eUndefined,
-				vk::ImageLayout::eTransferDstOptimal
-		);
+				vk::ImageLayout::eTransferDstOptimal);
 		
-		uint32_t channels = 4; // TODO: check image.m_format
+		uint32_t channels = getChannelsByFormat(image.m_format);
 		const size_t image_size = (
 				image.m_width * image.m_height * image.m_depth * channels
 		);
@@ -320,7 +396,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		m_core->submitCommands(
+		m_core->recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
 					vk::ImageAspectFlags aspectFlags;
@@ -354,22 +430,143 @@ namespace vkcv {
 					);
 				},
 				[&]() {
-					switchImageLayout(
+					switchImageLayoutImmediate(
 							handle,
-							vk::ImageLayout::eTransferDstOptimal,
 							vk::ImageLayout::eShaderReadOnlyOptimal
 					);
-					
-					m_bufferManager.destroyBuffer(bufferHandle);
 				}
 		);
 	}
 
-	void ImageManager::destroyImage(const ImageHandle& handle)
-	{
+	void ImageManager::recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle) {
+
+		const auto id = handle.getId();
+		if (id >= m_images.size()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "Invalid image handle");
+			return;
+		}
+
+		auto& image = m_images[id];
+		recordImageLayoutTransition(handle, vk::ImageLayout::eGeneral, cmdBuffer);
+
+		vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
+			vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor;
+
+		uint32_t srcWidth = image.m_width;
+		uint32_t srcHeight = image.m_height;
+		uint32_t srcDepth = image.m_depth;
+
+		auto half = [](uint32_t in) {
+			return std::max<uint32_t>(in / 2, 1);
+		};
+
+		uint32_t dstWidth = half(srcWidth);
+		uint32_t dstHeight = half(srcHeight);
+		uint32_t dstDepth = half(srcDepth);
+
+		for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
+			uint32_t dstMip = srcMip + 1;
+			vk::ImageBlit region(
+				vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
+				{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
+				vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
+				{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
+
+			cmdBuffer.blitImage(
+				image.m_handle,
+				vk::ImageLayout::eGeneral,
+				image.m_handle,
+				vk::ImageLayout::eGeneral,
+				region,
+				vk::Filter::eLinear);
+
+			srcWidth = dstWidth;
+			srcHeight = dstHeight;
+			srcDepth = dstDepth;
+
+			dstWidth = half(dstWidth);
+			dstHeight = half(dstHeight);
+			dstDepth = half(dstDepth);
+
+			recordImageMemoryBarrier(handle, cmdBuffer);
+		}
+	}
+
+	void ImageManager::generateImageMipChainImmediate(const ImageHandle& handle) {
+
+		const auto& device = m_core->getContext().getDevice();
+
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Graphics;
+
+		if (handle.isSwapchainImage()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "You cannot generate a mip chain for the swapchain, what are you smoking?");
+			return;
+		}
+
+		const auto record = [this, handle](const vk::CommandBuffer cmdBuffer) {
+			recordImageMipGenerationToCmdBuffer(cmdBuffer, handle);
+		};
+
+		m_core->recordAndSubmitCommandsImmediate(submitInfo, record, nullptr);
+	}
+
+	void ImageManager::recordImageMipChainGenerationToCmdStream(
+		const vkcv::CommandStreamHandle& cmdStream,
+		const ImageHandle& handle) {
+
+		const auto record = [this, handle](const vk::CommandBuffer cmdBuffer) {
+			recordImageMipGenerationToCmdBuffer(cmdBuffer, handle);
+		};
+		m_core->recordCommandsToStream(cmdStream, record, nullptr);
+	}
+
+	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
+		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return 0;
+		}
+		
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		
+		return image.m_width;
+	}
+	
+	uint32_t ImageManager::getImageHeight(const ImageHandle &handle) const {
+		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+		
+		if (id >= m_images.size() && !isSwapchainImage) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return 0;
+		}
+		
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		
+		return image.m_height;
+	}
+	
+	uint32_t ImageManager::getImageDepth(const ImageHandle &handle) const {
 		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return 0;
+		}
+		
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		
+		return image.m_depth;
+	}
+	
+	void ImageManager::destroyImageById(uint64_t id)
+	{
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 		
@@ -377,9 +574,11 @@ namespace vkcv {
 
 		const vk::Device& device = m_core->getContext().getDevice();
 		
-		if (image.m_view) {
-			device.destroyImageView(image.m_view);
-			image.m_view = nullptr;
+		for (auto& view : image.m_viewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
 		}
 
 		if (image.m_memory) {
@@ -393,5 +592,54 @@ namespace vkcv {
 		}
 	}
 
+	vk::Format ImageManager::getImageFormat(const ImageHandle& handle) const {
+
+		const uint64_t id = handle.getId();
+		const bool isSwapchainFormat = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainFormat) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return vk::Format::eUndefined;
+		}
+
+		return isSwapchainFormat ? m_swapchainImages[m_currentSwapchainInputImage].m_format : m_images[id].m_format;
+	}
+
+	uint32_t ImageManager::getImageMipCount(const ImageHandle& handle) const {
+		const uint64_t id = handle.getId();
+		const bool isSwapchainFormat = handle.isSwapchainImage();
+
+		if (handle.isSwapchainImage()) {
+			return 1;
+		}
+
+		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return 0;
+		}
+
+		return m_images[id].m_viewPerMip.size();
+	}
+
+	void ImageManager::setCurrentSwapchainImageIndex(int index) {
+		m_currentSwapchainInputImage = index;
+	}
+
+	void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views, 
+		uint32_t width, uint32_t height, vk::Format format) {
+
+		// destroy old views
+		for (auto image : m_swapchainImages) {
+			for (const auto& view : image.m_viewPerMip) {
+				m_core->getContext().getDevice().destroyImageView(view);
+			}
+		}
+
+		assert(images.size() == views.size());
+		m_swapchainImages.clear();
+		for (int i = 0; i < images.size(); i++) {
+			m_swapchainImages.push_back(Image(images[i], nullptr, { views[i] }, width, height, 1, format, 1));
+		}
+	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index 7dc6746f37b7d03900302afbd0536b909f9e48fc..ecba7eb5959c1d78a0be41e0b3ac555bffd92d95 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -11,31 +11,58 @@
 #include "vkcv/Handles.hpp"
 
 namespace vkcv {
-	
+
 	class ImageManager
 	{
 		friend class Core;
-	private:
+	public:
 		struct Image
 		{
-			vk::Image m_handle;
-			vk::DeviceMemory m_memory;
-			vk::ImageView m_view;
-			uint32_t m_width = 0;
-			uint32_t m_height = 0;
-			uint32_t m_depth = 0;
-			vk::Format m_format;
-			uint32_t m_layers = 1;
-			uint32_t m_levels = 1;
+			vk::Image                   m_handle;
+			vk::DeviceMemory            m_memory;
+			std::vector<vk::ImageView>  m_viewPerMip;
+			uint32_t                    m_width     = 0;
+			uint32_t                    m_height    = 0;
+			uint32_t                    m_depth     = 0;
+			vk::Format                  m_format;
+			uint32_t                    m_layers    = 1;
+			vk::ImageLayout             m_layout    = vk::ImageLayout::eUndefined;
+		private:
+			// struct is public so utility functions can access members, but only ImageManager can create Image
+			friend ImageManager;
+			Image(
+				vk::Image                   handle,
+				vk::DeviceMemory            memory,
+				std::vector<vk::ImageView>  views,
+				uint32_t                    width,
+				uint32_t                    height,
+				uint32_t                    depth,
+				vk::Format                  format,
+				uint32_t                    layers);
+
+			Image();
 		};
+	private:
 		
 		Core* m_core;
 		BufferManager& m_bufferManager;
 		
 		std::vector<Image> m_images;
+		std::vector<Image> m_swapchainImages;
+		int m_currentSwapchainInputImage;
 		
 		ImageManager(BufferManager& bufferManager) noexcept;
 		
+		/**
+		 * Destroys and deallocates image represented by a given
+		 * image handle id.
+		 *
+		 * @param id Image handle id
+		 */
+		void destroyImageById(uint64_t id);
+
+		void recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle);
+
 	public:
 		~ImageManager() noexcept;
 		ImageManager(ImageManager&& other) = delete;
@@ -44,7 +71,16 @@ namespace vkcv {
 		ImageManager& operator=(ImageManager&& other) = delete;
 		ImageManager& operator=(const ImageManager& other) = delete;
 		
-		ImageHandle createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format);
+		ImageHandle createImage(
+			uint32_t    width, 
+			uint32_t    height, 
+			uint32_t    depth, 
+			vk::Format  format, 
+			uint32_t    mipCount,
+			bool        supportStorage, 
+			bool        supportColorAttachment);
+		
+		ImageHandle createSwapchainImage();
 		
 		[[nodiscard]]
 		vk::Image getVulkanImage(const ImageHandle& handle) const;
@@ -53,18 +89,40 @@ namespace vkcv {
 		vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle& handle) const;
 		
 		[[nodiscard]]
-		vk::ImageView getVulkanImageView(const ImageHandle& handle) const;
-		
-		void switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout);
-		void fillImage(const ImageHandle& handle, void* data, size_t size);
+		vk::ImageView getVulkanImageView(const ImageHandle& handle, const size_t mipLevel = 0) const;
 
-		/**
-		 * Destroys and deallocates image represented by a given
-		 * buffer handle.
-		 *
-		 * @param handle Image handle
-		 */
-		void destroyImage(const ImageHandle& handle);
+		void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout);
+		void recordImageLayoutTransition(
+			const ImageHandle& handle, 
+			vk::ImageLayout newLayout, 
+			vk::CommandBuffer cmdBuffer);
+
+		void recordImageMemoryBarrier(
+			const ImageHandle& handle,
+			vk::CommandBuffer cmdBuffer);
+
+		void fillImage(const ImageHandle& handle, void* data, size_t size);
+		void generateImageMipChainImmediate(const ImageHandle& handle);
+		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream, const ImageHandle& handle);
+		
+		[[nodiscard]]
+		uint32_t getImageWidth(const ImageHandle& handle) const;
+		
+		[[nodiscard]]
+		uint32_t getImageHeight(const ImageHandle& handle) const;
+		
+		[[nodiscard]]
+		uint32_t getImageDepth(const ImageHandle& handle) const;
 		
+		[[nodiscard]]
+		vk::Format getImageFormat(const ImageHandle& handle) const;
+
+		[[nodiscard]]
+		uint32_t getImageMipCount(const ImageHandle& handle) const;
+
+		void setCurrentSwapchainImageIndex(int index);
+		void setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views,
+			uint32_t width, uint32_t height, vk::Format format);
+
 	};
 }
\ No newline at end of file
diff --git a/src/vkcv/PassConfig.cpp b/src/vkcv/PassConfig.cpp
index ef07d3ee8d6170ae893cd055eefcc971cd1b87a3..602f1d3e2a8100ebd9bbb83772312d3d659abe86 100644
--- a/src/vkcv/PassConfig.cpp
+++ b/src/vkcv/PassConfig.cpp
@@ -5,15 +5,9 @@
 namespace vkcv
 {
     AttachmentDescription::AttachmentDescription(
-		AttachmentLayout initial,
-		AttachmentLayout in_pass,
-		AttachmentLayout final,
 		AttachmentOperation store_op,
 		AttachmentOperation load_op,
 		vk::Format format) noexcept :
-	layout_initial{initial},
-	layout_in_pass{in_pass},
-	layout_final{final},
 	store_operation{store_op},
 	load_operation{load_op},
 	format(format)
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index 26e5f290d04ebaf16940cd99386253b5ab3622cc..c34b0d3631c48561f42eb7f21ba5578156910f51 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -1,4 +1,5 @@
 #include "PassManager.hpp"
+#include "vkcv/Image.hpp"
 
 namespace vkcv
 {
@@ -49,17 +50,14 @@ namespace vkcv
 
     PassManager::PassManager(vk::Device device) noexcept :
     m_Device{device},
-    m_Passes{},
-    m_NextPassId(0)
+    m_Passes{}
     {}
 
     PassManager::~PassManager() noexcept
     {
-        for(const auto &pass : m_Passes)
-            m_Device.destroy(pass.m_Handle);
-	
-		m_Passes.clear();
-        m_NextPassId = 0;
+    	for (uint64_t id = 0; id < m_Passes.size(); id++) {
+			destroyPassById(id);
+    	}
     }
 
     PassHandle PassManager::createPass(const PassConfig &config)
@@ -76,62 +74,63 @@ namespace vkcv
         for (uint32_t i = 0; i < config.attachments.size(); i++)
         {
             // TODO: Renderpass struct should hold proper format information
-            vk::Format format = config.attachments[i].format;
+            vk::Format      format = config.attachments[i].format;
+            vk::ImageLayout layout;
 
-            if (config.attachments[i].layout_in_pass == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT)
+            if (isDepthFormat(config.attachments[i].format))
             {
+                layout                              = vk::ImageLayout::eDepthStencilAttachmentOptimal;
                 depthAttachmentReference.attachment = i;
-                depthAttachmentReference.layout = getVkLayoutFromAttachLayout(config.attachments[i].layout_in_pass);
-                pDepthAttachment = &depthAttachmentReference;
+                depthAttachmentReference.layout     = layout;
+                pDepthAttachment                    = &depthAttachmentReference;
             }
             else
             {
-                vk::AttachmentReference attachmentRef(i, getVkLayoutFromAttachLayout(config.attachments[i].layout_in_pass));
+                layout = vk::ImageLayout::eColorAttachmentOptimal;
+                vk::AttachmentReference attachmentRef(i, layout);
                 colorAttachmentReferences.push_back(attachmentRef);
             }
 
             vk::AttachmentDescription attachmentDesc(
-            		{},
-            		format,
-            		vk::SampleCountFlagBits::e1,
-            		getVKLoadOpFromAttachOp(config.attachments[i].load_operation),
-            		getVkStoreOpFromAttachOp(config.attachments[i].store_operation),
-            		vk::AttachmentLoadOp::eDontCare,
-            		vk::AttachmentStoreOp::eDontCare,
-            		getVkLayoutFromAttachLayout(config.attachments[i].layout_initial),
-            		getVkLayoutFromAttachLayout(config.attachments[i].layout_final)
-			);
-            
+                {},
+                format,
+                vk::SampleCountFlagBits::e1,
+                getVKLoadOpFromAttachOp(config.attachments[i].load_operation),
+                getVkStoreOpFromAttachOp(config.attachments[i].store_operation),
+                vk::AttachmentLoadOp::eDontCare,
+                vk::AttachmentStoreOp::eDontCare,
+                layout,
+                layout);
+
             attachmentDescriptions.push_back(attachmentDesc);
         }
         
         const vk::SubpassDescription subpassDescription(
-        		{},
-        		vk::PipelineBindPoint::eGraphics,
-        		0,
-        		{},
-        		static_cast<uint32_t>(colorAttachmentReferences.size()),
-        		colorAttachmentReferences.data(),
-        		{},
-        		pDepthAttachment,
-        		0,
-        		{}
-        );
+            {},
+            vk::PipelineBindPoint::eGraphics,
+            0,
+            {},
+            static_cast<uint32_t>(colorAttachmentReferences.size()),
+            colorAttachmentReferences.data(),
+            {},
+            pDepthAttachment,
+            0,
+            {});
 
         const vk::RenderPassCreateInfo passInfo(
-        		{},
-        		static_cast<uint32_t>(attachmentDescriptions.size()),
-        		attachmentDescriptions.data(),
-        		1,
-        		&subpassDescription,
-        		0,
-        		{}
-	  	);
+            {},
+            static_cast<uint32_t>(attachmentDescriptions.size()),
+            attachmentDescriptions.data(),
+            1,
+            &subpassDescription,
+            0,
+            {});
 
         vk::RenderPass renderPass = m_Device.createRenderPass(passInfo);
-	
-		m_Passes.push_back({ renderPass, config });
-		return PassHandle(m_NextPassId++);
+
+        const uint64_t id = m_Passes.size();
+        m_Passes.push_back({ renderPass, config });
+        return PassHandle(id, [&](uint64_t id) { destroyPassById(id); });
     }
 
     vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
@@ -160,4 +159,17 @@ namespace vkcv
 		return pass.m_Config;
     }
     
+    void PassManager::destroyPassById(uint64_t id) {
+    	if (id >= m_Passes.size()) {
+    		return;
+    	}
+    	
+    	auto& pass = m_Passes[id];
+	
+		if (pass.m_Handle) {
+			m_Device.destroy(pass.m_Handle);
+			pass.m_Handle = nullptr;
+		}
+    }
+    
 }
diff --git a/src/vkcv/PassManager.hpp b/src/vkcv/PassManager.hpp
index bfc20fe25ace95bd8d94832b953b6b14ab9cadee..661a8b277ecb446c4bbaeeb63560ffde28c31d99 100644
--- a/src/vkcv/PassManager.hpp
+++ b/src/vkcv/PassManager.hpp
@@ -17,7 +17,9 @@ namespace vkcv
     	
         vk::Device m_Device;
         std::vector<Pass> m_Passes;
-        uint64_t m_NextPassId;
+        
+        void destroyPassById(uint64_t id);
+        
     public:
         PassManager() = delete; // no default ctor
         explicit PassManager(vk::Device device) noexcept; // ctor
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
deleted file mode 100644
index d317258470bde76e8b8ba8e1f9bc684ea469b6c0..0000000000000000000000000000000000000000
--- a/src/vkcv/PipelineConfig.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @authors Mara Vogt, Mark Mints
- * @file src/vkcv/Pipeline.cpp
- * @brief Pipeline class to handle shader stages
- */
-
-#include "vkcv/PipelineConfig.hpp"
-
-namespace vkcv {
-
-    PipelineConfig::PipelineConfig(
-		const ShaderProgram&						shaderProgram, 
-		uint32_t									width, 
-		uint32_t									height, 
-		PassHandle									&passHandle, 
-		const std::vector<VertexAttribute>			&vertexAttributes,
-		const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts)
-		:
-		m_ShaderProgram(shaderProgram),
-		m_Height(height),
-		m_Width(width),
-		m_PassHandle(passHandle),
-		m_vertexAttributes(vertexAttributes),
-		m_descriptorLayouts(descriptorLayouts)
-		{}
-}
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 1b0d665e9011b9c652a69e35c295a701a6029dd2..df36442efc2992bf16b6e82245ef9753dad95e5d 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -1,43 +1,56 @@
 #include "PipelineManager.hpp"
+#include "vkcv/Image.hpp"
+#include "vkcv/Logger.hpp"
 
 namespace vkcv
 {
 
     PipelineManager::PipelineManager(vk::Device device) noexcept :
     m_Device{device},
-    m_Pipelines{},
-    m_PipelineLayouts{},
-    m_NextPipelineId{0}
+    m_Pipelines{}
     {}
 
     PipelineManager::~PipelineManager() noexcept
     {
-        for(const auto &pipeline: m_Pipelines)
-            m_Device.destroy(pipeline);
-
-        for(const auto &layout : m_PipelineLayouts)
-            m_Device.destroy(layout);
-
-        m_Pipelines.clear();
-        m_PipelineLayouts.clear();
-        m_NextPipelineId = 0;
+    	for (uint64_t id = 0; id < m_Pipelines.size(); id++) {
+			destroyPipelineById(id);
+    	}
     }
 
 	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
-	vk::Format vertexFormatToVulkanFormat(const VertexFormat format) {
+	vk::Format vertexFormatToVulkanFormat(const VertexAttachmentFormat format) {
 		switch (format) {
-		case VertexFormat::FLOAT: return vk::Format::eR32Sfloat;
-		case VertexFormat::FLOAT2: return vk::Format::eR32G32Sfloat;
-		case VertexFormat::FLOAT3: return vk::Format::eR32G32B32Sfloat;
-		case VertexFormat::FLOAT4: return vk::Format::eR32G32B32A32Sfloat;
-		case VertexFormat::INT: return vk::Format::eR32Sint;
-		case VertexFormat::INT2: return vk::Format::eR32G32Sint;
-		case VertexFormat::INT3: return vk::Format::eR32G32B32Sint;
-		case VertexFormat::INT4: return vk::Format::eR32G32B32A32Sint;
-		default: std::cerr << "Warning: Unknown vertex format" << std::endl; return vk::Format::eUndefined;
+		case VertexAttachmentFormat::FLOAT:
+			return vk::Format::eR32Sfloat;
+		case VertexAttachmentFormat::FLOAT2:
+			return vk::Format::eR32G32Sfloat;
+		case VertexAttachmentFormat::FLOAT3:
+			return vk::Format::eR32G32B32Sfloat;
+		case VertexAttachmentFormat::FLOAT4:
+			return vk::Format::eR32G32B32A32Sfloat;
+		case VertexAttachmentFormat::INT:
+			return vk::Format::eR32Sint;
+		case VertexAttachmentFormat::INT2:
+			return vk::Format::eR32G32Sint;
+		case VertexAttachmentFormat::INT3:
+			return vk::Format::eR32G32B32Sint;
+		case VertexAttachmentFormat::INT4:
+			return vk::Format::eR32G32B32A32Sint;
+		default:
+			vkcv_log(LogLevel::WARNING, "Unknown vertex format");
+			return vk::Format::eUndefined;
 		}
 	}
 
+    vk::PrimitiveTopology primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) {
+        switch (topology) {
+        case(PrimitiveTopology::PointList):     return vk::PrimitiveTopology::ePointList;
+        case(PrimitiveTopology::LineList):      return vk::PrimitiveTopology::eLineList;
+        case(PrimitiveTopology::TriangleList):  return vk::PrimitiveTopology::eTriangleList;
+        default: std::cout << "Error: Unknown primitive topology type" << std::endl; return vk::PrimitiveTopology::eTriangleList;
+        }
+    }
+
     PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, PassManager& passManager)
     {
 		const vk::RenderPass &pass = passManager.getVkPass(config.m_PassHandle);
@@ -46,7 +59,7 @@ namespace vkcv
         const bool existsFragmentShader = config.m_ShaderProgram.existsShader(ShaderStage::FRAGMENT);
         if (!(existsVertexShader && existsFragmentShader))
         {
-            std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl;
+			vkcv_log(LogLevel::ERROR, "Requires vertex and fragment shader code");
             return PipelineHandle();
         }
 
@@ -89,24 +102,24 @@ namespace vkcv
         std::vector<vk::VertexInputAttributeDescription>	vertexAttributeDescriptions;
 		std::vector<vk::VertexInputBindingDescription>		vertexBindingDescriptions;
 
-        VertexLayout layout = config.m_ShaderProgram.getVertexLayout();
-        std::unordered_map<uint32_t, VertexInputAttachment> attachments = layout.attachmentMap;
+        const VertexLayout &layout = config.m_VertexLayout;
 
-		for (int i = 0; i < attachments.size(); i++) {
-			VertexInputAttachment &attachment = attachments.at(i);
-
-            uint32_t	location		= attachment.location;
-            uint32_t	binding			= i;
-            vk::Format	vertexFormat	= vertexFormatToVulkanFormat(attachment.format);
+        // iterate over the layout's specified, mutually exclusive buffer bindings that make up a vertex buffer
+        for (const auto &vertexBinding : layout.vertexBindings)
+        {
+            vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation,
+                                                   vertexBinding.stride,
+                                                   vk::VertexInputRate::eVertex);
 
-			//FIXME: hoping that order is the same and compatible: add explicit mapping and validation
-			const VertexAttribute attribute = config.m_vertexAttributes[i];
+            // iterate over the bindings' specified, mutually exclusive vertex input attachments that make up a vertex
+            for(const auto &vertexAttachment: vertexBinding.vertexAttachments)
+            {
+                vertexAttributeDescriptions.emplace_back(vertexAttachment.inputLocation,
+                                                         vertexBinding.bindingLocation,
+                                                         vertexFormatToVulkanFormat(vertexAttachment.format),
+                                                         vertexAttachment.offset % vertexBinding.stride);
 
-            vertexAttributeDescriptions.push_back({location, binding, vertexFormatToVulkanFormat(attachment.format), 0});
-			vertexBindingDescriptions.push_back(vk::VertexInputBindingDescription(
-				binding,
-				attribute.stride + getFormatSize(attachment.format),
-				vk::VertexInputRate::eVertex));
+            }
         }
 
         // Handover Containers to PipelineVertexInputStateCreateIngo Struct
@@ -120,9 +133,9 @@ namespace vkcv
 
         // input assembly state
         vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
-                {},
-                vk::PrimitiveTopology::eTriangleList,
-                false
+            {},
+            primitiveTopologyToVulkanPrimitiveTopology(config.m_PrimitiveTopology),
+            false
         );
 
         // viewport state
@@ -144,6 +157,14 @@ namespace vkcv
                 0.f,
                 1.f
         );
+        vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization;
+        if (config.m_UseConservativeRasterization) {
+            conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT(
+                {}, 
+                vk::ConservativeRasterizationModeEXT::eOverestimate,
+                0.f);
+            pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization;
+        }
 
         // multisample state
         vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
@@ -180,14 +201,15 @@ namespace vkcv
                 { 1.f,1.f,1.f,1.f }
         );
 
-		const size_t matrixPushConstantSize = 4 * 4 * sizeof(float);
+		const size_t matrixPushConstantSize = config.m_ShaderProgram.getPushConstantSize();
 		const vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eAll, 0, matrixPushConstantSize);
 
         // pipeline layout
         vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
 			{},
-			(config.m_descriptorLayouts),
+			(config.m_DescriptorLayouts),
 			(pushConstantRange));
+
         vk::PipelineLayout vkPipelineLayout{};
         if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
         {
@@ -214,14 +236,39 @@ namespace vkcv
 		const PassConfig& passConfig = passManager.getPassConfig(config.m_PassHandle);
 		
 		for (const auto& attachment : passConfig.attachments) {
-			if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
+			if (isDepthFormat(attachment.format)) {
 				p_depthStencilCreateInfo = &depthStencilCreateInfo;
 				break;
 			}
 		}
-	
-		// graphics pipeline create
+
+		std::vector<vk::DynamicState> dynamicStates = {};
+		if(config.m_UseDynamicViewport)
+        {
+		    dynamicStates.push_back(vk::DynamicState::eViewport);
+		    dynamicStates.push_back(vk::DynamicState::eScissor);
+        }
+
+        vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo({},
+                                                            static_cast<uint32_t>(dynamicStates.size()),
+                                                            dynamicStates.data());
+
+        // graphics pipeline create
         std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
+
+		const char *geometryShaderName = "main";	// outside of if to make sure it stays in scope
+		vk::ShaderModule geometryModule;
+		if (config.m_ShaderProgram.existsShader(ShaderStage::GEOMETRY)) {
+			const vkcv::Shader geometryShader = config.m_ShaderProgram.getShader(ShaderStage::GEOMETRY);
+			const auto& geometryCode = geometryShader.shaderCode;
+			const vk::ShaderModuleCreateInfo geometryModuleInfo({}, geometryCode.size(), reinterpret_cast<const uint32_t*>(geometryCode.data()));
+			if (m_Device.createShaderModule(&geometryModuleInfo, nullptr, &geometryModule) != vk::Result::eSuccess) {
+				return PipelineHandle();
+			}
+			vk::PipelineShaderStageCreateInfo geometryStage({}, vk::ShaderStageFlagBits::eGeometry, geometryModule, geometryShaderName);
+			shaderStages.push_back(geometryStage);
+		}
+
         const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
                 {},
                 static_cast<uint32_t>(shaderStages.size()),
@@ -234,7 +281,7 @@ namespace vkcv
                 &pipelineMultisampleStateCreateInfo,
 				p_depthStencilCreateInfo,
                 &pipelineColorBlendStateCreateInfo,
-                nullptr,
+                &dynamicStateCreateInfo,
                 vkPipelineLayout,
                 pass,
                 0,
@@ -247,24 +294,140 @@ namespace vkcv
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
+            if (geometryModule) {
+                m_Device.destroy(geometryModule);
+            }
+            m_Device.destroy();
             return PipelineHandle();
         }
 
         m_Device.destroy(vertexModule);
         m_Device.destroy(fragmentModule);
-
-        m_Pipelines.push_back(vkPipeline);
-        m_PipelineLayouts.push_back(vkPipelineLayout);
-        return PipelineHandle(m_NextPipelineId++);
+        if (geometryModule) {
+            m_Device.destroy(geometryModule);
+        }
+        
+        const uint64_t id = m_Pipelines.size();
+        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config });
+        return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
     }
 
     vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const
     {
-        return m_Pipelines.at(handle.getId());
+		const uint64_t id = handle.getId();
+	
+		if (id >= m_Pipelines.size()) {
+			return nullptr;
+		}
+	
+		auto& pipeline = m_Pipelines[id];
+	
+		return pipeline.m_handle;
     }
 
     vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const
     {
-        return m_PipelineLayouts.at(handle.getId());
+    	const uint64_t id = handle.getId();
+    	
+		if (id >= m_Pipelines.size()) {
+			return nullptr;
+		}
+	
+		auto& pipeline = m_Pipelines[id];
+    	
+        return pipeline.m_layout;
+    }
+    
+    void PipelineManager::destroyPipelineById(uint64_t id) {
+    	if (id >= m_Pipelines.size()) {
+    		return;
+    	}
+    	
+    	auto& pipeline = m_Pipelines[id];
+    	
+    	if (pipeline.m_handle) {
+			m_Device.destroy(pipeline.m_handle);
+			pipeline.m_handle = nullptr;
+    	}
+	
+		if (pipeline.m_layout) {
+			m_Device.destroy(pipeline.m_layout);
+			pipeline.m_layout = nullptr;
+		}
+    }
+
+    const PipelineConfig& PipelineManager::getPipelineConfig(const PipelineHandle &handle) const
+    {
+        const uint64_t id = handle.getId();
+        
+        if (id >= m_Pipelines.size()) {
+        	static PipelineConfig dummyConfig;
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return dummyConfig;
+        }
+        
+        return m_Pipelines[id].m_config;
+    }
+
+    PipelineHandle PipelineManager::createComputePipeline(
+        const ShaderProgram &shaderProgram, 
+        const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts) {
+
+        // Temporally handing over the Shader Program instead of a pipeline config
+        vk::ShaderModule computeModule{};
+        if (createShaderModule(computeModule, shaderProgram, ShaderStage::COMPUTE) != vk::Result::eSuccess)
+            return PipelineHandle();
+
+        vk::PipelineShaderStageCreateInfo pipelineComputeShaderStageInfo(
+                {},
+                vk::ShaderStageFlagBits::eCompute,
+                computeModule,
+                "main",
+                nullptr
+        );
+
+        vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, descriptorSetLayouts);
+
+        const size_t pushConstantSize = shaderProgram.getPushConstantSize();
+        vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eCompute, 0, pushConstantSize);
+        if (pushConstantSize > 0) {
+            pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
+            pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRange);
+        }
+
+        vk::PipelineLayout vkPipelineLayout{};
+        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
+        {
+            m_Device.destroy(computeModule);
+            return PipelineHandle();
+        }
+
+        vk::ComputePipelineCreateInfo computePipelineCreateInfo{};
+        computePipelineCreateInfo.stage = pipelineComputeShaderStageInfo;
+        computePipelineCreateInfo.layout = vkPipelineLayout;
+
+        vk::Pipeline vkPipeline;
+        if (m_Device.createComputePipelines(nullptr, 1, &computePipelineCreateInfo, nullptr, &vkPipeline)!= vk::Result::eSuccess)
+        {
+            m_Device.destroy(computeModule);
+            return PipelineHandle();
+        }
+
+        m_Device.destroy(computeModule);
+
+        const uint64_t id = m_Pipelines.size();
+        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, PipelineConfig() });
+
+        return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
+    }
+
+    // There is an issue for refactoring the Pipeline Manager.
+    // While including Compute Pipeline Creation, some private helper functions where introduced:
+
+    vk::Result PipelineManager::createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, const ShaderStage stage)
+    {
+        std::vector<char> code = shaderProgram.getShader(stage).shaderCode;
+        vk::ShaderModuleCreateInfo moduleInfo({}, code.size(), reinterpret_cast<uint32_t*>(code.data()));
+        return m_Device.createShaderModule(&moduleInfo, nullptr, &module);
     }
 }
\ No newline at end of file
diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp
index 896d0df1ce10f56d291ef1accf93f9783cdd9db4..b153eb4632b844e84b92953fe8abf6666a13e0c9 100644
--- a/src/vkcv/PipelineManager.hpp
+++ b/src/vkcv/PipelineManager.hpp
@@ -11,10 +11,19 @@ namespace vkcv
     class PipelineManager
     {
     private:
+    	struct Pipeline {
+			vk::Pipeline m_handle;
+			vk::PipelineLayout m_layout;
+			PipelineConfig m_config;
+    	};
+    	
         vk::Device m_Device;
-        std::vector<vk::Pipeline> m_Pipelines;
-        std::vector<vk::PipelineLayout> m_PipelineLayouts;
-        uint64_t m_NextPipelineId;
+        std::vector<Pipeline> m_Pipelines;
+        
+        void destroyPipelineById(uint64_t id);
+
+        vk::Result createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, ShaderStage stage);
+
     public:
         PipelineManager() = delete; // no default ctor
         explicit PipelineManager(vk::Device device) noexcept; // ctor
@@ -28,9 +37,17 @@ namespace vkcv
 
         PipelineHandle createPipeline(const PipelineConfig &config, PassManager& passManager);
 
+        PipelineHandle createComputePipeline(
+            const ShaderProgram& shaderProgram,
+            const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts);
+
         [[nodiscard]]
         vk::Pipeline getVkPipeline(const PipelineHandle &handle) const;
+
         [[nodiscard]]
         vk::PipelineLayout getVkPipelineLayout(const PipelineHandle &handle) const;
+
+        [[nodiscard]]
+        const PipelineConfig &getPipelineConfig(const PipelineHandle &handle) const;
     };
 }
diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp
index c062437553c4c49d72f6d9a4f1160da2e5d41884..df6c74cccf6c4652adc6a4c78802f282ea6ae293 100644
--- a/src/vkcv/QueueManager.cpp
+++ b/src/vkcv/QueueManager.cpp
@@ -1,10 +1,10 @@
 
 #include <limits>
 #include <unordered_set>
+#include <iostream>
 
 #include "vkcv/QueueManager.hpp"
-
-#include "vkcv/QueueManager.hpp"
+#include "vkcv/Logger.hpp"
 
 namespace vkcv {
 
@@ -89,7 +89,14 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        throw std::runtime_error("Too many graphics queues were requested than being available!");
+                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                            if (initialQueueFamilyStatus[i][0] > 0) {
+                                queuePairsGraphics.push_back(std::pair(i, 0));
+                                found = true;
+                            }
+                        }
+	
+						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
                     }
                     break;
                 case vk::QueueFlagBits::eCompute:
@@ -104,7 +111,14 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        throw std::runtime_error("Too many compute queues were requested than being available!");
+                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                            if (initialQueueFamilyStatus[i][1] > 0) {
+                                queuePairsCompute.push_back(std::pair(i, 0));
+                                found = true;
+                            }
+                        }
+                        
+						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
                     }
                     break;
                 case vk::QueueFlagBits::eTransfer:
@@ -119,7 +133,14 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        throw std::runtime_error("Too many transfer queues were requested than being available!");
+                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                            if (initialQueueFamilyStatus[i][2] > 0) {
+                                queuePairsTransfer.push_back(std::pair(i, 0));
+                                found = true;
+                            }
+                        }
+	
+						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
                     }
                     break;
                 default:
diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp
index 7935bbc16a54f170cb38161629c109d56cf56d3c..a6ebb95b5e237dcd06ed8041b3f16489f7339d6a 100644
--- a/src/vkcv/SamplerManager.cpp
+++ b/src/vkcv/SamplerManager.cpp
@@ -10,7 +10,7 @@ namespace vkcv {
 	
 	SamplerManager::~SamplerManager() {
 		for (uint64_t id = 0; id < m_samplers.size(); id++) {
-			destroySampler(SamplerHandle(id));
+			destroySamplerById(id);
 		}
 	}
 	
@@ -87,7 +87,7 @@ namespace vkcv {
 				false,
 				vk::CompareOp::eAlways,
 				0.0f,
-				1.0f,
+				16.0f,
 				vk::BorderColor::eIntOpaqueBlack,
 				false
 		);
@@ -96,7 +96,7 @@ namespace vkcv {
 		
 		const uint64_t id = m_samplers.size();
 		m_samplers.push_back(sampler);
-		return SamplerHandle(id);
+		return SamplerHandle(id, [&](uint64_t id) { destroySamplerById(id); });
 	}
 	
 	vk::Sampler SamplerManager::getVulkanSampler(const SamplerHandle &handle) const {
@@ -109,9 +109,7 @@ namespace vkcv {
 		return m_samplers[id];
 	}
 	
-	void SamplerManager::destroySampler(const SamplerHandle &handle) {
-		const uint64_t id = handle.getId();
-		
+	void SamplerManager::destroySamplerById(uint64_t id) {
 		if (id >= m_samplers.size()) {
 			return;
 		}
diff --git a/src/vkcv/SamplerManager.hpp b/src/vkcv/SamplerManager.hpp
index 41f58b2f33daaf8b08c785c05c7f14184cf47958..511176d4f87633a8691ca730ecc383e2588d8cf0 100644
--- a/src/vkcv/SamplerManager.hpp
+++ b/src/vkcv/SamplerManager.hpp
@@ -18,6 +18,8 @@ namespace vkcv {
 		
 		explicit SamplerManager(const vk::Device& device) noexcept;
 		
+		void destroySamplerById(uint64_t id);
+		
 	public:
 		~SamplerManager();
 		
@@ -34,8 +36,6 @@ namespace vkcv {
 		
 		[[nodiscard]]
 		vk::Sampler getVulkanSampler(const SamplerHandle& handle) const;
-		
-		void destroySampler(const SamplerHandle& handle);
 	
 	};
 	
diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 5185b8b402eae5cd514689ba51a06e1a437271bf..971797d9a42d071a1730ebf31a0b554f92fa361f 100644
--- a/src/vkcv/ShaderProgram.cpp
+++ b/src/vkcv/ShaderProgram.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "vkcv/ShaderProgram.hpp"
+#include "vkcv/Logger.hpp"
 
 namespace vkcv {
     /**
@@ -13,32 +14,36 @@ namespace vkcv {
      * @param[in] relative path to the shader code
      * @return vector of chars as a buffer for the code
      */
-	std::vector<char> readShaderCode(const std::filesystem::path &shaderPath)
-	{
-		std::ifstream file(shaderPath.string(), std::ios::ate | std::ios::binary);
+	std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) {
+		std::ifstream file (shaderPath.string(), std::ios::ate | std::ios::binary);
+		
 		if (!file.is_open()) {
-		    std::cout << "The file could not be opened." << std::endl;
+			vkcv_log(LogLevel::ERROR, "The file could not be opened");
 			return std::vector<char>{};
 		}
+		
 		size_t fileSize = (size_t)file.tellg();
 		std::vector<char> buffer(fileSize);
+		
 		file.seekg(0);
 		file.read(buffer.data(), fileSize);
+		file.close();
+		
         return buffer;
 	}
 
-	VertexFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
+	VertexAttachmentFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
         switch (basetype) {
             case spirv_cross::SPIRType::Int:
                 switch (vecsize) {
                     case 1:
-                        return VertexFormat::INT;
+                        return VertexAttachmentFormat::INT;
                     case 2:
-                        return VertexFormat::INT2;
+                        return VertexAttachmentFormat::INT2;
                     case 3:
-                        return VertexFormat::INT3;
+                        return VertexAttachmentFormat::INT3;
                     case 4:
-                        return VertexFormat::INT4;
+                        return VertexAttachmentFormat::INT4;
                     default:
                         break;
                 }
@@ -46,13 +51,13 @@ namespace vkcv {
             case spirv_cross::SPIRType::Float:
                 switch (vecsize) {
                     case 1:
-                        return VertexFormat::FLOAT;
+                        return VertexAttachmentFormat::FLOAT;
                     case 2:
-                        return VertexFormat::FLOAT2;
+                        return VertexAttachmentFormat::FLOAT2;
                     case 3:
-                        return VertexFormat::FLOAT3;
+                        return VertexAttachmentFormat::FLOAT3;
                     case 4:
-                        return VertexFormat::FLOAT4;
+                        return VertexAttachmentFormat::FLOAT4;
                     default:
                         break;
                 }
@@ -60,27 +65,31 @@ namespace vkcv {
             default:
                 break;
         }
-        std::cout << "Shader Program Reflection: unknown Vertex Format" << std::endl;
-        return VertexFormat::FLOAT;
+		
+		vkcv_log(LogLevel::WARNING, "Unknown vertex format");
+        return VertexAttachmentFormat::FLOAT;
 	}
 
 	ShaderProgram::ShaderProgram() noexcept :
 	m_Shaders{},
-    m_VertexLayout{}
+    m_VertexAttachments{},
+    m_DescriptorSets{}
 	{}
 
 	bool ShaderProgram::addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath)
 	{
-	    if(m_Shaders.find(shaderStage) != m_Shaders.end())
-	        std::cout << "Found existing shader stage. Overwriting."  << std::endl;
+	    if(m_Shaders.find(shaderStage) != m_Shaders.end()) {
+			vkcv_log(LogLevel::WARNING, "Overwriting existing shader stage");
+		}
 
 	    const std::vector<char> shaderCode = readShaderCode(shaderPath);
-	    if (shaderCode.empty())
-	        return false;
-	    else
-        {
+	    
+	    if (shaderCode.empty()) {
+			return false;
+		} else {
             Shader shader{shaderCode, shaderStage};
             m_Shaders.insert(std::make_pair(shaderStage, shader));
+            reflectShader(shaderStage);
             return true;
         }
 	}
@@ -103,34 +112,113 @@ namespace vkcv {
         auto shaderCodeChar = m_Shaders.at(shaderStage).shaderCode;
         std::vector<uint32_t> shaderCode;
 
-        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++) {
+        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++)
             shaderCode.push_back(((uint32_t*) shaderCodeChar.data())[i]);
-        }
 
         spirv_cross::Compiler comp(move(shaderCode));
         spirv_cross::ShaderResources resources = comp.get_shader_resources();
 
-		if (shaderStage == ShaderStage::VERTEX) {
-			std::vector<VertexInputAttachment> inputVec;
-			uint32_t offset = 0;
+        //reflect vertex input
+		if (shaderStage == ShaderStage::VERTEX)
+		{
+			// spirv-cross API (hopefully) returns the stage_inputs in order
+			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++)
+			{
+                // spirv-cross specific objects
+				auto& stage_input = resources.stage_inputs[i];
+				const spirv_cross::SPIRType& base_type = comp.get_type(stage_input.base_type_id);
+
+				// vertex input location
+				const uint32_t attachment_loc = comp.get_decoration(stage_input.id, spv::DecorationLocation);
+                // vertex input name
+                const std::string attachment_name = stage_input.name;
+				// vertex input format (implies its size)
+				const VertexAttachmentFormat attachment_format = convertFormat(base_type.basetype, base_type.vecsize);
+
+                m_VertexAttachments.emplace_back(attachment_loc, attachment_name, attachment_format);
+            }
+		}
+
+		//reflect descriptor sets (uniform buffer, storage buffer, sampler, sampled image, storage image)
+        std::vector<std::pair<uint32_t, DescriptorBinding>> bindings;
+        int32_t maxSetID = -1;
+        for (uint32_t i = 0; i < resources.uniform_buffers.size(); i++) {
+            auto& u = resources.uniform_buffers[i];
+            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
+                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::UNIFORM_BUFFER, base_type.vecsize, shaderStage));
+            bindings.push_back(descriptor);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+        }
+
+        for (uint32_t i = 0; i < resources.storage_buffers.size(); i++) {
+            auto& u = resources.storage_buffers[i];
+            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
+                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::STORAGE_BUFFER, base_type.vecsize, shaderStage));
+            bindings.push_back(descriptor);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+        }
 
-			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++) {
-				auto& u = resources.stage_inputs[i];
-				const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+        for (uint32_t i = 0; i < resources.separate_samplers.size(); i++) {
+            auto& u = resources.separate_samplers[i];
+            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
+                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::SAMPLER, base_type.vecsize, shaderStage));
+            bindings.push_back(descriptor);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+        }
 
-				VertexInputAttachment input = VertexInputAttachment(comp.get_decoration(u.id, spv::DecorationLocation),
-					0,
-					convertFormat(base_type.basetype, base_type.vecsize),
-					offset);
-				inputVec.push_back(input);
-				offset += base_type.vecsize * base_type.width / 8;
-			}
+        for (uint32_t i = 0; i < resources.separate_images.size(); i++) {
+            auto& u = resources.separate_images[i];
+            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
+                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::IMAGE_SAMPLED, base_type.vecsize, shaderStage));
+            bindings.push_back(descriptor);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID)
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
 
-			m_VertexLayout = VertexLayout(inputVec);
+        }
+
+        for (uint32_t i = 0; i < resources.storage_images.size(); i++) {
+            auto& u = resources.storage_images[i];
+            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
+                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::IMAGE_STORAGE, base_type.vecsize, shaderStage));
+            bindings.push_back(descriptor);
+            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID)
+                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+        }
+        if (maxSetID != -1) {
+            if((int32_t)m_DescriptorSets.size() <= maxSetID) m_DescriptorSets.resize(maxSetID + 1);
+            for (const auto &binding : bindings) {
+                m_DescriptorSets[binding.first].push_back(binding.second);
+            }
+        }
+
+        //reflect push constants
+		for (const auto &pushConstantBuffer : resources.push_constant_buffers) {
+			for (const auto &range : comp.get_active_buffer_ranges(pushConstantBuffer.id)) {
+				const size_t size = range.range + range.offset;
+				m_pushConstantSize = std::max(m_pushConstantSize, size);
+			}
 		}
     }
 
-    const VertexLayout& ShaderProgram::getVertexLayout() const{
-        return m_VertexLayout;
+    const std::vector<VertexAttachment> &ShaderProgram::getVertexAttachments() const
+    {
+        return m_VertexAttachments;
+	}
+
+    const std::vector<std::vector<DescriptorBinding>>& ShaderProgram::getReflectedDescriptors() const {
+        return m_DescriptorSets;
+    }
+
+	size_t ShaderProgram::getPushConstantSize() const
+	{
+		return m_pushConstantSize;
 	}
 }
diff --git a/src/vkcv/Surface.cpp b/src/vkcv/Surface.cpp
deleted file mode 100644
index 29b6c646dc212cba2cc31f32dca5c4fcc023cd03..0000000000000000000000000000000000000000
--- a/src/vkcv/Surface.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "Surface.hpp"
-
-#define GLFW_INCLUDE_VULKAN
-#include <GLFW/glfw3.h>
-
-namespace vkcv {
-	/**
-	* creates surface and checks availability
-	* @param window current window for the surface
-	* @param instance Vulkan-Instance
-	* @param physicalDevice Vulkan-PhysicalDevice
-	* @return created surface
-	*/
-	vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice) {
-		//create surface
-		VkSurfaceKHR surface;
-		if (glfwCreateWindowSurface(VkInstance(instance), window, nullptr, &surface) != VK_SUCCESS) {
-			throw std::runtime_error("failed to create a window surface!");
-		}
-		vk::Bool32 surfaceSupport = false;
-		if (physicalDevice.getSurfaceSupportKHR(0, vk::SurfaceKHR(surface), &surfaceSupport) != vk::Result::eSuccess && surfaceSupport != true) {
-			throw std::runtime_error("surface is not supported by the device!");
-		}
-
-		return vk::SurfaceKHR(surface);
-	}
-}
diff --git a/src/vkcv/Surface.hpp b/src/vkcv/Surface.hpp
deleted file mode 100644
index 74aafeba821334767ac5e13cd33e1d9674e12f5b..0000000000000000000000000000000000000000
--- a/src/vkcv/Surface.hpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-
-struct GLFWwindow;
-
-namespace vkcv {	
-	vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice);
-}
\ No newline at end of file
diff --git a/src/vkcv/SwapChain.cpp b/src/vkcv/Swapchain.cpp
similarity index 52%
rename from src/vkcv/SwapChain.cpp
rename to src/vkcv/Swapchain.cpp
index 3483ae37e718453a99d56d31e025433acb7f4422..2c5b3530c396bc3532aa94cb59a120e3555291bf 100644
--- a/src/vkcv/SwapChain.cpp
+++ b/src/vkcv/Swapchain.cpp
@@ -1,29 +1,69 @@
-#include <vkcv/SwapChain.hpp>
+#include <vkcv/Swapchain.hpp>
+#include <utility>
 
-namespace vkcv {
+#include <GLFW/glfw3.h>
 
-    SwapChain::SwapChain(vk::SurfaceKHR surface, vk::SwapchainKHR swapchain, vk::SurfaceFormatKHR format, uint32_t imageCount)
-        : m_surface(surface), m_swapchain(swapchain), m_format( format), m_ImageCount(imageCount)
+namespace vkcv
+{
+    /**
+    * creates surface and checks availability
+    * @param window current window for the surface
+    * @param instance Vulkan-Instance
+    * @param physicalDevice Vulkan-PhysicalDevice
+    * @return created surface
+    */
+    vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice) {
+        //create surface
+        VkSurfaceKHR surface;
+        if (glfwCreateWindowSurface(VkInstance(instance), window, nullptr, &surface) != VK_SUCCESS) {
+            throw std::runtime_error("failed to create a window surface!");
+        }
+        vk::Bool32 surfaceSupport = false;
+        if (physicalDevice.getSurfaceSupportKHR(0, vk::SurfaceKHR(surface), &surfaceSupport) != vk::Result::eSuccess && surfaceSupport != true) {
+            throw std::runtime_error("surface is not supported by the device!");
+        }
+
+        return vk::SurfaceKHR(surface);
+    }
+
+    Swapchain::Swapchain(const Surface &surface,
+                         vk::SwapchainKHR swapchain,
+                         vk::Format format,
+                         vk::ColorSpaceKHR colorSpace,
+                         vk::PresentModeKHR presentMode,
+                         uint32_t imageCount,
+						 vk::Extent2D extent) noexcept :
+			m_Surface(surface),
+			m_Swapchain(swapchain),
+			m_Format(format),
+			m_ColorSpace(colorSpace),
+			m_PresentMode(presentMode),
+			m_ImageCount(imageCount),
+			m_Extent(extent),
+			m_RecreationRequired(false)
     {}
+    
+    Swapchain::Swapchain(const Swapchain &other) :
+			m_Surface(other.m_Surface),
+			m_Swapchain(other.m_Swapchain),
+			m_Format(other.m_Format),
+			m_ColorSpace(other.m_ColorSpace),
+			m_PresentMode(other.m_PresentMode),
+			m_ImageCount(other.m_ImageCount),
+			m_Extent(other.m_Extent),
+			m_RecreationRequired(other.m_RecreationRequired.load())
+	{}
 
-    const vk::SwapchainKHR& SwapChain::getSwapchain() const {
-        return m_swapchain;
+    const vk::SwapchainKHR& Swapchain::getSwapchain() const {
+        return m_Swapchain;
     }
 
-    /**
-     * gets surface of the swapchain
-     * @return current surface
-     */
-    vk::SurfaceKHR SwapChain::getSurface() {
-        return m_surface;
+    vk::SurfaceKHR Swapchain::getSurface() const {
+        return m_Surface.handle;
     }
 
-    /**
-     * gets the surface of the swapchain
-     * @return chosen format
-     */
-    vk::SurfaceFormatKHR SwapChain::getSurfaceFormat(){
-        return m_format;
+    vk::Format Swapchain::getFormat() const{
+        return m_Format;
     }
 
     /**
@@ -33,7 +73,7 @@ namespace vkcv {
      * @param window of the current application
      * @return chosen Extent for the surface
      */
-    vk::Extent2D chooseSwapExtent(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window &window){
+    vk::Extent2D chooseExtent(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window &window){
         vk::SurfaceCapabilitiesKHR surfaceCapabilities;
         if(physicalDevice.getSurfaceCapabilitiesKHR(surface,&surfaceCapabilities) != vk::Result::eSuccess){
             throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface.");
@@ -43,15 +83,10 @@ namespace vkcv {
                 static_cast<uint32_t>(window.getWidth()),
                 static_cast<uint32_t>(window.getHeight())
         };
+        
         extent2D.width = std::max(surfaceCapabilities.minImageExtent.width, std::min(surfaceCapabilities.maxImageExtent.width, extent2D.width));
         extent2D.height = std::max(surfaceCapabilities.minImageExtent.height, std::min(surfaceCapabilities.maxImageExtent.height, extent2D.height));
 
-        if (extent2D.width > surfaceCapabilities.maxImageExtent.width ||
-            extent2D.width < surfaceCapabilities.minImageExtent.width ||
-            extent2D.height > surfaceCapabilities.maxImageExtent.height ||
-            extent2D.height < surfaceCapabilities.minImageExtent.height) {
-            std::printf("Surface size not matching. Resizing to allowed value.");
-        }
         return extent2D;
     }
 
@@ -61,7 +96,7 @@ namespace vkcv {
      * @param surface of the swapchain
      * @return available Format
      */
-    vk::SurfaceFormatKHR chooseSwapSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
+    vk::SurfaceFormatKHR chooseSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
         uint32_t formatCount;
         physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, nullptr);
         std::vector<vk::SurfaceFormatKHR> availableFormats(formatCount);
@@ -126,46 +161,101 @@ namespace vkcv {
      * @param context that keeps instance, physicalDevice and a device.
      * @return swapchain
      */
-    SwapChain SwapChain::create(const Window &window, const Context &context, const vk::SurfaceKHR surface) {
+    Swapchain Swapchain::create(const Window &window, const Context &context) {
         const vk::Instance& instance = context.getInstance();
         const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
         const vk::Device& device = context.getDevice();
 
-        vk::Extent2D extent2D = chooseSwapExtent(physicalDevice, surface, window);
-        vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(physicalDevice, surface);
-        vk::PresentModeKHR presentMode = choosePresentMode(physicalDevice, surface);
-        uint32_t imageCount = chooseImageCount(physicalDevice, surface);
+        Surface surface;
+        surface.handle       = createSurface(window.getWindow(), instance, physicalDevice);
+        surface.formats      = physicalDevice.getSurfaceFormatsKHR(surface.handle);
+        surface.capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.handle);
+        surface.presentModes = physicalDevice.getSurfacePresentModesKHR(surface.handle);
+
+        vk::Extent2D chosenExtent = chooseExtent(physicalDevice, surface.handle, window);
+        vk::SurfaceFormatKHR chosenSurfaceFormat = chooseSurfaceFormat(physicalDevice, surface.handle);
+        vk::PresentModeKHR chosenPresentMode = choosePresentMode(physicalDevice, surface.handle);
+        uint32_t chosenImageCount = chooseImageCount(physicalDevice, surface.handle);
 
         vk::SwapchainCreateInfoKHR swapchainCreateInfo(
                 vk::SwapchainCreateFlagsKHR(),  //flags
-                surface,    // surface
-                imageCount,  // minImageCount TODO: how many do we need for our application?? "must be less than or equal to the value returned in maxImageCount" -> 3 for Triple Buffering, else 2 for Double Buffering (should be the standard)
-                surfaceFormat.format,   // imageFormat
-                surfaceFormat.colorSpace,   // imageColorSpace
-                extent2D,   // imageExtent
+                surface.handle,    // surface
+                chosenImageCount,  // minImageCount TODO: how many do we need for our application?? "must be less than or equal to the value returned in maxImageCount" -> 3 for Triple Buffering, else 2 for Double Buffering (should be the standard)
+                chosenSurfaceFormat.format,   // imageFormat
+                chosenSurfaceFormat.colorSpace,   // imageColorSpace
+                chosenExtent,   // imageExtent
                 1,  // imageArrayLayers TODO: should we only allow non-stereoscopic applications? yes -> 1, no -> ? "must be greater than 0, less or equal to maxImageArrayLayers"
-                vk::ImageUsageFlagBits::eColorAttachment,  // imageUsage TODO: what attachments? only color? depth?
+                vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,  // imageUsage TODO: what attachments? only color? depth?
                 vk::SharingMode::eExclusive,    // imageSharingMode TODO: which sharing mode? "VK_SHARING_MODE_EXCLUSIV access exclusive to a single queue family, better performance", "VK_SHARING_MODE_CONCURRENT access from multiple queues"
                 0,  // queueFamilyIndexCount, the number of queue families having access to the image(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
                 nullptr,    // pQueueFamilyIndices, the pointer to an array of queue family indices having access to the images(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
                 vk::SurfaceTransformFlagBitsKHR::eIdentity, // preTransform, transformations applied onto the image before display
                 vk::CompositeAlphaFlagBitsKHR::eOpaque, // compositeAlpha, TODO: how to handle transparent pixels? do we need transparency? If no -> opaque
-                presentMode,    // presentMode
+                chosenPresentMode,    // presentMode
                 true,   // clipped
                 nullptr // oldSwapchain
         );
 
         vk::SwapchainKHR swapchain = device.createSwapchainKHR(swapchainCreateInfo);
 
-        return SwapChain(surface, swapchain, surfaceFormat, imageCount);
+        return Swapchain(surface,
+                         swapchain,
+                         chosenSurfaceFormat.format,
+                         chosenSurfaceFormat.colorSpace,
+                         chosenPresentMode,
+                         chosenImageCount,
+						 chosenExtent);
+    }
+    
+    bool Swapchain::shouldUpdateSwapchain() const {
+    	return m_RecreationRequired;
+    }
+    
+    void Swapchain::updateSwapchain(const Context &context, const Window &window) {
+    	if (!m_RecreationRequired.exchange(false))
+    		return;
+    	
+		vk::SwapchainKHR oldSwapchain = m_Swapchain;
+		vk::Extent2D extent2D = chooseExtent(context.getPhysicalDevice(), m_Surface.handle, window);
+	
+		vk::SwapchainCreateInfoKHR swapchainCreateInfo(
+				vk::SwapchainCreateFlagsKHR(),
+				m_Surface.handle,
+				m_ImageCount,
+				m_Format,
+				m_ColorSpace,
+				extent2D,
+				1,
+				vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,
+				vk::SharingMode::eExclusive,
+				0,
+				nullptr,
+				vk::SurfaceTransformFlagBitsKHR::eIdentity,
+				vk::CompositeAlphaFlagBitsKHR::eOpaque,
+				m_PresentMode,
+				true,
+				oldSwapchain
+		);
+	
+		m_Swapchain = context.getDevice().createSwapchainKHR(swapchainCreateInfo);
+		context.getDevice().destroySwapchainKHR(oldSwapchain);
+		
+		m_Extent = extent2D;
     }
 
+    void Swapchain::signalSwapchainRecreation() {
+		m_RecreationRequired = true;
+    }
+    
+    const vk::Extent2D& Swapchain::getExtent() const {
+    	return m_Extent;
+    }
 
-    SwapChain::~SwapChain() {
+    Swapchain::~Swapchain() {
         // needs to be destroyed by creator
     }
 
-	uint32_t SwapChain::getImageCount() {
+	uint32_t Swapchain::getImageCount() const {
 		return m_ImageCount;
 	}
 }
diff --git a/src/vkcv/VertexLayout.cpp b/src/vkcv/VertexLayout.cpp
index b06c6743e1e19a5e282af248ab6b590eb97529fd..fa079a3264ae47b32461bda26485adb97b0be280 100644
--- a/src/vkcv/VertexLayout.cpp
+++ b/src/vkcv/VertexLayout.cpp
@@ -3,51 +3,60 @@
 //
 
 #include "vkcv/VertexLayout.hpp"
+#include "vkcv/Logger.hpp"
 
 namespace vkcv {
-    uint32_t getFormatSize(VertexFormat format) {
+    uint32_t getFormatSize(VertexAttachmentFormat format) {
         switch (format) {
-            case VertexFormat::FLOAT:
+            case VertexAttachmentFormat::FLOAT:
                 return 4;
-            case VertexFormat::FLOAT2:
+            case VertexAttachmentFormat::FLOAT2:
                 return 8;
-            case VertexFormat::FLOAT3:
+            case VertexAttachmentFormat::FLOAT3:
                 return 12;
-            case VertexFormat::FLOAT4:
+            case VertexAttachmentFormat::FLOAT4:
                 return 16;
-            case VertexFormat::INT:
+            case VertexAttachmentFormat::INT:
                 return 4;
-            case VertexFormat::INT2:
+            case VertexAttachmentFormat::INT2:
                 return 8;
-            case VertexFormat::INT3:
+            case VertexAttachmentFormat::INT3:
                 return 12;
-            case VertexFormat::INT4:
+            case VertexAttachmentFormat::INT4:
                 return 16;
             default:
-                break;
+				vkcv_log(LogLevel::WARNING, "No format given");
+                return 0;
         }
-        std::cout << "VertexLayout: No format given" << std::endl;
-        return 0;
     }
 
-    VertexInputAttachment::VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept:
-            location{location},
-            binding{binding},
+    VertexAttachment::VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept:
+            inputLocation{inputLocation},
+            name{name},
             format{format},
-            offset{offset}
-            {}
-
-    VertexLayout::VertexLayout() noexcept :
-    stride{0},
-    attachmentMap()
+            offset{0}
     {}
 
-    VertexLayout::VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept {
-        stride = 0;
-        for (const auto &input : inputs) {
-            attachmentMap.insert(std::make_pair(input.location, input));
-            stride += getFormatSize(input.format);
+
+    VertexBinding::VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept :
+    bindingLocation{bindingLocation},
+    stride{0},
+    vertexAttachments{attachments}
+    {
+        uint32_t offset = 0;
+        for (auto &attachment : vertexAttachments)
+        {
+            offset += getFormatSize(attachment.format);
+            attachment.offset = offset;
         }
+        stride = offset;
     }
 
+    VertexLayout::VertexLayout() noexcept :
+    vertexBindings{}
+    {}
+
+    VertexLayout::VertexLayout(const std::vector<VertexBinding> &bindings) noexcept :
+    vertexBindings{bindings}
+    {}
 }
\ No newline at end of file
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index c21271b78f7501721d5c0496d0344dd68e2e7e52..03a58a23b994209c7a0ee195732dc98543f0eddc 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -5,64 +5,103 @@
  */
 
 #include <GLFW/glfw3.h>
-
 #include "vkcv/Window.hpp"
 
 namespace vkcv {
 
-    static uint32_t s_WindowCount = 0;
-
-    Window::Window(GLFWwindow *window)
-            : m_window(window) {
+	static std::vector<GLFWwindow*> s_Windows;
+
+    Window::Window(GLFWwindow *window) :
+    m_window(window),
+	e_mouseButton(true),
+	e_mouseMove(true),
+	e_mouseScroll(true),
+	e_resize(true),
+	e_key(true),
+	e_char(true),
+	e_gamepad(true)
+    {
+		glfwSetWindowUserPointer(m_window, this);
+	
+		// combine Callbacks with Events
+		glfwSetMouseButtonCallback(m_window, Window::onMouseButtonEvent);
+		glfwSetCursorPosCallback(m_window, Window::onMouseMoveEvent);
+		glfwSetWindowSizeCallback(m_window, Window::onResize);
+		glfwSetKeyCallback(m_window, Window::onKeyEvent);
+		glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
+		glfwSetCharCallback(m_window, Window::onCharEvent);
     }
 
     Window::~Window() {
+        Window::e_mouseButton.unlock();
+        Window::e_mouseMove.unlock();
+        Window::e_mouseScroll.unlock();
+        Window::e_resize.unlock();
+        Window::e_key.unlock();
+        Window::e_char.unlock();
+        Window::e_gamepad.unlock();
+
+		s_Windows.erase(std::find(s_Windows.begin(), s_Windows.end(), m_window));
         glfwDestroyWindow(m_window);
-        s_WindowCount--;
 
-        if(s_WindowCount == 0) {
+        if(s_Windows.empty()) {
             glfwTerminate();
         }
     }
 
     Window Window::create( const char *windowTitle, int width, int height, bool resizable) {
-        if(s_WindowCount == 0) {
-            glfwInit();
-        }
-        s_WindowCount++;
-
-        width = std::max(width, 1);
-        height = std::max(height, 1);
-
-        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
-        glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
-        GLFWwindow *window;
-        window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
-
-        return Window(window);
+		if(s_Windows.empty()) {
+			glfwInit();
+		}
+	
+		width = std::max(width, 1);
+		height = std::max(height, 1);
+	
+		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+		glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
+		GLFWwindow *window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
+	
+		s_Windows.push_back(window);
+	
+		return Window(window);
     }
 
-    void Window::initEvents() {
-        glfwSetWindowUserPointer(m_window, this);
-
-        // combine Callbacks with Events
-        glfwSetMouseButtonCallback(m_window, Window::onMouseButtonEvent);
-
-        glfwSetCursorPosCallback(m_window, Window::onMouseMoveEvent);
-
-        glfwSetWindowSizeCallback(m_window, Window::onResize);
-
-        glfwSetKeyCallback(m_window, Window::onKeyEvent);
+    void Window::pollEvents() {
 
-        glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
-    }
+    	for (auto glfwWindow : s_Windows) {
+			auto window = static_cast<Window *>(glfwGetWindowUserPointer(glfwWindow));
+			
+			window->e_mouseButton.unlock();
+			window->e_mouseMove.unlock();
+			window->e_mouseScroll.unlock();
+			window->e_resize.unlock();
+			window->e_key.unlock();
+			window->e_char.unlock();
+			window->e_gamepad.unlock();
+    	}
 
-    void Window::pollEvents() {
         glfwPollEvents();
+    	
+    	for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) {
+    		if (glfwJoystickPresent(gamepadIndex)) {
+				onGamepadEvent(gamepadIndex);
+			}
+		}
+	
+		for (auto glfwWindow : s_Windows) {
+			auto window = static_cast<Window *>(glfwGetWindowUserPointer(glfwWindow));
+		
+			window->e_mouseButton.lock();
+			window->e_mouseMove.lock();
+			window->e_mouseScroll.lock();
+			window->e_resize.lock();
+			window->e_key.lock();
+			window->e_char.lock();
+			window->e_gamepad.lock();
+		}
     }
 
     void Window::onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -71,7 +110,6 @@ namespace vkcv {
     }
 
     void Window::onMouseMoveEvent(GLFWwindow *callbackWindow, double x, double y) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -88,7 +126,6 @@ namespace vkcv {
     }
 
     void Window::onResize(GLFWwindow *callbackWindow, int width, int height) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -97,13 +134,33 @@ namespace vkcv {
     }
 
     void Window::onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
             window->e_key(key, scancode, action, mods);
         }
     }
+    
+    void Window::onCharEvent(GLFWwindow *callbackWindow, unsigned int c) {
+		auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+	
+		if (window != nullptr) {
+			window->e_char(c);
+		}
+    }
+
+    void Window::onGamepadEvent(int gamepadIndex) {
+        int activeWindowIndex = std::find_if(s_Windows.begin(),
+                                             s_Windows.end(),
+                                             [](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);})
+                                - s_Windows.begin();
+        activeWindowIndex *= (activeWindowIndex < s_Windows.size());    // fixes index getting out of bounds (e.g. if there is no focused window)
+        auto window = static_cast<Window *>(glfwGetWindowUserPointer(s_Windows[activeWindowIndex]));
+
+        if (window != nullptr) {
+            window->e_gamepad(gamepadIndex);
+        }
+    }
 
     bool Window::isWindowOpen() const {
         return !glfwWindowShouldClose(m_window);
@@ -124,4 +181,4 @@ namespace vkcv {
     GLFWwindow *Window::getWindow() const {
         return m_window;
     }
-}
\ No newline at end of file
+}