diff --git a/config/Sources.cmake b/config/Sources.cmake
index 6fc477cc9552d0a9a8921151ca4435b894630755..4f673e00d1e42e733534480d6085affd651a8c04 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -29,10 +29,14 @@ 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/ShaderStage.hpp
+		
 		${vkcv_include}/vkcv/ShaderProgram.hpp
 		${vkcv_source}/vkcv/ShaderProgram.cpp
 
@@ -69,4 +73,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/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 0a50765fedb07f1117b5e0ecaa43c03b362c23ce..4a51b24f5c978daebc5116e20b527252c8063d61 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -22,13 +22,11 @@
 #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;
@@ -37,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 typename event_function<const vk::CommandBuffer&>::type RecordCommandFunction;
-	typedef typename event_function<>::type FinishCommandFunction;
 
     class Core final
     {
@@ -65,27 +61,30 @@ namespace vkcv
 
         Context m_Context;
 
-        SwapChain					m_swapchain;
-        std::vector<vk::ImageView>	m_swapchainImageViews;
-        const 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;
+        SwapChain                       m_swapchain;
+        std::vector<vk::ImageView>      m_swapchainImageViews;
+        std::vector<vk::Image>          m_swapchainImages;
+		std::vector<vk::ImageLayout>    m_swapchainImageLayouts;
+        const Window&                   m_window;
 
-		CommandResources				m_CommandResources;
-		SyncResources					m_SyncResources;
-		uint32_t						m_currentSwapchainImageIndex;
+        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;
 
-		ImageHandle						m_DepthImage;
+		CommandResources    m_CommandResources;
+		SyncResources       m_SyncResources;
+		uint32_t            m_currentSwapchainImageIndex;
 
         std::function<void(int, int)> e_resizeHandle;
 
         static std::vector<vk::ImageView> createImageViews( Context &context, SwapChain& swapChain);
 
+		void recordSwapchainImageLayoutTransition(vk::CommandBuffer cmdBuffer, vk::ImageLayout newLayout);
+
     public:
         /**
          * Destructor of #Core destroys the Vulkan objects contained in the core's context.
@@ -158,6 +157,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.
@@ -211,29 +223,30 @@ namespace vkcv
          *   @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 getDescriptorSetLayout(ResourcesHandle handle, size_t setIndex);
+		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
 
 		/**
 		 * @brief start recording command buffers and increment frame index
 		*/
-		void beginFrame();
-
-		/**
-		 * @brief render a beautiful triangle
-		*/
-		void renderMesh(
-			const PassHandle						&renderpassHandle,
-			const PipelineHandle					&pipelineHandle,
-			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);
+		bool beginFrame(uint32_t& width, uint32_t& height);
+
+		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
@@ -251,6 +264,20 @@ 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 recordAndSubmitCommands(
+			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);
     };
 }
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/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/Handles.hpp b/include/vkcv/Handles.hpp
index bb0438d3e2482bb119c633a4e987cf28dac2375f..ea05bd212dd9314957e4771070bedb3963b1b2dc 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -79,7 +79,7 @@ namespace vkcv
 		using Handle::Handle;
 	};
 	
-	class ResourcesHandle : public Handle {
+	class DescriptorSetHandle : public Handle {
 		friend class DescriptorManager;
 	private:
 		using Handle::Handle;
@@ -93,14 +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 d76bd12169f622f05f1731b0eb3065d6133a5f2f..a1219ce4147ebbb8ae0650da8a87766f8967874b 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:
@@ -37,11 +41,9 @@ namespace vkcv {
 		void fill(void* data, size_t size = SIZE_MAX);
 	private:
 		ImageManager* const m_manager;
-		const ImageHandle m_handle;
-		const vk::Format m_format;
-		vk::ImageLayout m_layout;
+		const ImageHandle   m_handle;
 
-		Image(ImageManager* manager, const ImageHandle& handle, vk::Format format);
+		Image(ImageManager* manager, const ImageHandle& handle);
 		
 		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
 		
diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..251b6b528c45ea509dbfcd0cfb7135b77031f1ac
--- /dev/null
+++ b/include/vkcv/Logger.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <iostream>
+
+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          \
+  ];                                \
+  std::snprintf(                    \
+    output_message,                 \
+    VKCV_DEBUG_MESSAGE_LEN,         \
+    __VA_ARGS__                     \
+  );                                \
+  std::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..729330fcaf7eeac2acfdd1816b86ac29c7d9e30b 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -7,9 +7,9 @@
 
 #include <vector>
 #include <cstdint>
-#include "vkcv/Handles.hpp"
+#include "Handles.hpp"
 #include "ShaderProgram.hpp"
-#include <vkcv/VertexLayout.hpp>
+#include "VertexLayout.hpp"
 
 namespace vkcv {
 
@@ -21,22 +21,26 @@ namespace vkcv {
          * @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
+         * @param passHandle handle for render pass
+         * @param vertexLayout layout of vertex buffer, comprised of its bindings and the bindings' attachments
          */
         PipelineConfig(
-			const ShaderProgram&						shaderProgram, 
-			uint32_t									width, 
-			uint32_t									height, 
-			PassHandle									&passHandle,
-			const std::vector<VertexAttribute>			&vertexAttributes,
-			const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts);
+            const ShaderProgram&                        shaderProgram,
+            uint32_t                                    width,
+            uint32_t                                    height,
+            const PassHandle                            &passHandle,
+            const VertexLayout                          &vertexLayouts,
+            const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
+            bool                                        useDynamicViewport);
+
+        ShaderProgram                         m_ShaderProgram;
+        uint32_t                              m_Height;
+        uint32_t                              m_Width;
+        PassHandle                            m_PassHandle;
+        VertexLayout                          m_VertexLayout;
+        std::vector<vk::DescriptorSetLayout>  m_DescriptorLayouts;
+        bool                                  m_UseDynamicViewport;
 
-		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;
     };
 
 }
\ No newline at end of file
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index ef5d1f00ea3eeb97d97d8824439ded1ed326f33c..28f746d78477062eae9b0ad88f8c5de71e11efd0 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
index 53f7b783051307c7d327f20f389d7bef0fb9a16c..089205d1633551b4ad9f11d0bdd5540b2bb61bbb 100644
--- a/include/vkcv/SwapChain.hpp
+++ b/include/vkcv/SwapChain.hpp
@@ -107,7 +107,7 @@ namespace vkcv
 		/**
 		 *
 		 */
-        void recreateSwapchain();
+        void signalSwapchainRecreation();
 	
         /**
          * TODO
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index ee0ad8ef56d5284af2be4c81b7ea2f0d052d5a6f..9f609b48472386cd7628ff40b5fa4b90bc91649a 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/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 24687e846ff9eae3275de357331a825f0b4ed2c3..d687bbf60a03a59eee8c29f9b676f10cc7f2f2b3 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -8,7 +8,6 @@
 #include <string>
 #include <vector>
 #include <cstdint>
-#include <vkcv/VertexLayout.hpp>
 
 /* These macros define limits of the following structs. Implementations can
  * test against these limits when performing sanity checks. The main constraint
@@ -53,6 +52,23 @@ enum PrimitiveMode {
 /* The indices in the index buffer can be of different bit width. */
 enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
 
+/* 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;
+
 typedef struct {
 	// TODO not yet needed for the first (unlit) triangle
 } Material;
@@ -71,7 +87,7 @@ 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
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index e660b442d0b9a0208f95c9d753ef19e926bcac44..f3823cc8f3fe54b53835f356dd14a086515118dd 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -7,6 +7,8 @@
 #define STBI_ONLY_JPEG
 #include <stb_image.h>
 
+#include <vkcv/Logger.hpp>
+
 namespace vkcv::asset {
 
 /**
@@ -39,11 +41,12 @@ 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);
 	}
 }
@@ -121,7 +124,7 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		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";
+			vkcv_log(LogLevel::ERROR, "Copying index buffer data");
 			return 0;
 		}
 	}
@@ -136,7 +139,7 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		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";
+			vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
 			return 0;
 		}
 	}
@@ -150,9 +153,8 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 	case fx::gltf::Accessor::ComponentType::UnsignedInt:
 		indexType = UINT32; break;
 	default:
-		std::cerr << "ERROR: Index type not supported: " <<
-			static_cast<uint16_t>(indexAccessor.componentType) <<
-			std::endl;
+		vkcv_log(LogLevel::ERROR, "Index type (%u) not supported",
+				 static_cast<uint16_t>(indexAccessor.componentType));
 		return 0;
 	}
 
diff --git a/modules/camera/CMakeLists.txt b/modules/camera/CMakeLists.txt
index 28080bf2b1cd3bbc88d6c13d7ef26a43d7c3e19a..73f2dd1c81be9c6cadf563f7936bfaba8c1d0025 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
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
index ff8fda811eaad7a99dbb940601f9e5904025255e..dc9f2dcb3038655f51fb2404abc21f98a2120399 100644
--- a/modules/camera/include/vkcv/camera/Camera.hpp
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -1,101 +1,199 @@
 #pragma once
 
+#define GLM_DEPTH_ZERO_TO_ONE
+#define GLM_FORCE_LEFT_HANDED
 #include <glm/glm.hpp>
 #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(int width, int height);
 
+        /**
+         * @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..5fe7aba586068beff15525617d8e4817662746b7
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -0,0 +1,64 @@
+#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;
+    };
+
+}
\ 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 4e52eccea25e8544a9a5cc89d0dc74ddd0e023c6..69c4b311a561b9f3ce27e3d9906d68f21e2334ac 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -1,40 +1,186 @@
 #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)> e_keyHandle;
-        std::function<void(double, double)> e_mouseMoveHandle;
-        std::function<void(double, double)> e_mouseScrollHandle;
-        std::function<void(int, int, int)> e_mouseButtonHandle;
-        std::function<void(int, int)> e_resizeHandle;
-
-        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();
+        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;
+        std::function<void(int, int)> m_resizeHandle;
+
+        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;
+
+        /**
+         * @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 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.
+         * @param[in] width The width of the window.
+         * @param[in] height The height of the window.
+         */
+        CameraManager(Window &window, float width, float height);
+
+        /**
+         * @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..c6a9f7c7ffa9a3be77f12c29e456291fb8f6b845
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
@@ -0,0 +1,138 @@
+#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;
+
+        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);
+    };
+
+}
\ 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..0211043a9c6b862df8e500af190ad1f75a3c78aa
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -0,0 +1,100 @@
+#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);
+
+    };
+
+}
\ 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 b1d7381e3d548c9edf5d41e8d084c7edb1d02647..eb1857968b2284287691c6ed41ba168db30d3f84 100644
--- a/modules/camera/src/vkcv/camera/Camera.cpp
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -1,44 +1,28 @@
 #include "vkcv/camera/Camera.hpp"
-#include <iostream>
 
-namespace vkcv {
+#define _USE_MATH_DEFINES
+#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,98 +30,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( int width, int height){
-        m_width = width;
-        m_height = height;
-        m_ratio = static_cast<float>(width)/glm::max(height, 1);
-        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;
     }
 
@@ -149,31 +146,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 2631890d646fbf27a4fbb14cfeef706678d8918c..977c61d03dac51598281262acf9609540063a9e4 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -1,88 +1,156 @@
-#include <iostream>
+
 #include "vkcv/camera/CameraManager.hpp"
 
-namespace vkcv{
+#include <vkcv/Logger.hpp>
+
+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, float width, float height)
+    : 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;
     }
 
-    void CameraManager::bindCamera(){
-        e_keyHandle = m_window.e_key.add( [&](int key, int scancode, int action, int mods) { this->keyCallback(key, scancode, action, mods); });
-        e_mouseMoveHandle = m_window.e_mouseMove.add( [&]( double offsetX, double offsetY) {this->mouseMoveCallback( offsetX, offsetY);} );
-        e_mouseScrollHandle =  m_window.e_mouseScroll.add([&](double offsetX, double offsetY) {this->scrollCallback( offsetX, offsetY);} );
-        e_mouseButtonHandle = m_window.e_mouseButton.add([&] (int button, int action, int mods) {this->mouseButtonCallback( button,  action,  mods);});
-        e_resizeHandle = m_window.e_resize.add([&] (int width, int height) {this->resizeCallback( width, height);});
+    CameraManager::~CameraManager() {}
+
+    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);});
+    }
+
+    void CameraManager::resizeCallback(int width, int height) {
+        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());
+    }
 
-        if(!m_roationActive){
-            return;
+    void CameraManager::scrollCallback(double offsetX, double offsetY) {
+		getActiveController().scrollCallback(offsetX, offsetY, getActiveCamera());
+    }
+
+    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;
         }
+    }
+    
+    CameraController& CameraManager::getActiveController() {
+    	const ControllerType type = getControllerType(getActiveCameraIndex());
+    	return getControllerByType(type);
+    }
+	
+	uint32_t CameraManager::addCamera(ControllerType controllerType) {
+        Camera camera;
+        camera.setPerspective(glm::radians(60.0f), m_window.getWidth() / m_window.getHeight(), 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;
+    }
 
-        float sensitivity = 0.05f;
-        xoffset *= sensitivity;
-        yoffset *= sensitivity;
+    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];
+    }
 
-        m_camera.panView( xoffset , yoffset );
+    Camera& CameraManager::getActiveCamera() {
+        return m_cameras[getActiveCameraIndex()];
     }
 
-    void CameraManager::scrollCallback(double offsetX, double offsetY) {
-        m_camera.changeFov(offsetY);
+    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;
     }
 
-    void CameraManager::keyCallback(int key, int scancode, int action, int mods) {
+    uint32_t CameraManager::getActiveCameraIndex() const {
+        return m_activeCameraIndex;
+    }
 
-        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;
+    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;
     }
 
-    void CameraManager::resizeCallback(int width, int height){
-            m_camera.updateRatio(width, height);
+    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];
     }
 
-    Camera &CameraManager::getCamera(){
-        return m_camera;
+    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) {
+		getActiveController().updateCamera(deltaTime, getActiveCamera());
+	}
+	
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a50a0efa4b4e75adb81ce869d6b927bd0046758
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -0,0 +1,155 @@
+#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_rotationActive = false;
+
+        m_cameraSpeed = 2.0f;
+
+        m_fov_nsteps = 100;
+        m_fov_min = 10;
+        m_fov_max = 120;
+    }
+
+    void PilotCameraController::changeFov(double offset, Camera &camera){
+        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) {
+        // handle yaw rotation
+        float yaw = camera.getYaw() + xOffset;
+        if (yaw < -180.0f) {
+            yaw += 360.0f;
+        }
+        else if (yaw > 180.0f) {
+            yaw -= 360.0f;
+        }
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() - yOffset;
+        if (pitch > 89.0f) {
+            pitch = 89.0f;
+        }
+        if (pitch < -89.0f) {
+            pitch = -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) * front;
+		position += distance * getDirectionFactor(m_left, m_right) * left;
+		position += distance * getDirectionFactor(m_upward, m_downward) * 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::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..201c6ecdc1c703dbcd53b7dc4b179c86576f2312
--- /dev/null
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -0,0 +1,100 @@
+#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) {
+        if (radius < 0.1f) {
+            m_radius = 0.1f;
+        }
+        else {
+            m_radius = radius;
+        }
+    }
+
+    void TrackballCameraController::panView(double xOffset, double yOffset, Camera &camera) {
+        // handle yaw rotation
+        float yaw = camera.getYaw() + xOffset * m_cameraSpeed;
+        if (yaw < 0.0f) {
+            yaw += 360.0f;
+        }
+        else if (yaw > 360.0f) {
+            yaw -= 360.0f;
+        }
+        camera.setYaw(yaw);
+
+        // handle pitch rotation
+        float pitch = camera.getPitch() + yOffset * m_cameraSpeed;
+        if (pitch < 0.0f) {
+            pitch += 360.0f;
+        }
+        else if (pitch > 360.0f) {
+            pitch -= 360.0f;
+        }
+        camera.setPitch(pitch);
+    }
+
+    void TrackballCameraController::updateRadius(double offset, Camera &camera) {
+        glm::vec3 cameraPosition = camera.getPosition();
+        glm::vec3 cameraCenter = camera.getCenter();
+        float radius = glm::length(cameraCenter - cameraPosition);  // get current camera radius
+        setRadius(radius - 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.05f;
+        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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 7ca73a0811df7f1568508b56312ce3348237a695..ccbdaf4101c5dabb3e9d43788e255eab85ad5776 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -2,3 +2,4 @@
 # Add new projects/examples here:
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
+add_subdirectory(cmd_sync_test)
\ No newline at end of file
diff --git a/projects/cmd_sync_test/.gitignore b/projects/cmd_sync_test/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..16f72da367245ad14a38ee756816f06f8cbbe3d2
--- /dev/null
+++ b/projects/cmd_sync_test/.gitignore
@@ -0,0 +1 @@
+cmd_sync_test
\ No newline at end of file
diff --git a/projects/cmd_sync_test/CMakeLists.txt b/projects/cmd_sync_test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..da1d12949d9e8c918d78ab5cb0484106fae69b6a
--- /dev/null
+++ b/projects/cmd_sync_test/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+project(cmd_sync_test)
+
+# 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(cmd_sync_test src/main.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(cmd_sync_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(cmd_sync_test 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(cmd_sync_test PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(cmd_sync_test 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(cmd_sync_test vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/cmd_sync_test/resources/cube/boards2_vcyc_jpg.jpg b/projects/cmd_sync_test/resources/cube/boards2_vcyc_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2636039e272289c0fba3fa2d88a060b857501248
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/cube/cube.bin b/projects/cmd_sync_test/resources/cube/cube.bin
new file mode 100644
index 0000000000000000000000000000000000000000..3303cd8635848bee18e10ab8754d5e4e7218db92
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/cube/cube.blend b/projects/cmd_sync_test/resources/cube/cube.blend
new file mode 100644
index 0000000000000000000000000000000000000000..62ccb2c742094bcfb5ed194ab905bffae86bfd65
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/cube/cube.blend1 b/projects/cmd_sync_test/resources/cube/cube.blend1
new file mode 100644
index 0000000000000000000000000000000000000000..13f21dcca218d7bc7a07a8a9682b5e1d9e607736
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/cube/cube.glb b/projects/cmd_sync_test/resources/cube/cube.glb
new file mode 100644
index 0000000000000000000000000000000000000000..66a42c65e71dcf375e04cc378256024dd3c7834d
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/cube/cube.gltf b/projects/cmd_sync_test/resources/cube/cube.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..428176144843dd06c78fe1d11a6392a0ea02b22d
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/shaders/compile.bat b/projects/cmd_sync_test/resources/shaders/compile.bat
new file mode 100644
index 0000000000000000000000000000000000000000..516c2f2f78001e1a5d182356e7c3fe82d66a45ee
--- /dev/null
+++ b/projects/cmd_sync_test/resources/shaders/compile.bat
@@ -0,0 +1,5 @@
+%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 shadow.vert -o shadow_vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shadow.frag -o shadow_frag.spv
+pause
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/frag.spv b/projects/cmd_sync_test/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ff3110571871d65ce119dc6c5006e7e67aa53546
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/frag.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/shader.frag b/projects/cmd_sync_test/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..95f1b3319e1ca5c7c34ff94e5e7198819c0233c1
--- /dev/null
+++ b/projects/cmd_sync_test/resources/shaders/shader.frag
@@ -0,0 +1,44 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+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 texture2D  meshTexture;
+layout(set=0, binding=1) uniform sampler    textureSampler;
+layout(set=0, binding=2) uniform sunBuffer {
+    vec3 L; float padding;
+    mat4 lightMatrix;
+};
+layout(set=0, binding=3) uniform texture2D  shadowMap;
+layout(set=0, binding=4) 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(1);
+    vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
+    sun *= shadowTest(passPos);
+    vec3 ambient = vec3(0.1);
+    vec3 albedo = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+	outColor = albedo * (sun + ambient);
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shader.vert b/projects/cmd_sync_test/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0ab82c203806356d0f35dc52c0a6988b286d90d1
--- /dev/null
+++ b/projects/cmd_sync_test/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  = inNormal;
+    passUV      = inUV;
+    passPos     = (model * vec4(inPosition, 1)).xyz;
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shadow.frag b/projects/cmd_sync_test/resources/shaders/shadow.frag
new file mode 100644
index 0000000000000000000000000000000000000000..848f853f556660b4900b5db7fb6fc98d57c1cd5b
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/shaders/shadow.vert b/projects/cmd_sync_test/resources/shaders/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/shaders/shadow_frag.spv b/projects/cmd_sync_test/resources/shaders/shadow_frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6be3bd2518a3b1f234e39aea2503ba86cfb3314b
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/shadow_frag.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/shadow_vert.spv b/projects/cmd_sync_test/resources/shaders/shadow_vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..afaa0824ee9be2c22209d611943c6512587dce24
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/shadow_vert.spv differ
diff --git a/projects/cmd_sync_test/resources/shaders/vert.spv b/projects/cmd_sync_test/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5e514eef5983927316465679af5461f507497130
Binary files /dev/null and b/projects/cmd_sync_test/resources/shaders/vert.spv differ
diff --git a/projects/cmd_sync_test/resources/triangle/Triangle.bin b/projects/cmd_sync_test/resources/triangle/Triangle.bin
new file mode 100644
index 0000000000000000000000000000000000000000..57f26ad96592b64377e6aa93823d96a94e6c5022
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/triangle/Triangle.blend b/projects/cmd_sync_test/resources/triangle/Triangle.blend
new file mode 100644
index 0000000000000000000000000000000000000000..2421dc5e1bb029d73a9ec09cc4530c5196851fd7
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/triangle/Triangle.glb b/projects/cmd_sync_test/resources/triangle/Triangle.glb
new file mode 100644
index 0000000000000000000000000000000000000000..4148620cd6af0dadbc791aa1c52bb5431a40884b
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/resources/triangle/Triangle.gltf b/projects/cmd_sync_test/resources/triangle/Triangle.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..a188e6ee16a5e8486cf307c7bda8cfd99bdbeea6
--- /dev/null
+++ b/projects/cmd_sync_test/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/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9946a35373576d3690d22bf18a03cd8e52b15e56
--- /dev/null
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -0,0 +1,308 @@
+#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, windowWidth, windowHeight);
+    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::Mesh mesh;
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+	int result = vkcv::asset::loadMesh(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.getSwapchainImageFormat()
+	);
+	
+	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::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()) {
+		vkcv::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/src/main.cpp b/projects/first_mesh/src/main.cpp
index 723bc9a5fed150d8be94c4c97ba83cfacccc6d4d..7c32a76dc6f4b2fe320df733e65a54a88e3a42c1 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -8,20 +8,21 @@
 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,
-        800,
-        600,
+		windowWidth,
+		windowHeight,
 		true
 	);
 
-	vkcv::CameraManager cameraManager(window, static_cast<float>(window.getWidth()), static_cast<float>(window.getHeight()));
-
 	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" }
 	);
@@ -58,65 +59,58 @@ 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()
 	);
 	
 	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 });
-
-	//only exemplary code for testing
-	for (int i = 0; i < 1001; i++) {
-		vkcv::ResourcesHandle furtherSets = core.createResourceDescription({ setConfig });
+    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] }));
 	}
-	//end of exemplary code
+	
+	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 trianglePipelineDefinition(
-		triangleShaderProgram,
+	const vkcv::PipelineConfig firstMeshPipelineConfig(
+        firstMeshProgram,
         UINT32_MAX,
         UINT32_MAX,
-		trianglePass,
-		mesh.vertexGroups[0].vertexBuffer.attributes,
-		{ core.getDescriptorSetLayout(set, 0) });
-	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+        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;
 	}
@@ -131,42 +125,72 @@ 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);
+	setWrites.sampledImageWrites    = { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
+	setWrites.samplerWrites         = { vkcv::SamplerDescriptorWrite(1, sampler) };
+	core.writeDescriptorSet(descriptorSet, setWrites);
 
-	auto start = std::chrono::system_clock::now();
-	while (window.isWindowOpen()) {
-        vkcv::Window::pollEvents();
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
-        if(window.getHeight() == 0 || window.getWidth() == 0)
-            continue;
+	const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
 
-		core.beginFrame();
+	vkcv::DescriptorSetUsage    descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+	vkcv::DrawcallInfo          drawcall(renderMesh, { descriptorUsage });
+
+    vkcv::camera::CameraManager cameraManager(window, windowWidth, windowHeight);
+    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()) {
+        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 = 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,
-			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_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/src/main.cpp b/projects/first_triangle/src/main.cpp
index 6c3b2272e32ebdc7844ebd16441f6e17c9429495..62d71d9ce3cab388361ac1163b67281ecc465af5 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -16,8 +16,6 @@ int main(int argc, const char** argv) {
 		false
 	);
 
-	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-
 	vkcv::Core core = vkcv::Core::create(
 		window,
 		applicationName,
@@ -77,9 +75,6 @@ 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());
@@ -93,28 +88,50 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 
+	// Graphics Pipeline
 	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);
 
 	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);
@@ -131,27 +148,60 @@ 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, windowWidth, windowHeight);
+    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, 0, -2));
+
 	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,
-			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);
 	    
 	    core.endFrame();
 	}
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index ef874606ed0f30f33e4b1e0720d4f3455a8e137e..6d494c4ec90726d46039007607464378624f1c75 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -158,7 +158,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		core->submitCommands(
+		core->recordAndSubmitCommands(
 				submitInfo,
 				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
 					const vk::BufferCopy region (
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/Core.cpp b/src/vkcv/Core.cpp
index 27e84ee5cdb062adc0e9f96cdfa7e761e6f24279..44e7111e1f4941ef2f0f8114ac788d7db4a13b5a 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -14,6 +14,9 @@
 #include "ImageManager.hpp"
 #include "DescriptorManager.hpp"
 #include "ImageLayoutTransitions.hpp"
+#include "vkcv/CommandStreamManager.hpp"
+
+#include "vkcv/Logger.hpp"
 
 namespace vkcv
 {
@@ -52,8 +55,8 @@ namespace vkcv
         return m_Context;
     }
 
-	Core::Core(Context &&context, Window &window, const 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> imageViews,
+        const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
             m_Context(std::move(context)),
             m_window(window),
             m_swapchain(swapChain),
@@ -61,20 +64,25 @@ namespace vkcv
             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 = window.e_resize.add( [&](int width, int height) {
-        	m_swapchain.recreateSwapchain();
-        });
+		e_resizeHandle = window.e_resize.add( [&](int width, int height) {
+			m_swapchain.signalSwapchainRecreation();
+		});
+
+		m_swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+		m_swapchainImageLayouts.resize(m_swapchainImages.size(), vk::ImageLayout::eUndefined);
 	}
 
 	Core::~Core() noexcept {
@@ -95,6 +103,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)
     {
@@ -103,17 +118,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) {
-			std::cerr << vk::to_string(acquireResult) << std::endl;
+		if (result != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::ERROR, "%s", vk::to_string(result).c_str());
 			return Result::ERROR;
 		}
 		
@@ -121,7 +142,7 @@ namespace vkcv
 		return Result::SUCCESS;
 	}
 
-	void Core::beginFrame() {
+	bool Core::beginFrame(uint32_t& width, uint32_t& height) {
 		if (m_swapchain.shouldUpdateSwapchain()) {
 			m_Context.getDevice().waitIdle();
 			
@@ -130,87 +151,105 @@ namespace vkcv
 			
 			m_swapchain.updateSwapchain(m_Context, m_window);
 			m_swapchainImageViews = createImageViews(m_Context, m_swapchain);
+			m_swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+
+			m_swapchainImageLayouts.clear();
+			m_swapchainImageLayouts.resize(m_swapchainImages.size(), vk::ImageLayout::eUndefined);
 		}
 		
     	if (acquireSwapchainImage() != Result::SUCCESS) {
-    		std::cerr << "Acquire failed!" << std::endl;
+			vkcv_log(LogLevel::ERROR, "Acquire failed");
     		
     		m_currentSwapchainImageIndex = std::numeric_limits<uint32_t>::max();
     	}
 		
 		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;
+		
+		return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max());
 	}
 
-	void Core::renderMesh(
-		const PassHandle						&renderpassHandle,
-		const PipelineHandle					&pipelineHandle,
-		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::Extent2D& extent = m_swapchain.getExtent();
-		
-		const uint32_t width = extent.width;
-		const uint32_t height = extent.height;
 
-		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
-		const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle);
-		
-		const bool checkForDepthImage = (
-				(!m_DepthImage) ||
-				(width != m_ImageManager->getImageWidth(m_DepthImage)) ||
-				(height != m_ImageManager->getImageHeight(m_DepthImage))
-		);
-		
-		if (checkForDepthImage) {
-			for (const auto &attachment : passConfig.attachments) {
-				if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
-					m_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 vkcv::PipelineConfig pipelineConfig = m_PipelineManager->getPipelineConfig(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 (m_DepthImage) {
-			attachments.push_back(m_ImageManager->getVulkanImageView(m_DepthImage));
+		const vk::ImageView swapchainImageView = m_swapchainImageViews[m_currentSwapchainImageIndex];
+
+		std::vector<vk::ImageView> attachmentsViews;
+		for (const ImageHandle handle : renderTargets) {
+			vk::ImageView targetHandle;
+			const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
+			if (handle.isSwapchainImage()) {
+				recordSwapchainImageLayoutTransition(cmdBuffer, vk::ImageLayout::eColorAttachmentOptimal);
+				targetHandle = m_swapchainImageViews[m_currentSwapchainImageIndex];
+			}
+			else {
+				targetHandle = m_ImageManager->getVulkanImageView(handle);
+				const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle));
+				const vk::ImageLayout targetLayout = 
+					isDepthFormat ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal;
+				m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer);
+			}
+			attachmentsViews.push_back(targetHandle);
 		}
 		
 		vk::Framebuffer framebuffer = nullptr;
-        const vk::FramebufferCreateInfo createInfo({},
-                                                   renderpass,
-                                                   static_cast<uint32_t>(attachments.size()),
-                                                   attachments.data(),
-                                                   width,
-                                                   height,
-                                                   1);
+        const vk::FramebufferCreateInfo createInfo(
+            {},
+            renderpass,
+            static_cast<uint32_t>(attachmentsViews.size()),
+            attachmentsViews.data(),
+            width,
+            height,
+            1);
         if(m_Context.m_Device.createFramebuffer(&createInfo, nullptr, &framebuffer) != vk::Result::eSuccess)
         {
-            std::cout << "FAILED TO CREATE TEMPORARY FRAMEBUFFER!" << std::endl;
+			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);
+            static_cast<float>(width), static_cast<float>(height),
+            0.0f, 1.0f);
 
         vk::Rect2D dynamicScissor({0, 0}, {width, height});
 
@@ -227,7 +266,7 @@ namespace vkcv
                 if (attachment.load_operation == AttachmentOperation::CLEAR) {
                     float clear = 0.0f;
 
-                    if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
+                    if (isDepthFormat(attachment.format)) {
                         clear = 1.0f;
                     }
 
@@ -246,35 +285,60 @@ namespace vkcv
 
             cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-            if(pipelineConfig.m_Height == UINT32_MAX && pipelineConfig.m_Width == UINT32_MAX)
+            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));
-            }
-
-            if (resourceHandle) {
-                const vk::DescriptorSet descriptorSet = m_DescriptorManager->getDescriptorSet(resourceHandle, resourceDescriptorSetIndex);
-                cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, descriptorSet, nullptr);
+            for (int i = 0; i < drawcalls.size(); i++) {
+                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
             }
 
-            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();
         };
 
-        auto finishFunction = [&]()
+        auto finishFunction = [framebuffer, this]()
         {
             m_Context.m_Device.destroy(framebuffer);
         };
 
-		submitCommands(submitInfo, submitFunction, finishFunction);
+		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 (pushConstantData.sizePerDrawcall > 0) {
+				cmdBuffer.pushConstants(
+					pipelineLayout,
+					vk::ShaderStageFlagBits::eCompute,
+					0,
+					pushConstantData.sizePerDrawcall,
+					pushConstantData.data);
+			}
+			cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]);
+		};
+
+		recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr);
 	}
 
 	void Core::endFrame() {
@@ -289,16 +353,22 @@ namespace vkcv
 			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());
 		}
 	}
 
@@ -306,7 +376,10 @@ namespace vkcv
 		return m_swapchain.getSwapchainFormat();
 	}
 	
-	void Core::submitCommands(const SubmitInfo &submitInfo, const RecordCommandFunction& record, const FinishCommandFunction& finish)
+	void Core::recordAndSubmitCommands(
+		const SubmitInfo &submitInfo, 
+		const RecordCommandFunction &record, 
+		const FinishCommandFunction &finish)
 	{
 		const vk::Device& device = m_Context.getDevice();
 
@@ -329,34 +402,60 @@ 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);
-    }
-    
+		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
+	}
+
 	Image Core::createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
 	{
     	return Image::create(m_ImageManager.get(), format, width, height, depth);
 	}
 
-    ResourcesHandle Core::createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets)
+    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::getDescriptorSetLayout(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::createImageViews( Context &context, SwapChain& swapChain){
@@ -380,11 +479,32 @@ namespace vkcv
                     vk::ImageViewType::e2D,
                     swapChain.getSwapchainFormat(),
                     componentMapping,
-                    subResourceRange
-            );
+                    subResourceRange);
 
             imageViews.push_back(context.getDevice().createImageView(imageViewCreateInfo));
         }
         return imageViews;
     }
+
+	void Core::recordSwapchainImageLayoutTransition(vk::CommandBuffer cmdBuffer, vk::ImageLayout newLayout) {
+		auto& imageLayout = m_swapchainImageLayouts[m_currentSwapchainImageIndex];
+		const auto transitionBarrier = createSwapchainImageLayoutTransitionBarrier(
+			m_swapchainImages[m_currentSwapchainImageIndex],
+			imageLayout,
+			newLayout);
+		recordImageBarrier(cmdBuffer, transitionBarrier);
+		imageLayout = newLayout;
+	}
+
+	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle handle) {
+		m_CommandStreamManager->recordCommandsToStream(handle, [&](vk::CommandBuffer cmdBuffer) {
+			recordSwapchainImageLayoutTransition(cmdBuffer, vk::ImageLayout::ePresentSrcKHR);
+		});
+	}
+
+	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);
+	}
 }
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 2ecb23bd39cd4656c12d1816ece3db404ecdc248..f591daf90b47b57a758b2b24c7fa87b5c33e3c46 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -1,12 +1,9 @@
 #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 }
     {
@@ -29,67 +26,63 @@ namespace vkcv
 
     DescriptorManager::~DescriptorManager() noexcept
     {
-        for (uint64_t id = 0; id < m_ResourceDescriptions.size(); id++) {
-			destroyResourceDescriptionById(id);
+        for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) {
+			destroyDescriptorSetById(id);
         }
+		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_Pools.back(), 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)
         {
 			//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, vk_sets.data());
+				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();
+				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
+						 vk::to_string(result).c_str());
+				
+				m_Device.destroy(set.layout);
+				return DescriptorSetHandle();
 			}
         };
 
-        const uint64_t id = m_ResourceDescriptions.size();
-        m_ResourceDescriptions.emplace_back(vk_sets, vk_setLayouts);
-        return ResourcesHandle(id, [&](uint64_t id) { destroyResourceDescriptionById(id); });
+        const uint64_t id = m_DescriptorSets.size();
+
+        m_DescriptorSets.push_back(set);
+        return DescriptorSetHandle(id, [&](uint64_t id) { destroyDescriptorSetById(id); });
     }
     
     struct WriteDescriptorSetInfo {
@@ -99,15 +92,14 @@ namespace vkcv
 		vk::DescriptorType type;
     };
 
-	void DescriptorManager::writeResourceDescription(
-		const 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;
@@ -230,12 +222,8 @@ namespace vkcv
 		m_Device.updateDescriptorSets(vulkanWrites, nullptr);
 	}
 
-	vk::DescriptorSet DescriptorManager::getDescriptorSet(const ResourcesHandle &handle, size_t index) const {
-		return m_ResourceDescriptions[handle.getId()].descriptorSets[index];
-	}
-
-	vk::DescriptorSetLayout DescriptorManager::getDescriptorSetLayout(const ResourcesHandle &handle, size_t index) const {
-		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) {
@@ -252,7 +240,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;
         }
     }
@@ -277,25 +265,25 @@ namespace vkcv
         }
     }
     
-    void DescriptorManager::destroyResourceDescriptionById(uint64_t id) {
-		if (id >= m_ResourceDescriptions.size()) {
+    void DescriptorManager::destroyDescriptorSetById(uint64_t id) {
+		if (id >= m_DescriptorSets.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid id");
 			return;
 		}
 		
-		auto& resourceDescription = m_ResourceDescriptions[id];
-	
-		for(const auto &layout : resourceDescription.descriptorSetLayouts) {
-			m_Device.destroyDescriptorSetLayout(layout);
+		auto& set = m_DescriptorSets[id];
+		if (set.layout) {
+			m_Device.destroyDescriptorSetLayout(set.layout);
+			set.layout = nullptr;
 		}
-	
-		resourceDescription.descriptorSetLayouts.clear();
+		// FIXME: descriptor set itself not destroyed
 	}
 
 	vk::DescriptorPool DescriptorManager::allocateDescriptorPool() {
 		vk::DescriptorPool pool;
 		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess)
 		{
-			std::cout << "FAILED TO ALLOCATE DESCRIPTOR POOL." << std::endl;
+			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
 			pool = nullptr;
 		};
 		m_Pools.push_back(pool);
diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp
index 22042c703256055e3852b7a4729faad39b5d0dbb..d18be64f3b069af68cecce68f6fa623c81f8dfa4 100644
--- a/src/vkcv/DescriptorManager.hpp
+++ b/src/vkcv/DescriptorManager.hpp
@@ -21,52 +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(
-			const ResourcesHandle	&handle,
-			size_t					setIndex,
+		void writeDescriptorSet(
+			const DescriptorSetHandle	&handle,
 			const DescriptorWrites  &writes,
 			const ImageManager      &imageManager,
 			const BufferManager     &bufferManager,
 			const SamplerManager    &samplerManager);
 
 		[[nodiscard]]
-		vk::DescriptorSet		getDescriptorSet(const ResourcesHandle &handle, size_t index) const;
-		[[nodiscard]]
-		vk::DescriptorSetLayout getDescriptorSetLayout(const ResourcesHandle &handle, size_t index) const;
+		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
 
 	private:
-		vk::Device			m_Device;
+		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;
+        std::vector<DescriptorSet> m_DescriptorSets;
 		
 		/**
 		* Converts the flags of the descriptor types from VulkanCV (vkcv) to Vulkan (vk).
@@ -85,7 +62,7 @@ namespace vkcv
 		* Destroys a specific resource description
 		* @param[in] the handle id of the respective resource description
 		*/
-		void destroyResourceDescriptionById(uint64_t id);
+		void destroyDescriptorSetById(uint64_t id);
 
 		/**
 		* creates a descriptor pool based on the poolSizes and poolInfo defined in the constructor
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85b6eeb5fa413223b7b7f10f77b868252912041b
--- /dev/null
+++ b/src/vkcv/DrawcallRecording.cpp
@@ -0,0 +1,41 @@
+#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);
+        }
+
+        cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
+
+        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);
+
+        cmdBuffer.drawIndexed(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 33e0cdba7f30fe6c3576803de1d22f912c935dd0..020489418c8e2db6ce2062d6fd20f06f90a05c37 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -93,7 +93,7 @@ namespace vkcv {
 	}
 	
 	ImageHandle ImageHandle::createSwapchainImageHandle(const HandleDestroyFunction &destroy) {
-		return ImageHandle(UINT64_MAX - 1, destroy);
+		return ImageHandle(uint64_t(UINT64_MAX - 1), destroy);
 	}
 	
 }
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index 9ce5c25a01957ca43c75c14f6b37a7a3e82feee4..f861daeb1cd7de9697e2f649de444666b8b0e63c 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -8,13 +8,24 @@
 
 namespace vkcv{
 	
+	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)
 	{
-		return Image(manager, manager->createImage(width, height, depth, format), format);
+		return Image(manager, manager->createImage(width, height, depth, format));
 	}
 	
 	vk::Format Image::getFormat() const {
-		return m_format;
+		return m_manager->getImageFormat(m_handle);
 	}
 	
 	uint32_t Image::getWidth() const {
@@ -28,15 +39,10 @@ namespace vkcv{
 	uint32_t Image::getDepth() const {
 		return m_manager->getImageDepth(m_handle);
 	}
-	
-	vk::ImageLayout Image::getLayout() const {
-		return m_layout;
-	}
 
 	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 {
@@ -47,12 +53,9 @@ namespace vkcv{
 		m_manager->fillImage(m_handle, data, size);
 	}
 	
-	Image::Image(ImageManager* manager, const ImageHandle& handle, vk::Format format) :
+	Image::Image(ImageManager* manager, const ImageHandle& handle) :
 		m_manager(manager),
-		m_handle(handle),
-		m_format(format),
-		m_layout(vk::ImageLayout::eUndefined)
-	{
-	}
+		m_handle(handle)
+	{}
 
 }
diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp
index 0b08819489c41c5cde3ceddbb0629a5d2ae3cd30..cb0f90a79d188cd80a5744d8c6ad7718e542d473 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_levels,
+			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 4b46b5fc422e1666049b22d422a16fec10fe08aa..1e3d19d02d7e86546d142bb64440364407e81824 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -5,11 +5,35 @@
  */
 #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,
+		vk::ImageView       view,
+		uint32_t            width,
+		uint32_t            height,
+		uint32_t            depth,
+		vk::Format          format,
+		uint32_t            layers,
+		uint32_t            levels)
+		:
+		m_handle(handle),
+		m_memory(memory),
+		m_view(view),
+		m_width(width),
+		m_height(height),
+		m_depth(depth),
+		m_format(format),
+		m_layers(layers),
+		m_levels(levels)
+	{}
+
 	/**
 	 * @brief searches memory type index for image allocation, combines requirements of image and application
 	 * @param physicalMemoryProperties Memory Properties of physical device
@@ -89,6 +113,11 @@ namespace vkcv {
 			}
 		}
 		
+		if (isDepthFormat) {
+			imageType = vk::ImageType::e2D;
+			imageViewType = vk::ImageViewType::e2D;
+		}
+		
 		vk::ImageTiling imageTiling = vk::ImageTiling::eOptimal;
 		
 		if (!formatProperties.optimalTilingFeatures) {
@@ -166,7 +195,7 @@ namespace vkcv {
 		vk::ImageView view = device.createImageView(imageViewCreateInfo);
 		
 		const uint64_t id = m_images.size();
-		m_images.push_back({ image, memory, view, width, height, depth, format, arrayLayers, mipLevels });
+		m_images.push_back(Image(image, memory, view, width, height, depth, format, arrayLayers, mipLevels));
 		return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); });
 	}
 	
@@ -178,6 +207,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
@@ -190,6 +220,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
@@ -202,6 +233,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
@@ -210,84 +242,53 @@ namespace vkcv {
 		return image.m_view;
 	}
 	
-	void ImageManager::switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) {
+	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			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
-		);
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
 		
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		
-		m_core->submitCommands(
+		m_core->recordAndSubmitCommands(
 			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();
+
+		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return;
+		}
+
+		auto& image = m_images[id];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
+		recordImageBarrier(cmdBuffer, transitionBarrier);
+		image.m_layout = newLayout;
 	}
 	
 	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
@@ -295,16 +296,15 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		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
 		const size_t image_size = (
@@ -324,7 +324,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		m_core->submitCommands(
+		m_core->recordAndSubmitCommands(
 				submitInfo,
 				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
 					vk::ImageAspectFlags aspectFlags;
@@ -358,9 +358,8 @@ namespace vkcv {
 					);
 				},
 				[&]() {
-					switchImageLayout(
+					switchImageLayoutImmediate(
 							handle,
-							vk::ImageLayout::eTransferDstOptimal,
 							vk::ImageLayout::eShaderReadOnlyOptimal
 					);
 				}
@@ -371,6 +370,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
@@ -383,6 +383,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
@@ -395,6 +396,7 @@ namespace vkcv {
 		const uint64_t id = handle.getId();
 		
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
@@ -406,6 +408,7 @@ namespace vkcv {
 	void ImageManager::destroyImageById(uint64_t id)
 	{
 		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 		
@@ -429,5 +432,16 @@ namespace vkcv {
 		}
 	}
 
+	vk::Format ImageManager::getImageFormat(const ImageHandle& handle) const {
+
+		const uint64_t id = handle.getId();
+
+		if (id >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Invalid handle");
+			return vk::Format::eUndefined;
+		}
+
+		return m_images[id].m_format;
+	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index f1bd9d0be774478f62098cbc2833b5c268be2eea..b9fccb25ec16bc1fd9569ab1a94627bd7ff06b18 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -11,23 +11,38 @@
 #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;
+			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::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,
+				vk::ImageView       view,
+				uint32_t            width,
+				uint32_t            height,
+				uint32_t            depth,
+				vk::Format          format,
+				uint32_t            layers,
+				uint32_t            levels);
 		};
+	private:
 		
 		Core* m_core;
 		BufferManager& m_bufferManager;
@@ -64,8 +79,13 @@ namespace vkcv {
 		
 		[[nodiscard]]
 		vk::ImageView getVulkanImageView(const ImageHandle& handle) const;
-		
-		void switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout);
+
+		void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout);
+		void recordImageLayoutTransition(
+			const ImageHandle& handle, 
+			vk::ImageLayout newLayout, 
+			vk::CommandBuffer cmdBuffer);
+
 		void fillImage(const ImageHandle& handle, void* data, size_t size);
 		
 		[[nodiscard]]
@@ -77,5 +97,7 @@ namespace vkcv {
 		[[nodiscard]]
 		uint32_t getImageDepth(const ImageHandle& handle) const;
 		
+		[[nodiscard]]
+		vk::Format getImageFormat(const ImageHandle& handle) const;
 	};
 }
\ 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 8b59495ae7eaa68b6f42ed2a4786ae688e4a6375..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
 {
@@ -73,63 +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);
-	
+
         const uint64_t id = m_Passes.size();
-		m_Passes.push_back({ renderPass, config });
-		return PassHandle(id, [&](uint64_t id) { destroyPassById(id); });
+        m_Passes.push_back({ renderPass, config });
+        return PassHandle(id, [&](uint64_t id) { destroyPassById(id); });
     }
 
     vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
index d317258470bde76e8b8ba8e1f9bc684ea469b6c0..3bd2a68cb86f167afecc551dbd664dee8a63eb08 100644
--- a/src/vkcv/PipelineConfig.cpp
+++ b/src/vkcv/PipelineConfig.cpp
@@ -9,18 +9,20 @@
 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)
+		const ShaderProgram&                        shaderProgram,
+		uint32_t                                    width,
+		uint32_t                                    height,
+		const PassHandle                            &passHandle,
+		const VertexLayout                          &vertexLayout,
+		const std::vector<vk::DescriptorSetLayout>  &descriptorLayouts,
+		bool                                        useDynamicViewport)
 		:
 		m_ShaderProgram(shaderProgram),
 		m_Height(height),
 		m_Width(width),
 		m_PassHandle(passHandle),
-		m_vertexAttributes(vertexAttributes),
-		m_descriptorLayouts(descriptorLayouts)
+		m_VertexLayout(vertexLayout),
+		m_DescriptorLayouts(descriptorLayouts),
+		m_UseDynamicViewport(useDynamicViewport)
 		{}
 }
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 16b08b7a5127769a19b7e0abe47b61f58406bafe..81b7525b160374915b1918c30870b05e619a30a4 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -1,4 +1,6 @@
 #include "PipelineManager.hpp"
+#include "vkcv/Image.hpp"
+#include "vkcv/Logger.hpp"
 
 namespace vkcv
 {
@@ -17,17 +19,27 @@ namespace vkcv
     }
 
 	// 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;
 		}
 	}
 
@@ -39,7 +51,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();
         }
 
@@ -82,24 +94,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);
-
-			//FIXME: hoping that order is the same and compatible: add explicit mapping and validation
-			const VertexAttribute attribute = config.m_vertexAttributes[i];
-
-            vertexAttributeDescriptions.emplace_back(location, binding, vertexFormatToVulkanFormat(attachment.format), 0);
-			vertexBindingDescriptions.emplace_back(vk::VertexInputBindingDescription(
-				binding,
-				attribute.stride + getFormatSize(attachment.format),
-				vk::VertexInputRate::eVertex));
+        // 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);
+
+            // 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);
+
+            }
         }
 
         // Handover Containers to PipelineVertexInputStateCreateIngo Struct
@@ -173,14 +185,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)
         {
@@ -207,14 +220,14 @@ 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;
 			}
 		}
 
 		std::vector<vk::DynamicState> dynamicStates = {};
-		if(config.m_Width == UINT32_MAX && config.m_Height == UINT32_MAX)
+		if(config.m_UseDynamicViewport)
         {
 		    dynamicStates.push_back(vk::DynamicState::eViewport);
 		    dynamicStates.push_back(vk::DynamicState::eScissor);
@@ -313,4 +326,65 @@ namespace vkcv
         return m_Configs.at(id);
     }
 
+    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 });
+
+        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 e243151f7248c07fa0287bb2eaf698e5080f7f61..634f5f4e6464532306e35fd10d9a1623df6ace16 100644
--- a/src/vkcv/PipelineManager.hpp
+++ b/src/vkcv/PipelineManager.hpp
@@ -21,7 +21,9 @@ namespace vkcv
         std::vector<PipelineConfig> m_Configs;
         
         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
@@ -35,6 +37,10 @@ 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;
 
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/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 5185b8b402eae5cd514689ba51a06e1a437271bf..aa945bb18e7cf04513b41510f1ea993a02e1f46d 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 {
     /**
@@ -17,7 +18,7 @@ namespace vkcv {
 	{
 		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();
@@ -27,18 +28,18 @@ namespace vkcv {
         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 +47,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 +61,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 +108,114 @@ 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.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.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);
+        }
 
-				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_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);
+        }
+
+        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);
+
+        }
+
+        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);
+            }
+        }
 
-			m_VertexLayout = VertexLayout(inputVec);
+        //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/SwapChain.cpp b/src/vkcv/SwapChain.cpp
index 39c310de60db2c3678749f142c926a26ac127b58..b787536b66cfd802dfd435a773a584c875eeb391 100644
--- a/src/vkcv/SwapChain.cpp
+++ b/src/vkcv/SwapChain.cpp
@@ -244,7 +244,7 @@ namespace vkcv
 		m_Extent = extent2D;
     }
 
-    void SwapChain::recreateSwapchain() {
+    void SwapChain::signalSwapchainRecreation() {
 		m_RecreationRequired = true;
     }
     
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