From 74825622bcb853d53b7137b92b8760160252ea81 Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Fri, 8 Jul 2022 00:53:50 +0200
Subject: [PATCH] Updated managers to be consistent using a template (WIP)

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 config/Sources.cmake                          |  17 +-
 include/vkcv/Core.hpp                         |  41 +-
 include/vkcv/DescriptorBinding.hpp            |  31 ++
 include/vkcv/DescriptorConfig.hpp             |  96 ----
 include/vkcv/DescriptorTypes.hpp              |  57 ++
 include/vkcv/DrawcallRecording.hpp            |   2 +-
 include/vkcv/Handles.hpp                      |  18 +-
 include/vkcv/ShaderProgram.hpp                |   2 +-
 .../rtx_ambient_occlusion/src/RTX/RTX.cpp     |   2 +-
 src/vkcv/BufferManager.cpp                    | 187 ++-----
 src/vkcv/BufferManager.hpp                    |  54 +-
 src/vkcv/ComputePipelineManager.cpp           | 131 ++---
 src/vkcv/ComputePipelineManager.hpp           |  60 +-
 src/vkcv/Core.cpp                             | 123 ++---
 ...riptorConfig.cpp => DescriptorBinding.cpp} |   7 +-
 src/vkcv/DescriptorManager.cpp                | 399 --------------
 src/vkcv/DescriptorManager.hpp                | 120 ----
 src/vkcv/DescriptorSetLayoutManager.cpp       | 109 ++++
 src/vkcv/DescriptorSetLayoutManager.hpp       |  70 +++
 src/vkcv/DescriptorSetManager.cpp             | 314 +++++++++++
 src/vkcv/DescriptorSetManager.hpp             | 106 ++++
 src/vkcv/GraphicsPipelineManager.cpp          | 178 +++---
 src/vkcv/GraphicsPipelineManager.hpp          |  54 +-
 src/vkcv/HandleManager.hpp                    | 116 ++++
 src/vkcv/ImageConfig.cpp                      |   2 +
 src/vkcv/ImageManager.cpp                     | 512 +++++++-----------
 src/vkcv/ImageManager.hpp                     | 127 +++--
 src/vkcv/PassManager.cpp                      |  78 +--
 src/vkcv/PassManager.hpp                      |  49 +-
 29 files changed, 1501 insertions(+), 1561 deletions(-)
 create mode 100644 include/vkcv/DescriptorBinding.hpp
 delete mode 100644 include/vkcv/DescriptorConfig.hpp
 create mode 100644 include/vkcv/DescriptorTypes.hpp
 rename src/vkcv/{DescriptorConfig.cpp => DescriptorBinding.cpp} (86%)
 delete mode 100644 src/vkcv/DescriptorManager.cpp
 delete mode 100644 src/vkcv/DescriptorManager.hpp
 create mode 100644 src/vkcv/DescriptorSetLayoutManager.cpp
 create mode 100644 src/vkcv/DescriptorSetLayoutManager.hpp
 create mode 100644 src/vkcv/DescriptorSetManager.cpp
 create mode 100644 src/vkcv/DescriptorSetManager.hpp
 create mode 100644 src/vkcv/HandleManager.hpp

diff --git a/config/Sources.cmake b/config/Sources.cmake
index a50be825..f2c69d35 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -23,6 +23,8 @@ set(vkcv_sources
 
 		${vkcv_include}/vkcv/Handles.hpp
 		${vkcv_source}/vkcv/Handles.cpp
+		
+		${vkcv_source}/vkcv/HandleManager.hpp
 
 		${vkcv_include}/vkcv/Window.hpp
 		${vkcv_source}/vkcv/Window.cpp
@@ -85,16 +87,21 @@ set(vkcv_sources
 		
 		${vkcv_include}/vkcv/TypeGuard.hpp
 		${vkcv_source}/vkcv/TypeGuard.cpp
+		
+		${vkcv_include}/vkcv/DescriptorTypes.hpp
 
-		${vkcv_source}/vkcv/DescriptorManager.hpp
-		${vkcv_source}/vkcv/DescriptorManager.cpp
-
-		${vkcv_include}/vkcv/DescriptorConfig.hpp
-		${vkcv_source}/vkcv/DescriptorConfig.cpp
+		${vkcv_include}/vkcv/DescriptorBinding.hpp
+		${vkcv_source}/vkcv/DescriptorBinding.cpp
 		
 		${vkcv_include}/vkcv/DescriptorWrites.hpp
 		${vkcv_source}/vkcv/DescriptorWrites.cpp
 		
+		${vkcv_source}/vkcv/DescriptorSetLayoutManager.hpp
+		${vkcv_source}/vkcv/DescriptorSetLayoutManager.cpp
+		
+		${vkcv_source}/vkcv/DescriptorSetManager.hpp
+		${vkcv_source}/vkcv/DescriptorSetManager.cpp
+		
 		${vkcv_source}/vkcv/SamplerManager.hpp
 		${vkcv_source}/vkcv/SamplerManager.cpp
 
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index b93f7042..bf5cf40f 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -22,7 +22,6 @@
 #include "CommandResources.hpp"
 #include "SyncResources.hpp"
 #include "Result.hpp"
-#include "vkcv/DescriptorConfig.hpp"
 #include "Sampler.hpp"
 #include "DescriptorWrites.hpp"
 #include "Event.hpp"
@@ -39,7 +38,8 @@ namespace vkcv
     class PassManager;
     class GraphicsPipelineManager;
     class ComputePipelineManager;
-    class DescriptorManager;
+    class DescriptorSetLayoutManager;
+	class DescriptorSetManager;
     class BufferManager;
     class SamplerManager;
     class ImageManager;
@@ -79,16 +79,17 @@ namespace vkcv
 
         Context m_Context;
 
-        std::unique_ptr<PassManager>             m_PassManager;
-        std::unique_ptr<GraphicsPipelineManager> m_PipelineManager;
-        std::unique_ptr<ComputePipelineManager>  m_ComputePipelineManager;
-        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;
-        std::unique_ptr<WindowManager>           m_WindowManager;
-        std::unique_ptr<SwapchainManager>        m_SwapchainManager;
+        std::unique_ptr<PassManager>             	m_PassManager;
+        std::unique_ptr<GraphicsPipelineManager> 	m_GraphicsPipelineManager;
+        std::unique_ptr<ComputePipelineManager>  	m_ComputePipelineManager;
+        std::unique_ptr<DescriptorSetLayoutManager> m_DescriptorSetLayoutManager;
+		std::unique_ptr<DescriptorSetManager>       m_DescriptorSetManager;
+        std::unique_ptr<BufferManager>           	m_BufferManager;
+        std::unique_ptr<SamplerManager>          	m_SamplerManager;
+        std::unique_ptr<ImageManager>            	m_ImageManager;
+        std::unique_ptr<CommandStreamManager>    	m_CommandStreamManager;
+        std::unique_ptr<WindowManager>           	m_WindowManager;
+        std::unique_ptr<SwapchainManager>        	m_SwapchainManager;
 
 		CommandResources    m_CommandResources;
 		SyncResources       m_SyncResources;
@@ -475,14 +476,6 @@ namespace vkcv
 		 */
 		[[nodiscard]]
 		DescriptorSetLayoutHandle createDescriptorSetLayout(const DescriptorBindings &bindings);
-		
-		/**
-		 * @brief Returns the descriptor set layout of a descriptor set layout handle.
-		 *
-		 * @param[in] handle Descriptor set layout handle
-		 * @return Descriptor set layout
-		 */
-		DescriptorSetLayout getDescriptorSetLayout(const DescriptorSetLayoutHandle handle) const;
 
 		/**
 		 * @brief Creates a new descriptor set
@@ -502,14 +495,6 @@ namespace vkcv
 		*/
 		void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites& writes);
 
-		/**
-		 * @brief Returns information about a descriptor set
-		 * 
-		 * @param handle Handle of the descriptor set
-		 * @return Struct containing the descriptor set's vulkan handle, layout handle and descriptor pool index
-		*/
-		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
-
 
 		/**
 		 * @brief Start recording command buffers and increment frame index
diff --git a/include/vkcv/DescriptorBinding.hpp b/include/vkcv/DescriptorBinding.hpp
new file mode 100644
index 00000000..c0a8a670
--- /dev/null
+++ b/include/vkcv/DescriptorBinding.hpp
@@ -0,0 +1,31 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Vanessa Karolek
+ * @file vkcv/DescriptorConfig.hpp
+ * @brief Structures to handle descriptor bindings.
+ */
+
+#include <unordered_map>
+
+#include "DescriptorTypes.hpp"
+#include "ShaderStage.hpp"
+
+namespace vkcv {
+	
+	/**
+	 * @brief Structure to store details from a descriptor binding.
+	 */
+	struct DescriptorBinding {
+		uint32_t bindingID;
+		DescriptorType descriptorType;
+		uint32_t descriptorCount;
+		ShaderStages shaderStages;
+		bool variableCount;
+		bool partialBinding;
+		
+		bool operator ==(const DescriptorBinding &other) const;
+	};
+	
+	typedef std::unordered_map<uint32_t, DescriptorBinding> DescriptorBindings;
+	
+}
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
deleted file mode 100644
index 8e4f3df2..00000000
--- a/include/vkcv/DescriptorConfig.hpp
+++ /dev/null
@@ -1,96 +0,0 @@
-#pragma once
-/**
- * @authors Artur Wasmut, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Vanessa Karolek
- * @file vkcv/DescriptorConfig.hpp
- * @brief Structures to handle descriptor types and bindings.
- */
-
-#include <unordered_map>
-
-#include "Handles.hpp"
-#include "ShaderStage.hpp"
-#include "Logger.hpp"
-
-namespace vkcv
-{
-
-	/**
-	 * @brief Enum class to specify the type of a descriptor set binding.
-	 */
-    enum class DescriptorType {
-        UNIFORM_BUFFER,
-        STORAGE_BUFFER,
-        SAMPLER,
-        IMAGE_SAMPLED,
-		IMAGE_STORAGE,
-        UNIFORM_BUFFER_DYNAMIC,
-        STORAGE_BUFFER_DYNAMIC,
-        ACCELERATION_STRUCTURE_KHR
-    };
-
-    /**
-     * @brief Converts the descriptor type from the frameworks enumeration
-     * to the Vulkan type specifier.
-     *
-     * @param[in] type Descriptor type
-     * @return Vulkan descriptor type
-     */
-    constexpr vk::DescriptorType getVkDescriptorType(DescriptorType type) noexcept {
-        switch (type)
-        {
-            case DescriptorType::UNIFORM_BUFFER:
-                return vk::DescriptorType::eUniformBuffer;
-            case DescriptorType::UNIFORM_BUFFER_DYNAMIC:
-                return vk::DescriptorType::eUniformBufferDynamic;
-            case DescriptorType::STORAGE_BUFFER:
-                return vk::DescriptorType::eStorageBuffer;
-            case DescriptorType::STORAGE_BUFFER_DYNAMIC:
-                return vk::DescriptorType::eStorageBufferDynamic;
-            case DescriptorType::SAMPLER:
-                return vk::DescriptorType::eSampler;
-            case DescriptorType::IMAGE_SAMPLED:
-                return vk::DescriptorType::eSampledImage;
-            case DescriptorType::IMAGE_STORAGE:
-                return vk::DescriptorType::eStorageImage;
-            case DescriptorType::ACCELERATION_STRUCTURE_KHR:
-                return vk::DescriptorType::eAccelerationStructureKHR;
-            default:
-                return vk::DescriptorType::eMutableVALVE;
-        }
-    }
-	
-	/**
-	 * @brief Structure to store details from a descriptor binding.
-	 */
-    struct DescriptorBinding {
-        uint32_t bindingID;
-        DescriptorType descriptorType;
-        uint32_t descriptorCount;
-        ShaderStages shaderStages;
-        bool variableCount;
-		bool partialBinding;
-
-        bool operator ==(const DescriptorBinding &other) const;
-    };
-    
-    typedef std::unordered_map<uint32_t, DescriptorBinding> DescriptorBindings;
-	
-	/**
-	 * @brief Structure to store details about a descriptor set layout.
-	 */
-    struct DescriptorSetLayout {
-        vk::DescriptorSetLayout vulkanHandle;
-        DescriptorBindings descriptorBindings;
-        size_t layoutUsageCount;
-    };
-	
-	/**
-	 * @brief Structure to store details about a descriptor set.
-	 */
-    struct DescriptorSet {
-        vk::DescriptorSet vulkanHandle;
-        DescriptorSetLayoutHandle setLayoutHandle;
-        size_t poolIndex;
-    };
-	
-}
diff --git a/include/vkcv/DescriptorTypes.hpp b/include/vkcv/DescriptorTypes.hpp
new file mode 100644
index 00000000..eaa739d6
--- /dev/null
+++ b/include/vkcv/DescriptorTypes.hpp
@@ -0,0 +1,57 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Vanessa Karolek
+ * @file vkcv/DescriptorConfig.hpp
+ * @brief Enum classes to handle descriptor types.
+ */
+ 
+#include <vulkan/vulkan.hpp>
+ 
+namespace vkcv {
+	
+	/**
+	 * @brief Enum class to specify the type of a descriptor set binding.
+	 */
+	enum class DescriptorType {
+		UNIFORM_BUFFER,
+		STORAGE_BUFFER,
+		SAMPLER,
+		IMAGE_SAMPLED,
+		IMAGE_STORAGE,
+		UNIFORM_BUFFER_DYNAMIC,
+		STORAGE_BUFFER_DYNAMIC,
+		ACCELERATION_STRUCTURE_KHR
+	};
+	
+	/**
+     * @brief Converts the descriptor type from the frameworks enumeration
+     * to the Vulkan type specifier.
+     *
+     * @param[in] type Descriptor type
+     * @return Vulkan descriptor type
+     */
+	constexpr vk::DescriptorType getVkDescriptorType(DescriptorType type) noexcept {
+		switch (type)
+		{
+			case DescriptorType::UNIFORM_BUFFER:
+				return vk::DescriptorType::eUniformBuffer;
+			case DescriptorType::UNIFORM_BUFFER_DYNAMIC:
+				return vk::DescriptorType::eUniformBufferDynamic;
+			case DescriptorType::STORAGE_BUFFER:
+				return vk::DescriptorType::eStorageBuffer;
+			case DescriptorType::STORAGE_BUFFER_DYNAMIC:
+				return vk::DescriptorType::eStorageBufferDynamic;
+			case DescriptorType::SAMPLER:
+				return vk::DescriptorType::eSampler;
+			case DescriptorType::IMAGE_SAMPLED:
+				return vk::DescriptorType::eSampledImage;
+			case DescriptorType::IMAGE_STORAGE:
+				return vk::DescriptorType::eStorageImage;
+			case DescriptorType::ACCELERATION_STRUCTURE_KHR:
+				return vk::DescriptorType::eAccelerationStructureKHR;
+			default:
+				return vk::DescriptorType::eMutableVALVE;
+		}
+	}
+
+}
diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
index 597774ee..217614c1 100644
--- a/include/vkcv/DrawcallRecording.hpp
+++ b/include/vkcv/DrawcallRecording.hpp
@@ -5,10 +5,10 @@
  * @brief Structures and functions to record drawcalls.
  */
 
+#include <vector>
 #include <vulkan/vulkan.hpp>
 
 #include "Handles.hpp"
-#include "DescriptorConfig.hpp"
 #include "PushConstants.hpp"
 
 namespace vkcv {
diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp
index dea17938..52ccb7a8 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -130,23 +130,23 @@ namespace vkcv
     private:
         using Handle::Handle;
     };
-
-    /**
-     * @brief Handle class for descriptor sets.
+	
+	/**
+     * @brief Handle class for descriptor set layouts.
      */
-	class DescriptorSetHandle : public Handle {
-		friend class DescriptorManager;
+	class DescriptorSetLayoutHandle : public Handle {
+		friend class DescriptorSetLayoutManager;
 	private:
 		using Handle::Handle;
 	};
 
     /**
-     * @brief Handle class for descriptor set layouts.
+     * @brief Handle class for descriptor sets.
      */
-	class DescriptorSetLayoutHandle : public Handle {
-	    friend class DescriptorManager;
+	class DescriptorSetHandle : public Handle {
+		friend class DescriptorSetManager;
 	private:
-	    using Handle::Handle;
+		using Handle::Handle;
 	};
 
     /**
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index c815834a..0cc4fed0 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -13,8 +13,8 @@
 #include <vulkan/vulkan.hpp>
 #include <spirv_cross.hpp>
 
+#include "DescriptorBinding.hpp"
 #include "VertexLayout.hpp"
-#include "DescriptorConfig.hpp"
 #include "ShaderStage.hpp"
 
 namespace vkcv {
diff --git a/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp b/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp
index 47560b4d..406f1451 100644
--- a/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp
+++ b/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp
@@ -123,7 +123,7 @@ namespace vkcv::rtx {
         vertexInfo.setRange(blas.vertexBuffer.deviceSize); //maybe check if size is correct
 
         vk::WriteDescriptorSet vertexWrite;
-        vertexWrite.setDstSet(m_core->getDescriptorSet(descriptorSetHandles[0]).vulkanHandle);
+        vertexWrite.setDstSet(m_core->getDescriptorSet().vulkanHandle);
         vertexWrite.setDstBinding(3);
         vertexWrite.setDescriptorCount(1);
         vertexWrite.setDescriptorType(vk::DescriptorType::eStorageBuffer);
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index 3e245cb7..22ef2890 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -9,14 +9,9 @@
 
 namespace vkcv {
 	
-	BufferManager::BufferManager() noexcept :
-		m_core(nullptr), m_buffers(), m_stagingBuffer(BufferHandle())
-	{
-	}
-	
-	void BufferManager::init() {
-		if (!m_core) {
-			return;
+	bool BufferManager::init(Core& core) {
+		if (!HandleManager<BufferEntry, BufferHandle>::init(core)) {
+			return false;
 		}
 		
 		m_stagingBuffer = createBuffer(
@@ -26,14 +21,40 @@ namespace vkcv {
 				1024 * 1024,
 				false
 		);
+		
+		return true;
 	}
 	
-	BufferManager::~BufferManager() noexcept {
-		for (uint64_t id = 0; id < m_buffers.size(); id++) {
-			destroyBufferById(id);
+	uint64_t BufferManager::getIdFrom(const BufferHandle &handle) const {
+		return handle.getId();
+	}
+	
+	BufferHandle BufferManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return BufferHandle(id, destroy);
+	}
+	
+	void BufferManager::destroyById(uint64_t id) {
+		auto& buffer = getById(id);
+		
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
+		
+		if (buffer.m_handle) {
+			allocator.destroyBuffer(buffer.m_handle, buffer.m_allocation);
+			
+			buffer.m_handle = nullptr;
+			buffer.m_allocation = nullptr;
 		}
 	}
 	
+	BufferManager::BufferManager() noexcept :
+			HandleManager<BufferEntry, BufferHandle>(),
+			m_stagingBuffer(BufferHandle())
+	{}
+	
+	BufferManager::~BufferManager() noexcept {
+		clear();
+	}
+	
 	BufferHandle BufferManager::createBuffer(const TypeGuard &typeGuard,
 											 BufferType type,
 											 BufferMemoryType memoryType,
@@ -76,7 +97,7 @@ namespace vkcv {
 			usageFlags |= vk::BufferUsageFlagBits::eTransferSrc;
 		}
 		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		vk::MemoryPropertyFlags memoryTypeFlags;
 		vma::MemoryUsage memoryUsage;
@@ -120,8 +141,7 @@ namespace vkcv {
 		vk::Buffer buffer = bufferAllocation.first;
 		vma::Allocation allocation = bufferAllocation.second;
 		
-		const uint64_t id = m_buffers.size();
-		m_buffers.push_back({
+		return add({
 			typeGuard,
 			type,
 			memoryType,
@@ -130,8 +150,6 @@ namespace vkcv {
 			allocation,
 			mappable
 		});
-		
-		return BufferHandle(id, [&](uint64_t id) { destroyBufferById(id); });
 	}
 	
 	/**
@@ -160,11 +178,11 @@ namespace vkcv {
 	 * @param core Core instance
 	 * @param info Staging-info structure
 	 */
-	static void fillFromStagingBuffer(Core* core, StagingWriteInfo& info) {
+	static void fillFromStagingBuffer(Core& core, StagingWriteInfo& info) {
 		const size_t remaining = info.size - info.stagingPosition;
 		const size_t mapped_size = std::min(remaining, info.stagingLimit);
 		
-		const vma::Allocator& allocator = core->getContext().getAllocator();
+		const vma::Allocator& allocator = core.getContext().getAllocator();
 		
 		void* mapped = allocator.mapMemory(info.stagingAllocation);
 		memcpy(mapped, reinterpret_cast<const char*>(info.data) + info.stagingPosition, mapped_size);
@@ -173,7 +191,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		core->recordAndSubmitCommandsImmediate(
+		core.recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
 					const vk::BufferCopy region (
@@ -223,14 +241,14 @@ namespace vkcv {
 	 * @param core Core instance
 	 * @param info Staging-info structure
 	 */
-	static void readToStagingBuffer(Core* core, StagingReadInfo& info) {
+	static void readToStagingBuffer(Core& core, StagingReadInfo& info) {
 		const size_t remaining = info.size - info.stagingPosition;
 		const size_t mapped_size = std::min(remaining, info.stagingLimit);
 		
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		core->recordAndSubmitCommandsImmediate(
+		core.recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
 					const vk::BufferCopy region (
@@ -242,7 +260,7 @@ namespace vkcv {
 					commandBuffer.copyBuffer(info.buffer, info.stagingBuffer, 1, &region);
 				},
 				[&core, &info, &mapped_size, &remaining]() {
-					const vma::Allocator& allocator = core->getContext().getAllocator();
+					const vma::Allocator& allocator = core.getContext().getAllocator();
 					
 					const void* mapped = allocator.mapMemory(info.stagingAllocation);
 					memcpy(reinterpret_cast<char*>(info.data) + info.stagingPosition, mapped, mapped_size);
@@ -261,75 +279,39 @@ namespace vkcv {
 	}
 	
 	vk::Buffer BufferManager::getBuffer(const BufferHandle& handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return nullptr;
-		}
-		
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		return buffer.m_handle;
 	}
 	
 	TypeGuard BufferManager::getTypeGuard(const BufferHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return TypeGuard(0);
-		}
-		
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		return buffer.m_typeGuard;
 	}
 	
 	BufferType BufferManager::getBufferType(const BufferHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return BufferType::UNKNOWN;
-		}
-		
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		return buffer.m_type;
 	}
 	
 	BufferMemoryType BufferManager::getBufferMemoryType(const BufferHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return BufferMemoryType::UNKNOWN;
-		}
-		
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		return buffer.m_memoryType;
 	}
 	
 	size_t BufferManager::getBufferSize(const BufferHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return 0;
-		}
-		
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		return buffer.m_size;
 	}
 	
 	vk::DeviceMemory BufferManager::getDeviceMemory(const BufferHandle& handle) const {
-		const uint64_t id = handle.getId();
+		auto& buffer = (*this)[handle];
 		
-		if (id >= m_buffers.size()) {
-			return nullptr;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		auto info = allocator.getAllocationInfo(
 				buffer.m_allocation
@@ -342,19 +324,13 @@ namespace vkcv {
 								   const void *data,
 								   size_t size,
 								   size_t offset) {
-		const uint64_t id = handle.getId();
+		auto& buffer = (*this)[handle];
 		
 		if (size == 0) {
 			size = SIZE_MAX;
 		}
 		
-		if (id >= m_buffers.size()) {
-			return;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		if (offset > buffer.m_size) {
 			return;
@@ -367,7 +343,7 @@ namespace vkcv {
 			memcpy(reinterpret_cast<char*>(mapped) + offset, data, max_size);
 			allocator.unmapMemory(buffer.m_allocation);
 		} else {
-			auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ];
+			auto& stagingBuffer = (*this)[ m_stagingBuffer ];
 			
 			StagingWriteInfo info;
 			info.data = data;
@@ -381,7 +357,7 @@ namespace vkcv {
 			info.stagingLimit = stagingBuffer.m_size;
 			info.stagingPosition = 0;
 			
-			fillFromStagingBuffer(m_core, info);
+			fillFromStagingBuffer(getCore(), info);
 		}
 	}
 	
@@ -389,19 +365,13 @@ namespace vkcv {
 								   void *data,
 								   size_t size,
 								   size_t offset) {
-		const uint64_t id = handle.getId();
+		auto& buffer = (*this)[handle];
 		
 		if (size == 0) {
 			size = SIZE_MAX;
 		}
 		
-		if (id >= m_buffers.size()) {
-			return;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		if (offset > buffer.m_size) {
 			return;
@@ -414,7 +384,7 @@ namespace vkcv {
 			memcpy(data, reinterpret_cast<const char*>(mapped) + offset, max_size);
 			allocator.unmapMemory(buffer.m_allocation);
 		} else {
-			auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ];
+			auto& stagingBuffer = (*this)[ m_stagingBuffer ];
 			
 			StagingReadInfo info;
 			info.data = data;
@@ -428,24 +398,18 @@ namespace vkcv {
 			info.stagingLimit = stagingBuffer.m_size;
 			info.stagingPosition = 0;
 			
-			readToStagingBuffer(m_core, info);
+			readToStagingBuffer(getCore(), info);
 		}
 	}
 	
 	void* BufferManager::mapBuffer(const BufferHandle& handle, size_t offset, size_t size) {
-		const uint64_t id = handle.getId();
+		auto& buffer = (*this)[handle];
 		
 		if (size == 0) {
 			size = SIZE_MAX;
 		}
 		
-		if (id >= m_buffers.size()) {
-			return nullptr;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		if (offset > buffer.m_size) {
 			return nullptr;
@@ -455,46 +419,15 @@ namespace vkcv {
 	}
 	
 	void BufferManager::unmapBuffer(const BufferHandle& handle) {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return;
-		}
+		auto& buffer = (*this)[handle];
 		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		allocator.unmapMemory(buffer.m_allocation);
 	}
-	
-	void BufferManager::destroyBufferById(uint64_t id) {
-		if (id >= m_buffers.size()) {
-			return;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
-		
-		if (buffer.m_handle) {
-			allocator.destroyBuffer(buffer.m_handle, buffer.m_allocation);
-			
-			buffer.m_handle = nullptr;
-			buffer.m_allocation = nullptr;
-		}
-	}
 
 	void BufferManager ::recordBufferMemoryBarrier(const BufferHandle& handle, vk::CommandBuffer cmdBuffer) {
-
-		const uint64_t id = handle.getId();
-
-		if (id >= m_buffers.size()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "Invalid buffer handle");
-			return;
-		}
-
-		auto& buffer = m_buffers[id];
+		auto& buffer = (*this)[handle];
 		
 		vk::BufferMemoryBarrier memoryBarrier(
 			vk::AccessFlagBits::eMemoryWrite, 
diff --git a/src/vkcv/BufferManager.hpp b/src/vkcv/BufferManager.hpp
index dd9b07e3..e8643812 100644
--- a/src/vkcv/BufferManager.hpp
+++ b/src/vkcv/BufferManager.hpp
@@ -10,42 +10,41 @@
 #include <vk_mem_alloc.hpp>
 
 #include "vkcv/BufferTypes.hpp"
-#include "vkcv/Handles.hpp"
 #include "vkcv/TypeGuard.hpp"
 
+#include "HandleManager.hpp"
+
 namespace vkcv {
 	
-	class Core;
+	struct BufferEntry {
+		TypeGuard m_typeGuard;
+		
+		BufferType m_type;
+		BufferMemoryType m_memoryType;
+		size_t m_size;
+		
+		vk::Buffer m_handle;
+		vma::Allocation m_allocation;
+		
+		bool m_mappable;
+	};
 	
 	/**
 	 * @brief Class to manage the creation, destruction, allocation
 	 * and filling of buffers.
 	 */
-	class BufferManager
-	{
+	class BufferManager : public HandleManager<BufferEntry, BufferHandle> {
 		friend class Core;
 	private:
-		
-		struct Buffer {
-			TypeGuard m_typeGuard;
-			
-			BufferType m_type;
-			BufferMemoryType m_memoryType;
-			size_t m_size;
-			
-			vk::Buffer m_handle;
-			vma::Allocation m_allocation;
-			
-			bool m_mappable;
-		};
-		
-		Core* m_core;
-		std::vector<Buffer> m_buffers;
 		BufferHandle m_stagingBuffer;
 		
-		BufferManager() noexcept;
+		bool init(Core& core) override;
 		
-		void init();
+		[[nodiscard]]
+		uint64_t getIdFrom(const BufferHandle& handle) const override;
+		
+		[[nodiscard]]
+		BufferHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
 		
 		/**
 		 * Destroys and deallocates buffer represented by a given
@@ -53,16 +52,12 @@ namespace vkcv {
 		 *
 		 * @param id Buffer handle id
 		 */
-		void destroyBufferById(uint64_t id);
+		void destroyById(uint64_t id) override;
 		
 	public:
-		~BufferManager() noexcept;
-		
-		BufferManager(BufferManager&& other) = delete;
-		BufferManager(const BufferManager& other) = delete;
+		BufferManager() noexcept;
 		
-		BufferManager& operator=(BufferManager&& other) = delete;
-		BufferManager& operator=(const BufferManager& other) = delete;
+		~BufferManager() noexcept override;
 		
 		/**
 		 * @brief Creates and allocates a new buffer and returns its
@@ -76,6 +71,7 @@ namespace vkcv {
 		 * @param[in] readable Support read functionality
 		 * @return New buffer handle
 		 */
+		[[nodiscard]]
 		BufferHandle createBuffer(const TypeGuard &typeGuard,
 								  BufferType type,
 								  BufferMemoryType memoryType,
diff --git a/src/vkcv/ComputePipelineManager.cpp b/src/vkcv/ComputePipelineManager.cpp
index 264389ca..68836cdb 100644
--- a/src/vkcv/ComputePipelineManager.cpp
+++ b/src/vkcv/ComputePipelineManager.cpp
@@ -1,53 +1,62 @@
 #include "ComputePipelineManager.hpp"
 
-namespace vkcv
-{
-
-    ComputePipelineManager::ComputePipelineManager(vk::Device device) noexcept :
-            m_Device{device},
-            m_Pipelines{}
-    {}
-
-    ComputePipelineManager::~ComputePipelineManager() noexcept
-    {
-        for (uint64_t id = 0; id < m_Pipelines.size(); id++) {
-            destroyPipelineById(id);
-        }
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+	
+	uint64_t ComputePipelineManager::getIdFrom(const ComputePipelineHandle &handle) const {
+		return handle.getId();
+	}
+	
+	ComputePipelineHandle ComputePipelineManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return ComputePipelineHandle(id, destroy);
+	}
+	
+	void ComputePipelineManager::destroyById(uint64_t id) {
+		auto& pipeline = getById(id);
+		
+		if (pipeline.m_handle) {
+			getCore().getContext().getDevice().destroy(pipeline.m_handle);
+			pipeline.m_handle = nullptr;
+		}
+		
+		if (pipeline.m_layout) {
+			getCore().getContext().getDevice().destroy(pipeline.m_layout);
+			pipeline.m_layout = nullptr;
+		}
+	}
+	
+	ComputePipelineManager::ComputePipelineManager() noexcept :
+		HandleManager<ComputePipelineEntry, ComputePipelineHandle>() {}
+	
+	vk::Result ComputePipelineManager::createShaderModule(vk::ShaderModule &module,
+														  const ShaderProgram &shaderProgram,
+														  const ShaderStage stage) {
+		std::vector<uint32_t> code = shaderProgram.getShaderBinary(stage);
+		vk::ShaderModuleCreateInfo moduleInfo({}, code.size() * sizeof(uint32_t), code.data());
+		return getCore().getContext().getDevice().createShaderModule(&moduleInfo, nullptr, &module);
+	}
+
+    ComputePipelineManager::~ComputePipelineManager() noexcept {
+        clear();
     }
 
-    vk::Pipeline ComputePipelineManager::getVkPipeline(const ComputePipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-
-        if (id >= m_Pipelines.size()) {
-            return nullptr;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
+    vk::Pipeline ComputePipelineManager::getVkPipeline(const ComputePipelineHandle &handle) const {
+        auto& pipeline = (*this)[handle];
         return pipeline.m_handle;
     }
 
-    vk::PipelineLayout ComputePipelineManager::getVkPipelineLayout(const ComputePipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-
-        if (id >= m_Pipelines.size()) {
-            return nullptr;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
+    vk::PipelineLayout ComputePipelineManager::getVkPipelineLayout(const ComputePipelineHandle &handle) const {
+		auto& pipeline = (*this)[handle];
         return pipeline.m_layout;
     }
 
     ComputePipelineHandle ComputePipelineManager::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 ComputePipelineHandle();
+            return {};
 
         vk::PipelineShaderStageCreateInfo pipelineComputeShaderStageInfo(
                 {},
@@ -67,10 +76,11 @@ namespace vkcv
         }
 
         vk::PipelineLayout vkPipelineLayout{};
-        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) !=
-            vk::Result::eSuccess) {
-            m_Device.destroy(computeModule);
-            return ComputePipelineHandle();
+        if (getCore().getContext().getDevice().createPipelineLayout(&pipelineLayoutCreateInfo,
+																	nullptr,
+																	&vkPipelineLayout) != vk::Result::eSuccess) {
+			getCore().getContext().getDevice().destroy(computeModule);
+            return {};
         }
 
         vk::ComputePipelineCreateInfo computePipelineCreateInfo{};
@@ -78,42 +88,17 @@ namespace vkcv
         computePipelineCreateInfo.layout = vkPipelineLayout;
 
         vk::Pipeline vkPipeline;
-        if (m_Device.createComputePipelines(nullptr, 1, &computePipelineCreateInfo, nullptr, &vkPipeline) !=
-            vk::Result::eSuccess) {
-            m_Device.destroy(computeModule);
+        if (getCore().getContext().getDevice().createComputePipelines(nullptr,
+																	  1,
+																	  &computePipelineCreateInfo,
+																	  nullptr,
+																	  &vkPipeline) != vk::Result::eSuccess) {
+			getCore().getContext().getDevice().destroy(computeModule);
             return ComputePipelineHandle();
         }
-
-        m_Device.destroy(computeModule);
-
-        const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({vkPipeline, vkPipelineLayout});
-
-        return ComputePipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
+	
+		getCore().getContext().getDevice().destroy(computeModule);
+        return add({ vkPipeline, vkPipelineLayout });
     }
 
-    void vkcv::ComputePipelineManager::destroyPipelineById(uint64_t id) {
-        if (id >= m_Pipelines.size()) {
-            return;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
-        if (pipeline.m_handle) {
-            m_Device.destroy(pipeline.m_handle);
-            pipeline.m_handle = nullptr;
-        }
-
-        if (pipeline.m_layout) {
-            m_Device.destroy(pipeline.m_layout);
-            pipeline.m_layout = nullptr;
-        }
-    }
-
-    vk::Result ComputePipelineManager::createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, const ShaderStage stage)
-    {
-        std::vector<uint32_t> code = shaderProgram.getShaderBinary(stage);
-        vk::ShaderModuleCreateInfo moduleInfo({}, code.size() * sizeof(uint32_t), code.data());
-        return m_Device.createShaderModule(&moduleInfo, nullptr, &module);
-    }
 }
\ No newline at end of file
diff --git a/src/vkcv/ComputePipelineManager.hpp b/src/vkcv/ComputePipelineManager.hpp
index 4c186ac7..860181c8 100644
--- a/src/vkcv/ComputePipelineManager.hpp
+++ b/src/vkcv/ComputePipelineManager.hpp
@@ -9,28 +9,45 @@
 #include <vulkan/vulkan.hpp>
 #include <vector>
 
-#include "vkcv/Handles.hpp"
+#include "HandleManager.hpp"
+
 #include "vkcv/ShaderProgram.hpp"
 #include "vkcv/ComputePipelineConfig.hpp"
 
-namespace vkcv
-{
+namespace vkcv {
+	
+	struct ComputePipelineEntry {
+		vk::Pipeline m_handle;
+		vk::PipelineLayout m_layout;
+	};
 
 	/**
 	 * @brief Class to manage compute pipelines.
 	 */
-    class ComputePipelineManager
-    {
+    class ComputePipelineManager : public HandleManager<ComputePipelineEntry, ComputePipelineHandle> {
+	private:
+		[[nodiscard]]
+		uint64_t getIdFrom(const ComputePipelineHandle& handle) const override;
+	
+		[[nodiscard]]
+		ComputePipelineHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
+	
+		/**
+		 * Destroys and deallocates compute pipeline represented by a given
+		 * compute pipeline handle id.
+		 *
+		 * @param id Compute pipeline handle id
+		 */
+		void destroyById(uint64_t id) override;
+	
+		vk::Result createShaderModule(vk::ShaderModule &module,
+									  const ShaderProgram &shaderProgram,
+									  ShaderStage stage);
+		
     public:
-        ComputePipelineManager() = delete; // no default ctor
-        explicit ComputePipelineManager(vk::Device device) noexcept; // ctor
-        ~ComputePipelineManager() noexcept; // dtor
-
-        ComputePipelineManager(const ComputePipelineManager &other) = delete; // copy-ctor
-        ComputePipelineManager(ComputePipelineManager &&other) = delete; // move-ctor;
-
-        ComputePipelineManager & operator=(const ComputePipelineManager &other) = delete; // copy-assign op
-        ComputePipelineManager & operator=(ComputePipelineManager &&other) = delete; // move-assign op
+		ComputePipelineManager() noexcept;
+		
+        ~ComputePipelineManager() noexcept override; // dtor
 
         /**
         * Returns a vk::Pipeline object by handle.
@@ -58,20 +75,7 @@ namespace vkcv
          */
         ComputePipelineHandle createComputePipeline(const ShaderProgram& shaderProgram,
 													const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts);
-
-    private:
-        struct ComputePipeline {
-            vk::Pipeline m_handle;
-            vk::PipelineLayout m_layout;
-        };
-
-        vk::Device m_Device;
-        std::vector<ComputePipeline> m_Pipelines;
-
-        void destroyPipelineById(uint64_t id);
-
-        vk::Result createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, ShaderStage stage);
-
+		
     };
 
 }
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 22f334be..821ae5e3 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -1,5 +1,5 @@
 /**
- * @authors Artur Wasmut
+ * @authors Artur Wasmut, Alexander Gauggel, Tobias Frisch
  * @file src/vkcv/Core.cpp
  * @brief Handling of global states regarding dependencies
  */
@@ -13,7 +13,8 @@
 #include "BufferManager.hpp"
 #include "SamplerManager.hpp"
 #include "ImageManager.hpp"
-#include "DescriptorManager.hpp"
+#include "DescriptorSetLayoutManager.hpp"
+#include "DescriptorSetManager.hpp"
 #include "WindowManager.hpp"
 #include "CommandStreamManager.hpp"
 #include <cmath>
@@ -44,32 +45,36 @@ namespace vkcv
         return Core(std::move(context) , commandResources, defaultSyncResources);
     }
 
-    const Context &Core::getContext() const
-    {
+    const Context &Core::getContext() const {
         return m_Context;
     }
 
     Core::Core(Context &&context, const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
-            m_Context(std::move(context)),
-            m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)},
-            m_PipelineManager{std::make_unique<GraphicsPipelineManager>(m_Context.m_Device, m_Context.m_PhysicalDevice)},
-            m_ComputePipelineManager{std::make_unique<ComputePipelineManager>(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_CommandStreamManager{std::unique_ptr<CommandStreamManager>(new CommandStreamManager)},
+			m_Context(std::move(context)),
+			m_PassManager(std::make_unique<PassManager>()),
+			m_GraphicsPipelineManager(std::make_unique<GraphicsPipelineManager>()),
+			m_ComputePipelineManager(std::make_unique<ComputePipelineManager>()),
+			m_DescriptorSetLayoutManager(std::make_unique<DescriptorSetLayoutManager>()),
+			m_DescriptorSetManager(std::make_unique<DescriptorSetManager>()),
+			m_BufferManager(std::make_unique<BufferManager>()),
+			m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))),
+			m_ImageManager(std::make_unique<ImageManager>()),
+			m_CommandStreamManager{std::unique_ptr<CommandStreamManager>(new CommandStreamManager)},
 			m_WindowManager(std::make_unique<WindowManager>()),
 			m_SwapchainManager(std::make_unique<SwapchainManager>()),
-            m_CommandResources(commandResources),
-            m_SyncResources(syncResources),
+			m_CommandResources(commandResources),
+			m_SyncResources(syncResources),
 			m_downsampler(nullptr)
 	{
-		m_BufferManager->m_core = this;
-		m_BufferManager->init();
+		m_PassManager->init(*this);
+		m_GraphicsPipelineManager->init(*this);
+		m_ComputePipelineManager->init(*this);
+		m_DescriptorSetLayoutManager->init(*this);
+		m_DescriptorSetManager->init(*this, *m_DescriptorSetLayoutManager);
+		m_BufferManager->init(*this);
 		m_CommandStreamManager->init(this);
 		m_SwapchainManager->m_context = &m_Context;
-		m_ImageManager->m_core = this;
+		m_ImageManager->init(*this, *m_BufferManager);
 		m_downsampler = std::unique_ptr<Downsampler>(new BlitDownsampler(*this, *m_ImageManager));
 	}
 
@@ -81,7 +86,7 @@ namespace vkcv
 	}
 	
 	GraphicsPipelineHandle Core::createGraphicsPipeline(const GraphicsPipelineConfig &config) {
-        return m_PipelineManager->createPipeline(config, *m_PassManager, *m_DescriptorManager);
+        return m_GraphicsPipelineManager->createPipeline(config, *m_PassManager, *m_DescriptorSetLayoutManager);
     }
 
     ComputePipelineHandle Core::createComputePipeline(const ComputePipelineConfig &config) {
@@ -89,7 +94,9 @@ namespace vkcv
 		layouts.resize(config.getDescriptorSetLayouts().size());
 	
 		for (size_t i = 0; i < layouts.size(); i++) {
-			layouts[i] = getDescriptorSetLayout(config.getDescriptorSetLayouts()[i]).vulkanHandle;
+			layouts[i] = m_DescriptorSetLayoutManager->getDescriptorSetLayout(
+					config.getDescriptorSetLayouts()[i]
+			).vulkanHandle;
 		}
 		
         return m_ComputePipelineManager->createComputePipeline(config.getShaderProgram(), layouts);
@@ -341,13 +348,13 @@ namespace vkcv
 		}
 	}
 	
-	void recordDrawcall(
-			const Core				&core,
-			const DrawcallInfo      &drawcall,
-			vk::CommandBuffer       cmdBuffer,
-			vk::PipelineLayout      pipelineLayout,
-			const PushConstants     &pushConstants,
-			const size_t            drawcallIndex) {
+	static void recordDrawcall(
+			const DescriptorSetManager 	&descriptorSetManager,
+			const DrawcallInfo      	&drawcall,
+			vk::CommandBuffer       	cmdBuffer,
+			vk::PipelineLayout      	pipelineLayout,
+			const PushConstants     	&pushConstants,
+			const size_t            	drawcallIndex) {
 		
 		for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) {
 			const auto& vertexBinding = drawcall.mesh.vertexBufferBindings[i];
@@ -359,7 +366,7 @@ namespace vkcv
 					vk::PipelineBindPoint::eGraphics,
 					pipelineLayout,
 					descriptorUsage.setLocation,
-					core.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle,
+					descriptorSetManager.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle,
 					nullptr);
 		}
 		
@@ -375,8 +382,7 @@ namespace vkcv
 		if (drawcall.mesh.indexBuffer) {
 			cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, getIndexType(drawcall.mesh.indexBitCount));
 			cmdBuffer.drawIndexed(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {});
-		}
-		else {
+		} else {
 			cmdBuffer.draw(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {});
 		}
 	}
@@ -403,8 +409,8 @@ namespace vkcv
 		const vk::RenderPass        renderpass      = m_PassManager->getVkPass(renderpassHandle);
 		const PassConfig            passConfig      = m_PassManager->getPassConfig(renderpassHandle);
 
-		const vk::Pipeline          pipeline        = m_PipelineManager->getVkPipeline(pipelineHandle);
-		const vk::PipelineLayout    pipelineLayout  = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
+		const vk::Pipeline          pipeline        = m_GraphicsPipelineManager->getVkPipeline(pipelineHandle);
+		const vk::PipelineLayout    pipelineLayout  = m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D            renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
 		vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
@@ -429,13 +435,13 @@ namespace vkcv
 
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-			const GraphicsPipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
+			const GraphicsPipelineConfig &pipeConfig = m_GraphicsPipelineManager->getPipelineConfig(pipelineHandle);
 			if (pipeConfig.isViewportDynamic()) {
 				recordDynamicViewport(cmdBuffer, width, height);
 			}
 
 			for (size_t i = 0; i < drawcalls.size(); i++) {
-				recordDrawcall(*this, drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
+				recordDrawcall(*m_DescriptorSetManager, drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
 			}
 
 			cmdBuffer.endRenderPass();
@@ -473,8 +479,8 @@ namespace vkcv
         const vk::RenderPass        renderpass      = m_PassManager->getVkPass(renderpassHandle);
         const PassConfig            passConfig      = m_PassManager->getPassConfig(renderpassHandle);
 
-        const vk::Pipeline          pipeline        = m_PipelineManager->getVkPipeline(pipelineHandle);
-        const vk::PipelineLayout    pipelineLayout  = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
+        const vk::Pipeline          pipeline        = m_GraphicsPipelineManager->getVkPipeline(pipelineHandle);
+        const vk::PipelineLayout    pipelineLayout  = m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
         const vk::Rect2D            renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
         vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
@@ -502,7 +508,7 @@ namespace vkcv
 
             cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-            const GraphicsPipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
+            const GraphicsPipelineConfig &pipeConfig = m_GraphicsPipelineManager->getPipelineConfig(pipelineHandle);
             if (pipeConfig.isViewportDynamic()) {
                 recordDynamicViewport(cmdBuffer, width, height);
             }
@@ -517,7 +523,7 @@ namespace vkcv
 					pushConstantData.getDrawcallData(0));
 			}
 
-            vkcv::DescriptorSet descSet = m_DescriptorManager->getDescriptorSet(compiledDescriptorSet);
+            const auto& descSet = m_DescriptorSetManager->getDescriptorSet(compiledDescriptorSet);
 
             cmdBuffer.bindDescriptorSets(
                     vk::PipelineBindPoint::eGraphics,
@@ -547,6 +553,7 @@ namespace vkcv
     }
 	
 	static void recordMeshShaderDrawcall(const Core& core,
+										 const DescriptorSetManager &descriptorSetManager,
 										 vk::CommandBuffer cmdBuffer,
 										 vk::PipelineLayout pipelineLayout,
 										 const PushConstants& pushConstantData,
@@ -567,7 +574,7 @@ namespace vkcv
 					vk::PipelineBindPoint::eGraphics,
 					pipelineLayout,
 					descriptorUsage.setLocation,
-					core.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle,
+					descriptorSetManager.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle,
 					nullptr);
 		}
 		
@@ -608,8 +615,8 @@ namespace vkcv
 		const vk::RenderPass        renderpass = m_PassManager->getVkPass(renderpassHandle);
 		const PassConfig            passConfig = m_PassManager->getPassConfig(renderpassHandle);
 
-		const vk::Pipeline          pipeline = m_PipelineManager->getVkPipeline(pipelineHandle);
-		const vk::PipelineLayout    pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
+		const vk::Pipeline          pipeline = m_GraphicsPipelineManager->getVkPipeline(pipelineHandle);
+		const vk::PipelineLayout    pipelineLayout = m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D            renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
 		vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
@@ -634,7 +641,7 @@ namespace vkcv
 
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-			const GraphicsPipelineConfig& pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
+			const GraphicsPipelineConfig& pipeConfig = m_GraphicsPipelineManager->getPipelineConfig(pipelineHandle);
 			if (pipeConfig.isViewportDynamic()) {
 				recordDynamicViewport(cmdBuffer, width, height);
 			}
@@ -643,6 +650,7 @@ namespace vkcv
                 const uint32_t pushConstantOffset = i * pushConstantData.getSizePerDrawcall();
                 recordMeshShaderDrawcall(
 					*this,
+					*m_DescriptorSetManager,
                     cmdBuffer,
                     pipelineLayout,
                     pushConstantData,
@@ -682,7 +690,7 @@ namespace vkcv
 					vk::PipelineBindPoint::eRayTracingKHR,
 					rtxPipelineLayout,
 					usage.setLocation,
-					{ getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
 					usage.dynamicOffsets
 				);
 			}
@@ -723,7 +731,7 @@ namespace vkcv
 					vk::PipelineBindPoint::eCompute,
 					pipelineLayout,
 					usage.setLocation,
-					{ getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
 					usage.dynamicOffsets
 				);
 			}
@@ -802,7 +810,7 @@ namespace vkcv
 					vk::PipelineBindPoint::eCompute,
 					pipelineLayout,
 					usage.setLocation,
-					{ getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
 					usage.dynamicOffsets
 				);
 			}
@@ -1021,23 +1029,16 @@ namespace vkcv
 		return getSwapchain(swapchainHandle);
 	}
 
-	DescriptorSetLayoutHandle Core::createDescriptorSetLayout(const DescriptorBindings &bindings)
-	{
-	    return m_DescriptorManager->createDescriptorSetLayout(bindings);
-	}
-
-	DescriptorSetLayout Core::getDescriptorSetLayout(const DescriptorSetLayoutHandle handle) const
-	{
-	    return m_DescriptorManager->getDescriptorSetLayout(handle);
+	DescriptorSetLayoutHandle Core::createDescriptorSetLayout(const DescriptorBindings &bindings) {
+	    return m_DescriptorSetLayoutManager->createDescriptorSetLayout(bindings);
 	}
 
-	DescriptorSetHandle Core::createDescriptorSet(const DescriptorSetLayoutHandle &layout)
-    {
-        return m_DescriptorManager->createDescriptorSet(layout);
+	DescriptorSetHandle Core::createDescriptorSet(const DescriptorSetLayoutHandle &layout) {
+        return m_DescriptorSetManager->createDescriptorSet(layout);
     }
 
 	void Core::writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes) {
-		m_DescriptorManager->writeDescriptorSet(
+		m_DescriptorSetManager->writeDescriptorSet(
 			handle,
 			writes, 
 			*m_ImageManager, 
@@ -1045,10 +1046,6 @@ namespace vkcv
 			*m_SamplerManager);
 	}
 
-	DescriptorSet Core::getDescriptorSet(const DescriptorSetHandle handle) const {
-		return m_DescriptorManager->getDescriptorSet(handle);
-	}
-
 	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle& cmdStream) {
 		auto swapchainHandle = ImageHandle::createSwapchainImageHandle();
 		recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) {
@@ -1280,7 +1277,7 @@ namespace vkcv
 				m_Context.getDevice(),
 				vk::ObjectType::ePipeline,
 				uint64_t(static_cast<VkPipeline>(
-						m_PipelineManager->getVkPipeline(handle)
+						m_GraphicsPipelineManager->getVkPipeline(handle)
 				)),
 				label
 		);
@@ -1312,7 +1309,7 @@ namespace vkcv
 				m_Context.getDevice(),
 				vk::ObjectType::eDescriptorSet,
 				uint64_t(static_cast<VkDescriptorSet>(
-						m_DescriptorManager->getDescriptorSet(handle).vulkanHandle
+						m_DescriptorSetManager->getDescriptorSet(handle).vulkanHandle
 				)),
 				label
 		);
diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorBinding.cpp
similarity index 86%
rename from src/vkcv/DescriptorConfig.cpp
rename to src/vkcv/DescriptorBinding.cpp
index 15bb05fd..0ed0144e 100644
--- a/src/vkcv/DescriptorConfig.cpp
+++ b/src/vkcv/DescriptorBinding.cpp
@@ -1,7 +1,7 @@
-#include "vkcv/DescriptorConfig.hpp"
+#include "vkcv/DescriptorBinding.hpp"
 
-namespace vkcv
-{
+namespace vkcv {
+	
     bool DescriptorBinding::operator==(const DescriptorBinding &other) const
     {
 	    return (this->bindingID == other.bindingID) &&
@@ -10,4 +10,5 @@ namespace vkcv
 	           (this->shaderStages == other.shaderStages) &&
 	           (this->variableCount == other.variableCount);
     }
+	
 }
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
deleted file mode 100644
index a9cabbd4..00000000
--- a/src/vkcv/DescriptorManager.cpp
+++ /dev/null
@@ -1,399 +0,0 @@
-#include "DescriptorManager.hpp"
-
-namespace vkcv
-{
-    DescriptorManager::DescriptorManager(vk::Device device) noexcept:
-        m_Device{ device }
-    {
-        /**
-         * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type below.
-		 * Finally, create an initial pool.
-         */
-		m_PoolSizes = {
-				vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
-				vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
-				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
-				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000),
-				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000),
-				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000),    // for RTX
-				vk::DescriptorPoolSize(vk::DescriptorType::eAccelerationStructureKHR, 1000) // for RTX
-		};
-
-		m_PoolInfo = vk::DescriptorPoolCreateInfo(
-				vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
-			1000,
-			static_cast<uint32_t>(m_PoolSizes.size()),
-			m_PoolSizes.data());
-
-		allocateDescriptorPool();
-    }
-
-    DescriptorManager::~DescriptorManager() noexcept
-    {
-        for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) {
-			destroyDescriptorSetById(id);
-        }
-		
-		for (uint64_t id = 0; id < m_DescriptorSetLayouts.size(); id++) {
-			// Resets the usage count to zero for destruction.
-			m_DescriptorSetLayouts[id].layoutUsageCount = 0;
-			destroyDescriptorSetLayoutById(id);
-		}
-        
-		m_DescriptorSets.clear();
-		m_DescriptorSetLayouts.clear();
-  
-		for (const auto &pool : m_Pools) {
-			if (pool) {
-				m_Device.destroy(pool);
-			}
-		}
-    }
-
-    DescriptorSetLayoutHandle DescriptorManager::createDescriptorSetLayout(const DescriptorBindings &bindings)
-    {
-        for (size_t i = 0; i < m_DescriptorSetLayouts.size(); i++)
-        {
-            if(m_DescriptorSetLayouts[i].descriptorBindings.size() != bindings.size())
-                continue;
-
-            if (m_DescriptorSetLayouts[i].descriptorBindings == bindings)
-            {
-				m_DescriptorSetLayouts[i].layoutUsageCount++;
-                return DescriptorSetLayoutHandle(i, [&](uint64_t id) { destroyDescriptorSetLayoutById(id); });
-            }
-        }
-        
-        //create the descriptor set's layout and binding flags by iterating over its bindings
-        std::vector<vk::DescriptorSetLayoutBinding> bindingsVector = {};
-		std::vector<vk::DescriptorBindingFlags> bindingsFlags = {};
-		
-        for (auto bindingElem : bindings)
-        {
-            DescriptorBinding binding = bindingElem.second;
-            uint32_t bindingID = bindingElem.first;
-	
-			bindingsVector.emplace_back(
-					bindingID,
-					getVkDescriptorType(binding.descriptorType),
-					binding.descriptorCount,
-					getShaderStageFlags(binding.shaderStages),
-					nullptr
-			);
-			
-			vk::DescriptorBindingFlags flags;
-			
-			if (binding.variableCount)
-				flags |= vk::DescriptorBindingFlagBits::eVariableDescriptorCount;
-			
-			if (binding.partialBinding)
-				flags |= vk::DescriptorBindingFlagBits::ePartiallyBound;
-	
-			bindingsFlags.push_back(flags);
-        }
-		
-        vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo (
-				bindingsFlags.size(), bindingsFlags.data()
-		);
-
-        //create the descriptor set's layout from the binding data gathered above
-        vk::DescriptorSetLayout vulkanHandle;
-        vk::DescriptorSetLayoutCreateInfo layoutInfo(vk::DescriptorSetLayoutCreateFlags(), bindingsVector);
-		layoutInfo.setPNext(&bindingFlagsInfo);
-		
-        auto result = m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &vulkanHandle);
-        if (result != vk::Result::eSuccess) {
-            vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
-            return DescriptorSetLayoutHandle();
-        };
-
-        const uint64_t id = m_DescriptorSetLayouts.size();
-        m_DescriptorSetLayouts.push_back({vulkanHandle, bindings, 1});
-        return DescriptorSetLayoutHandle(id, [&](uint64_t id) { destroyDescriptorSetLayoutById(id); });
-    }
-
-    DescriptorSetHandle DescriptorManager::createDescriptorSet(const DescriptorSetLayoutHandle &layout)
-    {
-        //create and allocate the set based on the layout provided
-        DescriptorSetLayout setLayout = m_DescriptorSetLayouts[layout.getId()];
-        vk::DescriptorSet vulkanHandle;
-        vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle);
-
-        uint32_t sumVariableDescriptorCounts = 0;
-        for (auto bindingElem : setLayout.descriptorBindings)
-        {
-            auto binding = bindingElem.second;
-            if(binding.variableCount)
-                sumVariableDescriptorCounts += binding.descriptorCount;
-        }
-
-        vk::DescriptorSetVariableDescriptorCountAllocateInfo variableAllocInfo(1, &sumVariableDescriptorCounts);
-
-        if (sumVariableDescriptorCounts > 0) {
-            allocInfo.setPNext(&variableAllocInfo);
-        }
-
-        auto result = m_Device.allocateDescriptorSets(&allocInfo, &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, &vulkanHandle);
-			}
-			
-			if (result != vk::Result::eSuccess) {
-				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
-						 vk::to_string(result).c_str());
-				return DescriptorSetHandle();
-			}
-        };
-	
-		size_t poolIndex = (m_Pools.size() - 1);
-        const uint64_t id = m_DescriptorSets.size();
-        m_DescriptorSets.push_back({ vulkanHandle, layout, poolIndex });
-        return DescriptorSetHandle(id, [&](uint64_t id) { destroyDescriptorSetById(id); });
-    }
-    
-	/**
-	 * @brief Structure to store details to write to a descriptor set.
-	 */
-    struct WriteDescriptorSetInfo {
-		size_t imageInfoIndex;
-		size_t bufferInfoIndex;
-		uint32_t binding;
-		uint32_t arrayElementIndex;
-		uint32_t descriptorCount;
-		vk::DescriptorType type;
-    };
-
-	void DescriptorManager::writeDescriptorSet(
-		const DescriptorSetHandle	&handle,
-		const DescriptorWrites	&writes,
-		const ImageManager		&imageManager, 
-		const BufferManager		&bufferManager,
-		const SamplerManager	&samplerManager) {
-		vk::DescriptorSet set = m_DescriptorSets[handle.getId()].vulkanHandle;
-
-		std::vector<vk::DescriptorImageInfo> imageInfos;
-		std::vector<vk::DescriptorBufferInfo> bufferInfos;
-		
-		std::vector<WriteDescriptorSetInfo> writeInfos;
-
-		for (const auto& write : writes.getSampledImageWrites())
-		{
-		    const vk::ImageLayout layout = (write.useGeneralLayout?
-					vk::ImageLayout::eGeneral :
-					vk::ImageLayout::eShaderReadOnlyOptimal
-			);
-			
-			for (uint32_t i = 0; i < write.mipCount; i++) {
-				const vk::DescriptorImageInfo imageInfo(
-						nullptr,
-						imageManager.getVulkanImageView(
-								write.image,
-								write.mipLevel + i,
-								write.arrayView
-						),
-						layout
-				);
-				
-				imageInfos.push_back(imageInfo);
-			}
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size() + 1 - write.mipCount,
-					0,
-					write.binding,
-					write.arrayIndex,
-					write.mipCount,
-					vk::DescriptorType::eSampledImage,
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.getStorageImageWrites()) {
-			for (uint32_t i = 0; i < write.mipCount; i++) {
-				const vk::DescriptorImageInfo imageInfo(
-						nullptr,
-						imageManager.getVulkanImageView(
-								write.image,
-								write.mipLevel + i,
-								write.arrayView
-						),
-						vk::ImageLayout::eGeneral
-				);
-				
-				imageInfos.push_back(imageInfo);
-			}
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size() + 1 - write.mipCount,
-					0,
-					write.binding,
-					0,
-					write.mipCount,
-					vk::DescriptorType::eStorageImage
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.getUniformBufferWrites()) {
-			const size_t size = bufferManager.getBufferSize(write.buffer);
-			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
-			
-			const vk::DescriptorBufferInfo bufferInfo(
-				bufferManager.getBuffer(write.buffer),
-				offset,
-				write.size == 0? size : std::min<uint32_t>(
-						write.size, size - offset
-				)
-			);
-			
-			bufferInfos.push_back(bufferInfo);
-
-			WriteDescriptorSetInfo vulkanWrite = {
-					0,
-					bufferInfos.size(),
-					write.binding,
-					0,
-					1,
-					write.dynamic?
-					vk::DescriptorType::eUniformBufferDynamic :
-					vk::DescriptorType::eUniformBuffer
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.getStorageBufferWrites()) {
-			const size_t size = bufferManager.getBufferSize(write.buffer);
-			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
-			
-			const vk::DescriptorBufferInfo bufferInfo(
-				bufferManager.getBuffer(write.buffer),
-				offset,
-				write.size == 0? size : std::min<uint32_t>(
-						write.size, size - offset
-				)
-			);
-			
-			bufferInfos.push_back(bufferInfo);
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					0,
-					bufferInfos.size(),
-					write.binding,
-					0,
-					1,
-					write.dynamic?
-					vk::DescriptorType::eStorageBufferDynamic :
-					vk::DescriptorType::eStorageBuffer
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.getSamplerWrites()) {
-			const vk::Sampler& sampler = samplerManager.getVulkanSampler(write.sampler);
-			
-			const vk::DescriptorImageInfo imageInfo(
-				sampler,
-				nullptr,
-				vk::ImageLayout::eGeneral
-			);
-			
-			imageInfos.push_back(imageInfo);
-
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size(),
-					0,
-					write.binding,
-					0,
-					1,
-					vk::DescriptorType::eSampler
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-		
-		std::vector<vk::WriteDescriptorSet> vulkanWrites;
-		
-		for (const auto& write : writeInfos) {
-			vk::WriteDescriptorSet vulkanWrite(
-					set,
-					write.binding,
-					write.arrayElementIndex,
-					write.descriptorCount,
-					write.type,
-					(write.imageInfoIndex > 0? &(imageInfos[write.imageInfoIndex - 1]) : nullptr),
-					(write.bufferInfoIndex > 0? &(bufferInfos[write.bufferInfoIndex - 1]) : nullptr)
-			);
-			
-			vulkanWrites.push_back(vulkanWrite);
-		}
-		
-		m_Device.updateDescriptorSets(vulkanWrites, nullptr);
-	}
-
-	DescriptorSetLayout DescriptorManager::getDescriptorSetLayout(const DescriptorSetLayoutHandle handle) const
-	{
-	    return m_DescriptorSetLayouts[handle.getId()];
-	}
-
-	DescriptorSet DescriptorManager::getDescriptorSet(const DescriptorSetHandle handle) const {
-		return m_DescriptorSets[handle.getId()];
-	}
-
-    void DescriptorManager::destroyDescriptorSetById(uint64_t id) {
-		if (id >= m_DescriptorSets.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid id");
-			return;
-		}
-		
-		auto& set = m_DescriptorSets[id];
-		
-		if (set.vulkanHandle) {
-			m_Device.freeDescriptorSets(m_Pools[set.poolIndex], 1, &(set.vulkanHandle));
-			set.setLayoutHandle = DescriptorSetLayoutHandle();
-			set.vulkanHandle = nullptr;
-		}
-	}
-
-	void DescriptorManager::destroyDescriptorSetLayoutById(uint64_t id) {
-	    if (id >= m_DescriptorSetLayouts.size()) {
-	        vkcv_log(LogLevel::ERROR, "Invalid id");
-	        return;
-	    }
-
-	    auto& layout = m_DescriptorSetLayouts[id];
-
-		if (layout.layoutUsageCount > 1) {
-			layout.layoutUsageCount--;
-			return;
-		} else {
-			layout.layoutUsageCount = 0;
-		}
-		
-	    if (layout.vulkanHandle){
-	        m_Device.destroy(layout.vulkanHandle);
-	        layout.vulkanHandle = nullptr;
-	    }
-	}
-
-	vk::DescriptorPool DescriptorManager::allocateDescriptorPool() {
-		vk::DescriptorPool pool;
-		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess) {
-			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
-			pool = nullptr;
-		} else {
-			m_Pools.push_back(pool);
-		}
-		
-		return pool;
-	}
-
-}
diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp
deleted file mode 100644
index 7b79e332..00000000
--- a/src/vkcv/DescriptorManager.hpp
+++ /dev/null
@@ -1,120 +0,0 @@
-#pragma once
-
-/**
- * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann, Tobias Frisch
- * @file src/vkcv/DescriptorManager.cpp
- * @brief Creation and handling of descriptor sets and the respective descriptor pools
- */
-#include <vulkan/vulkan.hpp>
-
-#include "vkcv/Handles.hpp"
-#include "vkcv/DescriptorConfig.hpp"
-#include "vkcv/DescriptorWrites.hpp"
-
-#include "ImageManager.hpp"
-#include "BufferManager.hpp"
-#include "SamplerManager.hpp"
-
-namespace vkcv
-{
-	
-	/**
-	 * @brief Class to manage descriptor sets and descriptor set layouts.
-	 */
-	class DescriptorManager
-	{
-	public:
-		/**
-		 * @brief Constructor of the descriptor manager
-		 *
-		 * @param[in,out] device Vulkan device
-		 */
-	    explicit DescriptorManager(vk::Device device) noexcept;
-		
-		/**
-		 * @brief Destructor of the descriptor manager
-		 */
-	    ~DescriptorManager() noexcept;
-
-		/**
-		 * @brief Creates a descriptor set layout with given descriptor bindings
-		 * or returns a matching handle.
-		 *
-		 * @param[in] bindings Descriptor bindings
-		 * @return Handle of descriptor set layout
-		 */
-	    DescriptorSetLayoutHandle createDescriptorSetLayout(const DescriptorBindings &bindings);
-		
-		/**
-		 * @brief Creates a descriptor set using a given descriptor set layout.
-		 *
-		 * @param[in] layout Handle of descriptor set layout
-		 * @return Handle of descriptor set
-		 */
-        DescriptorSetHandle createDescriptorSet(const DescriptorSetLayoutHandle &layout);
-
-		/**
-		 * @brief Writes to a descriptor set using writes and all required managers.
-		 *
-		 * @param[in] handle Handle of descriptor set
-		 * @param[in] writes Descriptor set writes
-		 * @param[in] imageManager Image manager
-		 * @param[in] bufferManager Buffer manager
-		 * @param[in] samplerManager Sampler manager
-		 */
-		void writeDescriptorSet(const DescriptorSetHandle &handle,
-								const DescriptorWrites &writes,
-								const ImageManager &imageManager,
-								const BufferManager &bufferManager,
-								const SamplerManager &samplerManager);
-
-		[[nodiscard]]
-		DescriptorSetLayout getDescriptorSetLayout(const DescriptorSetLayoutHandle handle) const;
-		
-		[[nodiscard]]
-		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
-
-	private:
-		vk::Device m_Device;
-		std::vector<vk::DescriptorPool>	m_Pools;
-		std::vector<vk::DescriptorPoolSize> m_PoolSizes;
-		vk::DescriptorPoolCreateInfo m_PoolInfo;
-
-		/**
-         * Contains all the descriptor set layout descriptions
-         * that were requested by the user in calls of createDescriptorSetLayout.
-         */
-        std::vector<DescriptorSetLayout> m_DescriptorSetLayouts;
-
-        /**
-		 * Contains all the descriptor sets that were created by the user in calls of createDescriptorSet.
-		 */
-        std::vector<DescriptorSet> m_DescriptorSets;
-
-		/**
-		 * @brief Destroys a specific descriptor set.
-		 *
-		 * @param[in] the DescriptorSetHandle
-		 */
-		void destroyDescriptorSetById(uint64_t id);
-
-		/**
-         * @brief Revokes the usage of a specific descriptor set layout and
-         * destroys it once the usage count is at zero.
-         *
-         * @param[in] the DescriptorSetLayoutHandle
-         */
-		void destroyDescriptorSetLayoutById(uint64_t id);
-
-		/**
-		 * @brief Creates a descriptor pool based on the poolSizes and poolInfo defined in the
-		 * constructor is called initially in the constructor and then every time the pool runs
-		 * out memory.
-		 *
-		 * @return a DescriptorPool object
-		 */
-		vk::DescriptorPool allocateDescriptorPool();
-		
-	};
-	
-}
\ No newline at end of file
diff --git a/src/vkcv/DescriptorSetLayoutManager.cpp b/src/vkcv/DescriptorSetLayoutManager.cpp
new file mode 100644
index 00000000..f3122964
--- /dev/null
+++ b/src/vkcv/DescriptorSetLayoutManager.cpp
@@ -0,0 +1,109 @@
+#include "DescriptorSetLayoutManager.hpp"
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+	
+	uint64_t DescriptorSetLayoutManager::getIdFrom(const DescriptorSetLayoutHandle &handle) const {
+		return handle.getId();
+	}
+	
+	DescriptorSetLayoutHandle DescriptorSetLayoutManager::createById(uint64_t id,
+																	 const HandleDestroyFunction &destroy) {
+		return DescriptorSetLayoutHandle(id, destroy);
+	}
+	
+	void DescriptorSetLayoutManager::destroyById(uint64_t id) {
+		auto& layout = getById(id);
+		
+		if (layout.layoutUsageCount > 1) {
+			layout.layoutUsageCount--;
+			return;
+		} else {
+			layout.layoutUsageCount = 0;
+		}
+		
+		if (layout.vulkanHandle){
+			getCore().getContext().getDevice().destroy(layout.vulkanHandle);
+			layout.vulkanHandle = nullptr;
+		}
+	}
+	
+	DescriptorSetLayoutManager::DescriptorSetLayoutManager() noexcept :
+			HandleManager<DescriptorSetLayoutEntry, DescriptorSetLayoutHandle>() {}
+	
+	DescriptorSetLayoutManager::~DescriptorSetLayoutManager() noexcept {
+		for (uint64_t id = 0; id < getCount(); id++) {
+			// Resets the usage count to zero for destruction.
+			getById(id).layoutUsageCount = 0;
+		}
+		
+		clear();
+	}
+	
+	DescriptorSetLayoutHandle DescriptorSetLayoutManager::createDescriptorSetLayout(const DescriptorBindings &bindings) {
+		for (uint64_t id = 0; id < getCount(); id++) {
+			auto& layout = getById(id);
+			
+			if (layout.descriptorBindings.size() != bindings.size())
+				continue;
+			
+			if (layout.descriptorBindings == bindings) {
+				layout.layoutUsageCount++;
+				return createById(id, [&](uint64_t id) { destroyById(id); });
+			}
+		}
+		
+		//create the descriptor set's layout and binding flags by iterating over its bindings
+		std::vector<vk::DescriptorSetLayoutBinding> bindingsVector = {};
+		std::vector<vk::DescriptorBindingFlags> bindingsFlags = {};
+		
+		for (auto bindingElem : bindings)
+		{
+			DescriptorBinding binding = bindingElem.second;
+			uint32_t bindingID = bindingElem.first;
+			
+			bindingsVector.emplace_back(
+					bindingID,
+					getVkDescriptorType(binding.descriptorType),
+					binding.descriptorCount,
+					getShaderStageFlags(binding.shaderStages),
+					nullptr
+			);
+			
+			vk::DescriptorBindingFlags flags;
+			
+			if (binding.variableCount)
+				flags |= vk::DescriptorBindingFlagBits::eVariableDescriptorCount;
+			
+			if (binding.partialBinding)
+				flags |= vk::DescriptorBindingFlagBits::ePartiallyBound;
+			
+			bindingsFlags.push_back(flags);
+		}
+		
+		vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo (
+				bindingsFlags.size(), bindingsFlags.data()
+		);
+		
+		//create the descriptor set's layout from the binding data gathered above
+		vk::DescriptorSetLayout vulkanHandle;
+		vk::DescriptorSetLayoutCreateInfo layoutInfo(vk::DescriptorSetLayoutCreateFlags(), bindingsVector);
+		layoutInfo.setPNext(&bindingFlagsInfo);
+		
+		auto result = getCore().getContext().getDevice().createDescriptorSetLayout(&layoutInfo,
+																				   nullptr,
+																				   &vulkanHandle);
+		if (result != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
+			return DescriptorSetLayoutHandle();
+		};
+		
+		return add({ vulkanHandle, bindings, 1 });
+	}
+	
+	const DescriptorSetLayoutEntry& DescriptorSetLayoutManager::getDescriptorSetLayout(const DescriptorSetLayoutHandle& handle) const {
+		return (*this)[handle];
+	}
+	
+}
diff --git a/src/vkcv/DescriptorSetLayoutManager.hpp b/src/vkcv/DescriptorSetLayoutManager.hpp
new file mode 100644
index 00000000..0dd2598c
--- /dev/null
+++ b/src/vkcv/DescriptorSetLayoutManager.hpp
@@ -0,0 +1,70 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann, Tobias Frisch
+ * @file src/vkcv/DescriptorManager.cpp
+ * @brief Creation and handling of descriptor set layouts.
+ */
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/DescriptorBinding.hpp"
+
+#include "HandleManager.hpp"
+
+namespace vkcv {
+	
+	/**
+	 * @brief Structure to store details about a descriptor set layout.
+	 */
+	struct DescriptorSetLayoutEntry {
+		vk::DescriptorSetLayout vulkanHandle;
+		DescriptorBindings descriptorBindings;
+		size_t layoutUsageCount;
+	};
+	
+	/**
+	 * @brief Class to manage descriptor set layouts.
+	 */
+	class DescriptorSetLayoutManager : public HandleManager<DescriptorSetLayoutEntry, DescriptorSetLayoutHandle> {
+		friend class Core;
+	private:
+		[[nodiscard]]
+		uint64_t getIdFrom(const DescriptorSetLayoutHandle& handle) const override;
+		
+		[[nodiscard]]
+		DescriptorSetLayoutHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
+		
+		/**
+		 * Destroys and deallocates descriptor set layout represented by a given
+		 * descriptor set layout handle id.
+		 *
+		 * @param id Descriptor set layout handle id
+		 */
+		void destroyById(uint64_t id) override;
+	
+	public:
+		/**
+		 * @brief Constructor of the descriptor set layout manager
+		 */
+		DescriptorSetLayoutManager() noexcept;
+		
+		/**
+		 * @brief Destructor of the descriptor set layout manager
+		 */
+		~DescriptorSetLayoutManager() noexcept override;
+		
+		/**
+		 * @brief Creates a descriptor set layout with given descriptor bindings
+		 * or returns a matching handle.
+		 *
+		 * @param[in] bindings Descriptor bindings
+		 * @return Handle of descriptor set layout
+		 */
+		[[nodiscard]]
+		DescriptorSetLayoutHandle createDescriptorSetLayout(const DescriptorBindings &bindings);
+		
+		[[nodiscard]]
+		const DescriptorSetLayoutEntry& getDescriptorSetLayout(const DescriptorSetLayoutHandle& handle) const;
+		
+	};
+	
+}
diff --git a/src/vkcv/DescriptorSetManager.cpp b/src/vkcv/DescriptorSetManager.cpp
new file mode 100644
index 00000000..31432bf2
--- /dev/null
+++ b/src/vkcv/DescriptorSetManager.cpp
@@ -0,0 +1,314 @@
+#include "DescriptorSetManager.hpp"
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+	
+	bool DescriptorSetManager::init(Core &core, DescriptorSetLayoutManager& descriptorSetLayoutManager) {
+		if (!HandleManager<DescriptorSetEntry, DescriptorSetHandle>::init(core)) {
+			return false;
+		}
+		
+		m_DescriptorSetLayoutManager = &descriptorSetLayoutManager;
+		
+		/**
+         * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type below.
+		 * Finally, create an initial pool.
+         */
+		m_PoolSizes = {
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000),    // for RTX
+				vk::DescriptorPoolSize(vk::DescriptorType::eAccelerationStructureKHR, 1000) // for RTX
+		};
+		
+		m_PoolInfo = vk::DescriptorPoolCreateInfo(
+				vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
+				1000,
+				static_cast<uint32_t>(m_PoolSizes.size()),
+				m_PoolSizes.data()
+		);
+		
+		return allocateDescriptorPool();
+	}
+	
+	uint64_t DescriptorSetManager::getIdFrom(const DescriptorSetHandle &handle) const {
+		return handle.getId();
+	}
+	
+	DescriptorSetHandle DescriptorSetManager::createById(uint64_t id,
+														 const HandleDestroyFunction &destroy) {
+		return DescriptorSetHandle(id, destroy);
+	}
+	
+	void DescriptorSetManager::destroyById(uint64_t id) {
+		auto& set = getById(id);
+		
+		if (set.vulkanHandle) {
+			getCore().getContext().getDevice().freeDescriptorSets(m_Pools[set.poolIndex], 1,
+																  &(set.vulkanHandle));
+			set.setLayoutHandle = DescriptorSetLayoutHandle();
+			set.vulkanHandle = nullptr;
+		}
+	}
+	
+	vk::DescriptorPool DescriptorSetManager::allocateDescriptorPool() {
+		vk::DescriptorPool pool;
+		if (getCore().getContext().getDevice().createDescriptorPool(&m_PoolInfo,
+																	nullptr,
+																	&pool) != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
+			pool = nullptr;
+		} else {
+			m_Pools.push_back(pool);
+		}
+		
+		return pool;
+	}
+	
+	DescriptorSetManager::DescriptorSetManager() noexcept :
+			HandleManager<DescriptorSetEntry, DescriptorSetHandle>() {
+		
+	}
+	
+	DescriptorSetManager::~DescriptorSetManager() noexcept {
+		clear();
+		
+		for (const auto &pool : m_Pools) {
+			if (pool) {
+				getCore().getContext().getDevice().destroy(pool);
+			}
+		}
+	}
+	
+	DescriptorSetHandle DescriptorSetManager::createDescriptorSet(const DescriptorSetLayoutHandle &layout) {
+		//create and allocate the set based on the layout provided
+		const auto& setLayout = m_DescriptorSetLayoutManager->getDescriptorSetLayout(layout);
+		
+		vk::DescriptorSet vulkanHandle;
+		vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle);
+		
+		uint32_t sumVariableDescriptorCounts = 0;
+		for (auto bindingElem : setLayout.descriptorBindings) {
+			auto binding = bindingElem.second;
+			
+			if(binding.variableCount)
+				sumVariableDescriptorCounts += binding.descriptorCount;
+		}
+		
+		vk::DescriptorSetVariableDescriptorCountAllocateInfo variableAllocInfo(1, &sumVariableDescriptorCounts);
+		
+		if (sumVariableDescriptorCounts > 0) {
+			allocInfo.setPNext(&variableAllocInfo);
+		}
+		
+		auto result = getCore().getContext().getDevice().allocateDescriptorSets(&allocInfo, &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 = getCore().getContext().getDevice().allocateDescriptorSets(&allocInfo, &vulkanHandle);
+			}
+			
+			if (result != vk::Result::eSuccess) {
+				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
+						 vk::to_string(result).c_str());
+				return {};
+			}
+		};
+		
+		size_t poolIndex = (m_Pools.size() - 1);
+		return add({ vulkanHandle, layout, poolIndex });
+	}
+	
+	/**
+	 * @brief Structure to store details to write to a descriptor set.
+	 */
+	struct WriteDescriptorSetInfo {
+		size_t imageInfoIndex;
+		size_t bufferInfoIndex;
+		uint32_t binding;
+		uint32_t arrayElementIndex;
+		uint32_t descriptorCount;
+		vk::DescriptorType type;
+	};
+	
+	void DescriptorSetManager::writeDescriptorSet(const DescriptorSetHandle	&handle,
+												  const DescriptorWrites	&writes,
+												  const ImageManager		&imageManager,
+												  const BufferManager		&bufferManager,
+												  const SamplerManager	&samplerManager) {
+		auto& set = (*this)[handle];
+		
+		std::vector<vk::DescriptorImageInfo> imageInfos;
+		std::vector<vk::DescriptorBufferInfo> bufferInfos;
+		
+		std::vector<WriteDescriptorSetInfo> writeInfos;
+		
+		for (const auto& write : writes.getSampledImageWrites()) {
+			const vk::ImageLayout layout = (write.useGeneralLayout?
+											vk::ImageLayout::eGeneral :
+											vk::ImageLayout::eShaderReadOnlyOptimal
+			);
+			
+			for (uint32_t i = 0; i < write.mipCount; i++) {
+				const vk::DescriptorImageInfo imageInfo(
+						nullptr,
+						imageManager.getVulkanImageView(
+								write.image,
+								write.mipLevel + i,
+								write.arrayView
+						),
+						layout
+				);
+				
+				imageInfos.push_back(imageInfo);
+			}
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					imageInfos.size() + 1 - write.mipCount,
+					0,
+					write.binding,
+					write.arrayIndex,
+					write.mipCount,
+					vk::DescriptorType::eSampledImage,
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		for (const auto& write : writes.getStorageImageWrites()) {
+			for (uint32_t i = 0; i < write.mipCount; i++) {
+				const vk::DescriptorImageInfo imageInfo(
+						nullptr,
+						imageManager.getVulkanImageView(
+								write.image,
+								write.mipLevel + i,
+								write.arrayView
+						),
+						vk::ImageLayout::eGeneral
+				);
+				
+				imageInfos.push_back(imageInfo);
+			}
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					imageInfos.size() + 1 - write.mipCount,
+					0,
+					write.binding,
+					0,
+					write.mipCount,
+					vk::DescriptorType::eStorageImage
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		for (const auto& write : writes.getUniformBufferWrites()) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+			
+			const vk::DescriptorBufferInfo bufferInfo(
+					bufferManager.getBuffer(write.buffer),
+					offset,
+					write.size == 0? size : std::min<uint32_t>(
+							write.size, size - offset
+					)
+			);
+			
+			bufferInfos.push_back(bufferInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					0,
+					bufferInfos.size(),
+					write.binding,
+					0,
+					1,
+					write.dynamic?
+					vk::DescriptorType::eUniformBufferDynamic :
+					vk::DescriptorType::eUniformBuffer
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		for (const auto& write : writes.getStorageBufferWrites()) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+			
+			const vk::DescriptorBufferInfo bufferInfo(
+					bufferManager.getBuffer(write.buffer),
+					offset,
+					write.size == 0? size : std::min<uint32_t>(
+							write.size, size - offset
+					)
+			);
+			
+			bufferInfos.push_back(bufferInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					0,
+					bufferInfos.size(),
+					write.binding,
+					0,
+					1,
+					write.dynamic?
+					vk::DescriptorType::eStorageBufferDynamic :
+					vk::DescriptorType::eStorageBuffer
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		for (const auto& write : writes.getSamplerWrites()) {
+			const vk::Sampler& sampler = samplerManager.getVulkanSampler(write.sampler);
+			
+			const vk::DescriptorImageInfo imageInfo(
+					sampler,
+					nullptr,
+					vk::ImageLayout::eGeneral
+			);
+			
+			imageInfos.push_back(imageInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					imageInfos.size(),
+					0,
+					write.binding,
+					0,
+					1,
+					vk::DescriptorType::eSampler
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		std::vector<vk::WriteDescriptorSet> vulkanWrites;
+		
+		for (const auto& write : writeInfos) {
+			vk::WriteDescriptorSet vulkanWrite (
+					set.vulkanHandle,
+					write.binding,
+					write.arrayElementIndex,
+					write.descriptorCount,
+					write.type,
+					(write.imageInfoIndex > 0? &(imageInfos[write.imageInfoIndex - 1]) : nullptr),
+					(write.bufferInfoIndex > 0? &(bufferInfos[write.bufferInfoIndex - 1]) : nullptr)
+			);
+			
+			vulkanWrites.push_back(vulkanWrite);
+		}
+		
+		getCore().getContext().getDevice().updateDescriptorSets(vulkanWrites, nullptr);
+	}
+	
+	const DescriptorSetEntry& DescriptorSetManager::getDescriptorSet(const DescriptorSetHandle& handle) const {
+		return (*this)[handle];
+	}
+	
+}
diff --git a/src/vkcv/DescriptorSetManager.hpp b/src/vkcv/DescriptorSetManager.hpp
new file mode 100644
index 00000000..f3c13aff
--- /dev/null
+++ b/src/vkcv/DescriptorSetManager.hpp
@@ -0,0 +1,106 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann, Tobias Frisch
+ * @file src/vkcv/DescriptorManager.cpp
+ * @brief Creation and handling of descriptor sets and the respective descriptor pools.
+ */
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/DescriptorBinding.hpp"
+#include "vkcv/DescriptorWrites.hpp"
+
+#include "HandleManager.hpp"
+#include "BufferManager.hpp"
+#include "DescriptorSetLayoutManager.hpp"
+#include "ImageManager.hpp"
+#include "SamplerManager.hpp"
+
+namespace vkcv {
+	
+	/**
+	 * @brief Structure to store details about a descriptor set.
+	 */
+	struct DescriptorSetEntry {
+		vk::DescriptorSet vulkanHandle;
+		DescriptorSetLayoutHandle setLayoutHandle;
+		size_t poolIndex;
+	};
+	
+	/**
+	 * @brief Class to manage descriptor sets.
+	 */
+	class DescriptorSetManager : public HandleManager<DescriptorSetEntry, DescriptorSetHandle> {
+		friend class Core;
+	private:
+		DescriptorSetLayoutManager* m_DescriptorSetLayoutManager;
+		
+		std::vector<vk::DescriptorPool>	m_Pools;
+		std::vector<vk::DescriptorPoolSize> m_PoolSizes;
+		vk::DescriptorPoolCreateInfo m_PoolInfo;
+		
+		bool init(Core& core, DescriptorSetLayoutManager& descriptorSetLayoutManager);
+		
+		[[nodiscard]]
+		uint64_t getIdFrom(const DescriptorSetHandle& handle) const override;
+		
+		[[nodiscard]]
+		DescriptorSetHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
+		
+		/**
+		 * Destroys and deallocates descriptor set represented by a given
+		 * descriptor set handle id.
+		 *
+		 * @param id Descriptor set handle id
+		 */
+		void destroyById(uint64_t id) override;
+		
+		/**
+		 * @brief Creates a descriptor pool based on the poolSizes and poolInfo defined in the
+		 * constructor is called initially in the constructor and then every time the pool runs
+		 * out memory.
+		 *
+		 * @return a DescriptorPool object
+		 */
+		vk::DescriptorPool allocateDescriptorPool();
+	
+	public:
+		/**
+		 * @brief Constructor of the descriptor set manager
+		 */
+		DescriptorSetManager() noexcept;
+		
+		/**
+		 * @brief Destructor of the descriptor set manager
+		 */
+		~DescriptorSetManager() noexcept override;
+		
+		/**
+		 * @brief Creates a descriptor set using a given descriptor set layout.
+		 *
+		 * @param[in] layout Handle of descriptor set layout
+		 * @return Handle of descriptor set
+		 */
+		[[nodiscard]]
+		DescriptorSetHandle createDescriptorSet(const DescriptorSetLayoutHandle &layout);
+		
+		/**
+		 * @brief Writes to a descriptor set using writes and all required managers.
+		 *
+		 * @param[in] handle Handle of descriptor set
+		 * @param[in] writes Descriptor set writes
+		 * @param[in] imageManager Image manager
+		 * @param[in] bufferManager Buffer manager
+		 * @param[in] samplerManager Sampler manager
+		 */
+		void writeDescriptorSet(const DescriptorSetHandle &handle,
+								const DescriptorWrites &writes,
+								const ImageManager &imageManager,
+								const BufferManager &bufferManager,
+								const SamplerManager &samplerManager);
+		
+		[[nodiscard]]
+		const DescriptorSetEntry& getDescriptorSet(const DescriptorSetHandle& handle) const;
+		
+	};
+	
+}
diff --git a/src/vkcv/GraphicsPipelineManager.cpp b/src/vkcv/GraphicsPipelineManager.cpp
index 9f30d92e..09288ebf 100644
--- a/src/vkcv/GraphicsPipelineManager.cpp
+++ b/src/vkcv/GraphicsPipelineManager.cpp
@@ -1,21 +1,38 @@
 #include "GraphicsPipelineManager.hpp"
+
+#include "vkcv/Core.hpp"
 #include "vkcv/Image.hpp"
 #include "vkcv/Logger.hpp"
 
-namespace vkcv
-{
+namespace vkcv {
 	
-	GraphicsPipelineManager::GraphicsPipelineManager(vk::Device device, vk::PhysicalDevice physicalDevice) noexcept :
-            m_Device(device),
-            m_physicalDevice(physicalDevice),
-            m_Pipelines{}
-    {}
+	uint64_t GraphicsPipelineManager::getIdFrom(const GraphicsPipelineHandle &handle) const {
+		return handle.getId();
+	}
 	
-	GraphicsPipelineManager::~GraphicsPipelineManager() noexcept
-    {
-        for (uint64_t id = 0; id < m_Pipelines.size(); id++) {
-            destroyPipelineById(id);
-        }
+	GraphicsPipelineHandle GraphicsPipelineManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return GraphicsPipelineHandle(id, destroy);
+	}
+	
+	void GraphicsPipelineManager::destroyById(uint64_t id) {
+		auto& pipeline = getById(id);
+		
+		if (pipeline.m_handle) {
+			getCore().getContext().getDevice().destroy(pipeline.m_handle);
+			pipeline.m_handle = nullptr;
+		}
+		
+		if (pipeline.m_layout) {
+			getCore().getContext().getDevice().destroy(pipeline.m_layout);
+			pipeline.m_layout = nullptr;
+		}
+	}
+	
+	GraphicsPipelineManager::GraphicsPipelineManager() noexcept :
+		HandleManager<GraphicsPipelineEntry, GraphicsPipelineHandle>() {}
+	
+	GraphicsPipelineManager::~GraphicsPipelineManager() noexcept {
+		clear();
     }
 
     // currently assuming default 32 bit formats, no lower precision or normalized variants supported
@@ -433,7 +450,7 @@ namespace vkcv
 
     GraphicsPipelineHandle GraphicsPipelineManager::createPipeline(const GraphicsPipelineConfig &config,
 																   const PassManager& passManager,
-																   const DescriptorManager& descriptorManager) {
+																   const DescriptorSetLayoutManager& descriptorManager) {
         const vk::RenderPass &pass = passManager.getVkPass(config.getPass());
 
 		const auto& program = config.getShaderProgram();
@@ -451,21 +468,20 @@ namespace vkcv
 				(existsTaskShader && existsMeshShader)
 		);
 
-        if (!validGeometryStages)
-        {
+        if (!validGeometryStages) {
             vkcv_log(LogLevel::ERROR, "Requires vertex or task and mesh shader");
-            return GraphicsPipelineHandle();
+            return {};
         }
 
         if (!existsFragmentShader) {
             vkcv_log(LogLevel::ERROR, "Requires fragment shader code");
-            return GraphicsPipelineHandle();
+            return {};
         }
 
         std::vector<vk::PipelineShaderStageCreateInfo> shaderStages;
         auto destroyShaderModules = [&shaderStages, this] {
             for (auto stage : shaderStages) {
-                m_Device.destroyShaderModule(stage.module);
+				getCore().getContext().getDevice().destroyShaderModule(stage.module);
             }
             shaderStages.clear();
         };
@@ -475,15 +491,14 @@ namespace vkcv
             const bool success = createPipelineShaderStageCreateInfo(
 					program,
                     ShaderStage::VERTEX,
-                    m_Device,
+					getCore().getContext().getDevice(),
                     &createInfo);
 
             if (success) {
                 shaderStages.push_back(createInfo);
-            }
-            else {
+            } else {
                 destroyShaderModules();
-                return GraphicsPipelineHandle();
+                return {};
             }
         }
 
@@ -492,15 +507,14 @@ namespace vkcv
             const bool success = createPipelineShaderStageCreateInfo(
 					program,
                     ShaderStage::TASK,
-                    m_Device,
+					getCore().getContext().getDevice(),
                     &createInfo);
 
             if (success) {
                 shaderStages.push_back(createInfo);
-            }
-            else {
+            } else {
                 destroyShaderModules();
-                return GraphicsPipelineHandle();
+                return {};
             }
         }
 
@@ -509,15 +523,14 @@ namespace vkcv
             const bool success = createPipelineShaderStageCreateInfo(
 					program,
                     ShaderStage::MESH,
-                    m_Device,
+					getCore().getContext().getDevice(),
                     &createInfo);
 
             if (success) {
                 shaderStages.push_back(createInfo);
-            }
-            else {
+            } else {
                 destroyShaderModules();
-                return GraphicsPipelineHandle();
+                return {};
             }
         }
 
@@ -526,15 +539,14 @@ namespace vkcv
             const bool success = createPipelineShaderStageCreateInfo(
 					program,
                     ShaderStage::FRAGMENT,
-                    m_Device,
+					getCore().getContext().getDevice(),
                     &createInfo);
 
             if (success) {
                 shaderStages.push_back(createInfo);
-            }
-            else {
+            } else {
                 destroyShaderModules();
-                return GraphicsPipelineHandle();
+                return {};
             }
         }
 
@@ -543,15 +555,14 @@ namespace vkcv
             const bool success = createPipelineShaderStageCreateInfo(
 					program,
                     ShaderStage::GEOMETRY,
-                    m_Device,
+					getCore().getContext().getDevice(),
                     &createInfo);
 
             if (success) {
                 shaderStages.push_back(createInfo);
-            }
-            else {
+            } else {
                 destroyShaderModules();
-                return GraphicsPipelineHandle();
+                return {};
             }
         }
 	
@@ -560,15 +571,14 @@ namespace vkcv
 			const bool success = createPipelineShaderStageCreateInfo(
 					program,
 					ShaderStage::TESS_CONTROL,
-					m_Device,
+					getCore().getContext().getDevice(),
 					&createInfo);
 		
 			if (success) {
 				shaderStages.push_back(createInfo);
-			}
-			else {
+			} else {
 				destroyShaderModules();
-				return GraphicsPipelineHandle();
+				return {};
 			}
 		}
 	
@@ -577,15 +587,14 @@ namespace vkcv
 			const bool success = createPipelineShaderStageCreateInfo(
 					program,
 					ShaderStage::TESS_EVAL,
-					m_Device,
+					getCore().getContext().getDevice(),
 					&createInfo);
 		
 			if (success) {
 				shaderStages.push_back(createInfo);
-			}
-			else {
+			} else {
 				destroyShaderModules();
-				return GraphicsPipelineHandle();
+				return {};
 			}
 		}
 
@@ -617,7 +626,7 @@ namespace vkcv
         vk::PhysicalDeviceProperties                                deviceProperties;
         vk::PhysicalDeviceProperties2                               deviceProperties2(deviceProperties);
         deviceProperties2.pNext = &conservativeRasterProperties;
-        m_physicalDevice.getProperties2(&deviceProperties2);
+		getCore().getContext().getPhysicalDevice().getProperties2(&deviceProperties2);
         vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo =
                 createPipelineRasterizationStateCreateInfo(config, conservativeRasterProperties);
 
@@ -644,9 +653,11 @@ namespace vkcv
                 createPipelineLayoutCreateInfo(config, descriptorSetLayouts);
 
         vk::PipelineLayout vkPipelineLayout{};
-        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess) {
+        if (getCore().getContext().getDevice().createPipelineLayout(&pipelineLayoutCreateInfo,
+																	nullptr,
+																	&vkPipelineLayout) != vk::Result::eSuccess) {
             destroyShaderModules();
-            return GraphicsPipelineHandle();
+            return {};
         }
 
         // Depth Stencil
@@ -685,77 +696,38 @@ namespace vkcv
         );
 
         vk::Pipeline vkPipeline{};
-        if (m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess)
+        if (getCore().getContext().getDevice().createGraphicsPipelines(nullptr,
+																	   1,
+																	   &graphicsPipelineCreateInfo,
+																	   nullptr,
+																	   &vkPipeline) != vk::Result::eSuccess)
         {
             // Catch runtime error if the creation of the pipeline fails.
             // Destroy everything to keep the memory clean.
             destroyShaderModules();
-            return GraphicsPipelineHandle();
+            return {};
         }
 
         // Clean Up
         destroyShaderModules();
 
         // Hand over Handler to main Application
-        const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config });
-        return GraphicsPipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
+        return add({ vkPipeline, vkPipelineLayout, config });
     }
 
-    vk::Pipeline GraphicsPipelineManager::getVkPipeline(const GraphicsPipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-
-        if (id >= m_Pipelines.size()) {
-            return nullptr;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
+    vk::Pipeline GraphicsPipelineManager::getVkPipeline(const GraphicsPipelineHandle &handle) const {
+        auto& pipeline = (*this)[handle];
         return pipeline.m_handle;
     }
 
-    vk::PipelineLayout GraphicsPipelineManager::getVkPipelineLayout(const GraphicsPipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-
-        if (id >= m_Pipelines.size()) {
-            return nullptr;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
+    vk::PipelineLayout GraphicsPipelineManager::getVkPipelineLayout(const GraphicsPipelineHandle &handle) const {
+		auto& pipeline = (*this)[handle];
         return pipeline.m_layout;
     }
 
-    void GraphicsPipelineManager::destroyPipelineById(uint64_t id) {
-        if (id >= m_Pipelines.size()) {
-            return;
-        }
-
-        auto& pipeline = m_Pipelines[id];
-
-        if (pipeline.m_handle) {
-            m_Device.destroy(pipeline.m_handle);
-            pipeline.m_handle = nullptr;
-        }
-
-        if (pipeline.m_layout) {
-            m_Device.destroy(pipeline.m_layout);
-            pipeline.m_layout = nullptr;
-        }
-    }
-
-    const GraphicsPipelineConfig& GraphicsPipelineManager::getPipelineConfig(const GraphicsPipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-
-        if (id >= m_Pipelines.size()) {
-            static GraphicsPipelineConfig dummyConfig;
-            vkcv_log(LogLevel::ERROR, "Invalid handle");
-            return dummyConfig;
-        }
-
-        return m_Pipelines[id].m_config;
+    const GraphicsPipelineConfig& GraphicsPipelineManager::getPipelineConfig(const GraphicsPipelineHandle &handle) const {
+		auto& pipeline = (*this)[handle];
+        return pipeline.m_config;
     }
+	
 }
diff --git a/src/vkcv/GraphicsPipelineManager.hpp b/src/vkcv/GraphicsPipelineManager.hpp
index 511dcf47..be18a4c9 100644
--- a/src/vkcv/GraphicsPipelineManager.hpp
+++ b/src/vkcv/GraphicsPipelineManager.hpp
@@ -10,23 +10,42 @@
 
 #include <vulkan/vulkan.hpp>
 #include <vector>
-#include "vkcv/Handles.hpp"
+#include "HandleManager.hpp"
 #include "vkcv/GraphicsPipelineConfig.hpp"
 #include "PassManager.hpp"
-#include "DescriptorManager.hpp"
+#include "DescriptorSetLayoutManager.hpp"
 
-namespace vkcv
-{
+namespace vkcv {
+	
+	struct GraphicsPipelineEntry {
+		vk::Pipeline m_handle;
+		vk::PipelineLayout m_layout;
+		GraphicsPipelineConfig m_config;
+	};
 	
 	/**
 	 * @brief Class to manage graphics pipelines.
 	 */
-    class GraphicsPipelineManager
-    {
+    class GraphicsPipelineManager : public HandleManager<GraphicsPipelineEntry, GraphicsPipelineHandle> {
+	private:
+		[[nodiscard]]
+		uint64_t getIdFrom(const GraphicsPipelineHandle& handle) const override;
+	
+		[[nodiscard]]
+		GraphicsPipelineHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
+	
+		/**
+		 * Destroys and deallocates graphics pipeline represented by a given
+		 * graphics pipeline handle id.
+		 *
+		 * @param id Graphics pipeline handle id
+		 */
+		void destroyById(uint64_t id) override;
+		
     public:
-		GraphicsPipelineManager() = delete; // no default ctor
-        explicit GraphicsPipelineManager(vk::Device device, vk::PhysicalDevice physicalDevice) noexcept; // ctor
-        ~GraphicsPipelineManager() noexcept; // dtor
+        GraphicsPipelineManager() noexcept;
+		
+        ~GraphicsPipelineManager() noexcept override; // dtor
 	
 		GraphicsPipelineManager(const GraphicsPipelineManager &other) = delete; // copy-ctor
 		GraphicsPipelineManager(GraphicsPipelineManager &&other) = delete; // move-ctor;
@@ -46,7 +65,7 @@ namespace vkcv
          */
 		GraphicsPipelineHandle createPipeline(const GraphicsPipelineConfig &config,
 											  const PassManager& passManager,
-											  const DescriptorManager& descriptorManager);
+											  const DescriptorSetLayoutManager& descriptorManager);
 
         /**
          * Returns a vk::Pipeline object by handle.
@@ -71,20 +90,7 @@ namespace vkcv
          */
         [[nodiscard]]
         const GraphicsPipelineConfig &getPipelineConfig(const GraphicsPipelineHandle &handle) const;
-
-    private:
-        struct GraphicsPipeline {
-            vk::Pipeline m_handle;
-            vk::PipelineLayout m_layout;
-			GraphicsPipelineConfig m_config;
-        };
-
-        vk::Device                      m_Device;
-        vk::PhysicalDevice              m_physicalDevice; // needed to get infos to configure conservative rasterization
-        std::vector<GraphicsPipeline>   m_Pipelines;
-
-        void destroyPipelineById(uint64_t id);
-
+		
     };
 	
 }
diff --git a/src/vkcv/HandleManager.hpp b/src/vkcv/HandleManager.hpp
new file mode 100644
index 00000000..13213dc2
--- /dev/null
+++ b/src/vkcv/HandleManager.hpp
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <optional>
+#include <vector>
+
+#include "vkcv/Handles.hpp"
+#include "vkcv/Logger.hpp"
+
+namespace vkcv {
+	
+	class Core;
+
+	template<typename T, typename H = Handle>
+	class HandleManager {
+		friend class Core;
+	private:
+		Core* m_core;
+		std::vector<T> m_entries;
+		
+	protected:
+		HandleManager() noexcept : m_core(nullptr), m_entries() {}
+		
+		virtual bool init(Core& core) {
+			if (m_core) {
+				vkcv_log(vkcv::LogLevel::ERROR, "Manager is already initialized");
+				return false;
+			}
+			
+			if (!m_entries.empty()) {
+				vkcv_log(vkcv::LogLevel::WARNING, "Entries added before initialization will be erased");
+			}
+			
+			m_core = &core;
+			m_entries.clear();
+			return true;
+		}
+		
+		[[nodiscard]]
+		const Core& getCore() const {
+			return *m_core;
+		}
+		
+		[[nodiscard]]
+		Core& getCore() {
+			return *m_core;
+		}
+		
+		[[nodiscard]]
+		size_t getCount() const {
+			return m_entries.size();
+		}
+		
+		[[nodiscard]]
+		const T& getById(uint64_t id) const {
+			if (id >= m_entries.size()) {
+				static T invalid;
+				vkcv_log(vkcv::LogLevel::ERROR, "Invalid handle id");
+				return invalid;
+			}
+			
+			return m_entries[id];
+		}
+		
+		[[nodiscard]]
+		T& getById(uint64_t id) {
+			if (id >= m_entries.size()) {
+				static T invalid;
+				vkcv_log(vkcv::LogLevel::ERROR, "Invalid handle id");
+				return invalid;
+			}
+			
+			return m_entries[id];
+		}
+		
+		virtual uint64_t getIdFrom(const H& handle) const = 0;
+		
+		[[nodiscard]]
+		virtual const T& operator[](const H& handle) const {
+			const Handle& _handle = handle;
+			return getById(getIdFrom(static_cast<const H&>(_handle)));
+		}
+		
+		[[nodiscard]]
+		virtual T& operator[](const H& handle) {
+			const Handle& _handle = handle;
+			return getById(getIdFrom(static_cast<const H&>(_handle)));
+		}
+		
+		H add(const T &entry) {
+			const uint64_t id = m_entries.size();
+			m_entries.push_back(entry);
+			return createById(id, [&](uint64_t id) { destroyById(id); });
+		}
+		
+		virtual H createById(uint64_t id, const HandleDestroyFunction& destroy) = 0;
+		
+		virtual void destroyById(uint64_t id) = 0;
+		
+		void clear() {
+			for (uint64_t id = 0; id < m_entries.size(); id++) {
+				destroyById(id);
+			}
+		}
+		
+	public:
+		HandleManager(HandleManager&& other) = delete;
+		HandleManager(const HandleManager& other) = delete;
+		
+		HandleManager& operator=(HandleManager&& other) = delete;
+		HandleManager& operator=(const HandleManager& other) = delete;
+		
+		virtual ~HandleManager() noexcept = default;
+		
+	};
+
+}
diff --git a/src/vkcv/ImageConfig.cpp b/src/vkcv/ImageConfig.cpp
index 80aeac3d..5fe3f0a9 100644
--- a/src/vkcv/ImageConfig.cpp
+++ b/src/vkcv/ImageConfig.cpp
@@ -2,6 +2,7 @@
 #include <vkcv/Logger.hpp>
 
 namespace vkcv {
+	
 	vk::SampleCountFlagBits msaaToVkSampleCountFlag(Multisampling msaa) {
 		switch (msaa) {
 		case Multisampling::None:   return vk::SampleCountFlagBits::e1;
@@ -21,4 +22,5 @@ namespace vkcv {
 		default: vkcv_log(vkcv::LogLevel::ERROR, "Unknown Multisampling enum setting"); return 1;
 		}
 	}
+	
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index b6d5a1f4..fe01a57b 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -11,46 +11,139 @@
 #include <algorithm>
 
 namespace vkcv {
-
-	/**
-	 * @brief searches memory type index for image allocation, combines requirements of image and application
-	 * @param physicalMemoryProperties Memory Properties of physical device
-	 * @param typeBits Bit field for suitable memory types
-	 * @param requirements Property flags that are required
-	 * @return memory type index for image
-	 */
-	uint32_t searchImageMemoryType(const vk::PhysicalDeviceMemoryProperties& physicalMemoryProperties, uint32_t typeBits, vk::MemoryPropertyFlags requirements) {
-		const uint32_t memoryCount = physicalMemoryProperties.memoryTypeCount;
-		for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) {
-			const uint32_t memoryTypeBits = (1 << memoryIndex);
-			const bool isRequiredMemoryType = typeBits & memoryTypeBits;
-
-			const vk::MemoryPropertyFlags properties =
-				physicalMemoryProperties.memoryTypes[memoryIndex].propertyFlags;
-			const bool hasRequiredProperties =
-				(properties & requirements) == requirements;
-
-			if (isRequiredMemoryType && hasRequiredProperties)
-				return static_cast<int32_t>(memoryIndex);
+	
+	bool ImageManager::init(Core &core, BufferManager &bufferManager) {
+		if (!HandleManager<ImageEntry, ImageHandle>::init(core)) {
+			return false;
 		}
-
-		// failed to find memory type
-		return -1;
+		
+		m_bufferManager = &bufferManager;
+		m_swapchainImages.clear();
+		return true;
 	}
-
-	ImageManager::ImageManager(BufferManager& bufferManager) noexcept :
-		m_core(nullptr), m_bufferManager(bufferManager), m_images()
-	{
+	
+	uint64_t ImageManager::getIdFrom(const ImageHandle &handle) const {
+		return handle.getId();
 	}
-
-	ImageManager::~ImageManager() noexcept {
-		for (uint64_t id = 0; id < m_images.size(); id++) {
-			destroyImageById(id);
+	
+	ImageHandle ImageManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return ImageHandle(id, destroy);
+	}
+	
+	void ImageManager::destroyById(uint64_t id) {
+		auto& image = getById(id);
+		
+		const vk::Device& device = getCore().getContext().getDevice();
+		
+		for (auto& view : image.m_viewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
+		}
+		
+		for (auto& view : image.m_arrayViewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
+		}
+		
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
+		
+		if (image.m_handle) {
+			allocator.destroyImage(image.m_handle, image.m_allocation);
+			
+			image.m_handle = nullptr;
+			image.m_allocation = nullptr;
+		}
+	}
+	
+	const BufferManager &ImageManager::getBufferManager() const {
+		return *m_bufferManager;
+	}
+	
+	BufferManager &ImageManager::getBufferManager() {
+		return *m_bufferManager;
+	}
+	
+	void ImageManager::recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer,
+														   const ImageHandle& handle) {
+		auto& image = (*this)[handle];
+		recordImageLayoutTransition(handle, 0, 0, vk::ImageLayout::eGeneral, cmdBuffer);
+		
+		vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
+										  vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor;
+		
+		uint32_t srcWidth = image.m_width;
+		uint32_t srcHeight = image.m_height;
+		uint32_t srcDepth = image.m_depth;
+		
+		auto half = [](uint32_t in) {
+			return std::max<uint32_t>(in / 2, 1);
+		};
+		
+		uint32_t dstWidth = half(srcWidth);
+		uint32_t dstHeight = half(srcHeight);
+		uint32_t dstDepth = half(srcDepth);
+		
+		for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
+			uint32_t dstMip = srcMip + 1;
+			vk::ImageBlit region(
+					vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
+					vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
+			
+			cmdBuffer.blitImage(
+					image.m_handle,
+					vk::ImageLayout::eGeneral,
+					image.m_handle,
+					vk::ImageLayout::eGeneral,
+					region,
+					vk::Filter::eLinear);
+			
+			srcWidth = dstWidth;
+			srcHeight = dstHeight;
+			srcDepth = dstDepth;
+			
+			dstWidth = half(dstWidth);
+			dstHeight = half(dstHeight);
+			dstDepth = half(dstDepth);
+			
+			recordImageMemoryBarrier(handle, cmdBuffer);
+		}
+	}
+	
+	const ImageEntry &ImageManager::operator[](const ImageHandle &handle) const {
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages[m_currentSwapchainInputImage];
+		}
+		
+		return HandleManager<ImageEntry, ImageHandle>::operator[](handle);
+	}
+	
+	ImageEntry &ImageManager::operator[](const ImageHandle &handle) {
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages[m_currentSwapchainInputImage];
 		}
 		
+		return HandleManager<ImageEntry, ImageHandle>::operator[](handle);
+	}
+	
+	ImageManager::ImageManager() noexcept :
+			HandleManager<ImageEntry, ImageHandle>(),
+			m_bufferManager(nullptr),
+			m_swapchainImages(),
+			m_currentSwapchainInputImage(0)
+	{}
+	
+	ImageManager::~ImageManager() noexcept {
+		clear();
+		
 		for (const auto& swapchainImage : m_swapchainImages) {
 			for (const auto view : swapchainImage.m_viewPerMip) {
-				m_core->getContext().getDevice().destroy(view);
+				getCore().getContext().getDevice().destroy(view);
 			}
 		}
 	}
@@ -73,9 +166,8 @@ namespace vkcv {
 		uint32_t        mipCount,
 		bool            supportStorage, 
 		bool            supportColorAttachment,
-		Multisampling   msaa)
-	{
-		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
+		Multisampling   msaa) {
+		const vk::PhysicalDevice& physicalDevice = getCore().getContext().getPhysicalDevice();
 		
 		const vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(format);
 		
@@ -95,7 +187,7 @@ namespace vkcv {
 				imageTiling = vk::ImageTiling::eLinear;
 				
 				if (!(formatProperties.linearTilingFeatures & vk::FormatFeatureFlagBits::eStorageImage))
-					return ImageHandle();
+					return {};
 			}
 		}
 		
@@ -109,7 +201,7 @@ namespace vkcv {
 			imageUsageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
 		}
 
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 
 		vk::ImageType imageType = vk::ImageType::e3D;
 		vk::ImageViewType imageViewType = vk::ImageViewType::e3D;
@@ -135,7 +227,7 @@ namespace vkcv {
 		
 		if (!formatProperties.optimalTilingFeatures) {
 			if (!formatProperties.linearTilingFeatures)
-				return ImageHandle();
+				return {};
 			
 			imageTiling = vk::ImageTiling::eLinear;
 		}
@@ -190,7 +282,7 @@ namespace vkcv {
 			aspectFlags = vk::ImageAspectFlagBits::eColor;
 		}
 		
-		const vk::Device& device = m_core->getContext().getDevice();
+		const vk::Device& device = getCore().getContext().getDevice();
 		
 		std::vector<vk::ImageView> views;
 		std::vector<vk::ImageView> arrayViews;
@@ -243,25 +335,22 @@ namespace vkcv {
 			arrayViews.push_back(device.createImageView(imageViewCreateInfo));
 		}
 		
-		const uint64_t id = m_images.size();
-		m_images.push_back({
+		return add({
 			image,
 			allocation,
 			
 			views,
 			arrayViews,
-			
+	
 			width,
 			height,
 			depth,
-			
+	
 			format,
 			arrayLayers,
 			vk::ImageLayout::eUndefined,
 			supportStorage
 		});
-		
-		return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); });
 	}
 	
 	ImageHandle ImageManager::createSwapchainImage() const {
@@ -269,38 +358,18 @@ namespace vkcv {
 	}
 	
 	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
-
-		if (handle.isSwapchainImage()) {
-			m_swapchainImages[m_currentSwapchainInputImage].m_handle;
-		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
-		}
-		
-		auto& image = m_images[id];
-		
+		auto& image = (*this)[handle];
 		return image.m_handle;
 	}
 	
 	vk::DeviceMemory ImageManager::getVulkanDeviceMemory(const ImageHandle &handle) const {
-
 		if (handle.isSwapchainImage()) {
 			vkcv_log(LogLevel::ERROR, "Swapchain image has no memory");
 			return nullptr;
 		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
-		}
-		
-		auto& image = m_images[id];
 		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
+		auto& image = (*this)[handle];
+		const vma::Allocator& allocator = getCore().getContext().getAllocator();
 		
 		auto info = allocator.getAllocationInfo(
 				image.m_allocation
@@ -312,18 +381,11 @@ namespace vkcv {
 	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle,
 												   size_t mipLevel,
 												   bool arrayView) const {
-		
 		if (handle.isSwapchainImage()) {
 			return m_swapchainImages[m_currentSwapchainInputImage].m_viewPerMip[0];
 		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
-		}
 		
-		const auto& image = m_images[id];
+		const auto& image = (*this)[handle];
 		const auto& views = arrayView? image.m_arrayViewPerMip : image.m_viewPerMip;
 		
 		if (mipLevel >= views.size()) {
@@ -334,7 +396,7 @@ namespace vkcv {
 		return views[mipLevel];
 	}
 	
-	static vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageManager::Image &image,
+	static vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageEntry &image,
 																	 uint32_t mipLevelCount,
 																	 uint32_t mipLevelOffset,
 																	 vk::ImageLayout newLayout) {
@@ -375,22 +437,13 @@ namespace vkcv {
 	}
 	
 	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
-		uint64_t id = handle.getId();
-		
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		auto& image = (*this)[handle];
 		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, 0, 0, newLayout);
 		
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		
-		m_core->recordAndSubmitCommandsImmediate(
+		getCore().recordAndSubmitCommandsImmediate(
 			submitInfo,
 			[transitionBarrier](const vk::CommandBuffer& commandBuffer) {
 			// TODO: precise PipelineStageFlagBits, will require a lot of context
@@ -414,15 +467,7 @@ namespace vkcv {
 												   uint32_t mipLevelOffset,
 												   vk::ImageLayout newLayout,
 												   vk::CommandBuffer cmdBuffer) {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		auto& image = (*this)[handle];
 		const auto transitionBarrier = createImageLayoutTransitionBarrier(
 				image,
 				mipLevelCount,
@@ -442,19 +487,9 @@ namespace vkcv {
 		image.m_layout = newLayout;
 	}
 
-	void ImageManager::recordImageMemoryBarrier(
-		const ImageHandle& handle,
-		vk::CommandBuffer cmdBuffer) {
-
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			std::cerr << "Error: ImageManager::recordImageMemoryBarrier invalid handle" << std::endl;
-			return;
-		}
-
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+	void ImageManager::recordImageMemoryBarrier(const ImageHandle& handle,
+												vk::CommandBuffer cmdBuffer) {
+		auto& image = (*this)[handle];
 		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, 0, 0, image.m_layout);
 		
 		cmdBuffer.pipelineBarrier(
@@ -485,22 +520,13 @@ namespace vkcv {
 		}
 	}
 	
-	void ImageManager::fillImage(const ImageHandle& handle, const void* data, size_t size)
-	{
-		const uint64_t id = handle.getId();
-		
+	void ImageManager::fillImage(const ImageHandle& handle, const void* data, size_t size) {
 		if (handle.isSwapchainImage()) {
 			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
 			return;
 		}
-
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-		
-		auto& image = m_images[id];
 		
+		auto& image = (*this)[handle];
 		switchImageLayoutImmediate(
 				handle,
 				vk::ImageLayout::eTransferDstOptimal);
@@ -512,7 +538,7 @@ namespace vkcv {
 		
 		const size_t max_size = std::min(size, image_size);
 		
-		BufferHandle bufferHandle = m_bufferManager.createBuffer(
+		BufferHandle bufferHandle = getBufferManager().createBuffer(
 				TypeGuard(1),
 				BufferType::STAGING,
 				BufferMemoryType::DEVICE_LOCAL,
@@ -520,14 +546,14 @@ namespace vkcv {
 				false
 		);
 		
-		m_bufferManager.fillBuffer(bufferHandle, data, max_size, 0);
+		getBufferManager().fillBuffer(bufferHandle, data, max_size, 0);
 		
-		vk::Buffer stagingBuffer = m_bufferManager.getBuffer(bufferHandle);
+		vk::Buffer stagingBuffer = getBufferManager().getBuffer(bufferHandle);
 		
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		m_core->recordAndSubmitCommandsImmediate(
+		getCore().recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
 					vk::ImageAspectFlags aspectFlags;
@@ -569,89 +595,21 @@ namespace vkcv {
 		);
 	}
 
-	void ImageManager::recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle) {
-
-		const auto id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "Invalid image handle");
-			return;
-		}
-
-		auto& image = m_images[id];
-		recordImageLayoutTransition(handle, 0, 0, vk::ImageLayout::eGeneral, cmdBuffer);
-
-		vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
-			vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor;
-
-		uint32_t srcWidth = image.m_width;
-		uint32_t srcHeight = image.m_height;
-		uint32_t srcDepth = image.m_depth;
-
-		auto half = [](uint32_t in) {
-			return std::max<uint32_t>(in / 2, 1);
-		};
-
-		uint32_t dstWidth = half(srcWidth);
-		uint32_t dstHeight = half(srcHeight);
-		uint32_t dstDepth = half(srcDepth);
-
-		for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
-			uint32_t dstMip = srcMip + 1;
-			vk::ImageBlit region(
-				vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
-				{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
-				vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
-				{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
-
-			cmdBuffer.blitImage(
-				image.m_handle,
-				vk::ImageLayout::eGeneral,
-				image.m_handle,
-				vk::ImageLayout::eGeneral,
-				region,
-				vk::Filter::eLinear);
-
-			srcWidth = dstWidth;
-			srcHeight = dstHeight;
-			srcDepth = dstDepth;
-
-			dstWidth = half(dstWidth);
-			dstHeight = half(dstHeight);
-			dstDepth = half(dstDepth);
-
-			recordImageMemoryBarrier(handle, cmdBuffer);
-		}
-	}
-
-	void ImageManager::recordImageMipChainGenerationToCmdStream(
-		const vkcv::CommandStreamHandle& cmdStream,
-		const ImageHandle& handle) {
-
+	void ImageManager::recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream,
+																const ImageHandle& handle) {
 		const auto record = [this, handle](const vk::CommandBuffer cmdBuffer) {
 			recordImageMipGenerationToCmdBuffer(cmdBuffer, handle);
 		};
-		m_core->recordCommandsToStream(cmdStream, record, nullptr);
+		
+		getCore().recordCommandsToStream(cmdStream, record, nullptr);
 	}
 
-	void ImageManager::recordMSAAResolve(vk::CommandBuffer cmdBuffer, ImageHandle src, ImageHandle dst) {
-
-		const uint64_t srcId = src.getId();
-		const uint64_t dstId = dst.getId();
-
-		const bool isSrcSwapchainImage = src.isSwapchainImage();
-		const bool isDstSwapchainImage = dst.isSwapchainImage();
-
-		const bool isSrcHandleInvalid = srcId >= m_images.size() && !isSrcSwapchainImage;
-		const bool isDstHandleInvalid = dstId >= m_images.size() && !isDstSwapchainImage;
-
-		if (isSrcHandleInvalid || isDstHandleInvalid) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-
-		auto& srcImage = isSrcSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[srcId];
-		auto& dstImage = isDstSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[dstId];
-
+	void ImageManager::recordMSAAResolve(vk::CommandBuffer cmdBuffer,
+										 const ImageHandle& src,
+										 const ImageHandle& dst) {
+		auto& srcImage = (*this)[src];
+		auto& dstImage = (*this)[dst];
+		
 		vk::ImageResolve region(
 			vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1),
 			vk::Offset3D(0, 0, 0),
@@ -671,152 +629,62 @@ namespace vkcv {
 	}
 
 	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		
+		auto& image = (*this)[handle];
 		return image.m_width;
 	}
 	
 	uint32_t ImageManager::getImageHeight(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-		
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		
+		auto& image = (*this)[handle];
 		return image.m_height;
 	}
 	
 	uint32_t ImageManager::getImageDepth(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		
+		auto& image = (*this)[handle];
 		return image.m_depth;
 	}
-	
-	void ImageManager::destroyImageById(uint64_t id)
-	{
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-		
-		auto& image = m_images[id];
-
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		for (auto& view : image.m_viewPerMip) {
-			if (view) {
-				device.destroyImageView(view);
-				view = nullptr;
-			}
-		}
-		
-		for (auto& view : image.m_arrayViewPerMip) {
-			if (view) {
-				device.destroyImageView(view);
-				view = nullptr;
-			}
-		}
-		
-		const vma::Allocator& allocator = m_core->getContext().getAllocator();
-
-		if (image.m_handle) {
-			allocator.destroyImage(image.m_handle, image.m_allocation);
-			
-			image.m_handle = nullptr;
-			image.m_allocation = nullptr;
-		}
-	}
 
 	vk::Format ImageManager::getImageFormat(const ImageHandle& handle) const {
-
-		const uint64_t id = handle.getId();
-		const bool isSwapchainFormat = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainFormat) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return vk::Format::eUndefined;
-		}
-
-		return isSwapchainFormat ? m_swapchainImages[m_currentSwapchainInputImage].m_format : m_images[id].m_format;
+		auto& image = (*this)[handle];
+		return image.m_format;
 	}
 	
 	bool ImageManager::isImageSupportingStorage(const ImageHandle& handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainFormat = handle.isSwapchainImage();
-		
-		if (isSwapchainFormat) {
-			return false;
-		}
-		
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
+		if (handle.isSwapchainImage()) {
 			return false;
 		}
 		
-		return m_images[id].m_storage;
+		auto& image = (*this)[handle];
+		return image.m_storage;
 	}
 
 	uint32_t ImageManager::getImageMipCount(const ImageHandle& handle) const {
-		const uint64_t id = handle.getId();
-
 		if (handle.isSwapchainImage()) {
 			return 1;
 		}
-
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-
-		return m_images[id].m_viewPerMip.size();
+		
+		auto& image = (*this)[handle];
+		return image.m_viewPerMip.size();
 	}
 	
 	uint32_t ImageManager::getImageArrayLayers(const ImageHandle& handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (handle.isSwapchainImage()) {
-			return m_swapchainImages[0].m_layers;
-		}
-		
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		return m_images[id].m_layers;
+		auto& image = (*this)[handle];
+		return image.m_layers;
 	}
 
 	void ImageManager::setCurrentSwapchainImageIndex(int index) {
 		m_currentSwapchainInputImage = index;
 	}
 
-	void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, const std::vector<vk::ImageView>& views,
-		uint32_t width, uint32_t height, vk::Format format) {
+	void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images,
+										  const std::vector<vk::ImageView>& views,
+										  uint32_t width,
+										  uint32_t height,
+										  vk::Format format) {
 
 		// destroy old views
 		for (const auto& image : m_swapchainImages) {
 			for (const auto& view : image.m_viewPerMip) {
-				m_core->getContext().getDevice().destroyImageView(view);
+				getCore().getContext().getDevice().destroyImageView(view);
 			}
 		}
 
@@ -839,20 +707,10 @@ namespace vkcv {
 		}
 	}
 
-	void ImageManager::updateImageLayoutManual(const vkcv::ImageHandle& handle, const vk::ImageLayout layout) {
-		const uint64_t id = handle.getId();
-
-		if (handle.isSwapchainImage()) {
-			m_swapchainImages[m_currentSwapchainInputImage].m_layout = layout;
-		}
-		else {
-			if (id >= m_images.size()) {
-				vkcv_log(LogLevel::ERROR, "Invalid handle");
-				return;
-			}
-			m_swapchainImages[id].m_layout = layout;
-		}
-		
+	void ImageManager::updateImageLayoutManual(const vkcv::ImageHandle& handle,
+											   const vk::ImageLayout layout) {
+		auto& image = (*this)[handle];
+		image.m_layout = layout;
 	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index fca4f3b2..bda9a6a0 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -9,7 +9,7 @@
 #include <vk_mem_alloc.hpp>
 
 #include "BufferManager.hpp"
-#include "vkcv/Handles.hpp"
+#include "HandleManager.hpp"
 #include "vkcv/ImageConfig.hpp"
 
 namespace vkcv {
@@ -22,44 +22,44 @@ namespace vkcv {
 	 * @return True, if the format is usable for depth buffers, otherwise false.
 	 */
 	bool isDepthImageFormat(vk::Format format);
+	
+	struct ImageEntry {
+		vk::Image                   m_handle;
+		vma::Allocation             m_allocation;
+		
+		std::vector<vk::ImageView>  m_viewPerMip;
+		std::vector<vk::ImageView>  m_arrayViewPerMip;
+		
+		uint32_t                    m_width;
+		uint32_t                    m_height;
+		uint32_t                    m_depth;
+		
+		vk::Format                  m_format;
+		uint32_t                    m_layers;
+		vk::ImageLayout             m_layout;
+		bool 						m_storage;
+	};
 
 	/**
 	 * @brief Class to manage the creation, destruction, allocation
 	 * and filling of images.
 	 */
-	class ImageManager
+	class ImageManager : HandleManager<ImageEntry, ImageHandle>
 	{
 		friend class Core;
-	public:
-		struct Image
-		{
-			vk::Image                   m_handle;
-			vma::Allocation             m_allocation;
-			
-			std::vector<vk::ImageView>  m_viewPerMip;
-			std::vector<vk::ImageView>  m_arrayViewPerMip;
-			
-			uint32_t                    m_width;
-			uint32_t                    m_height;
-			uint32_t                    m_depth;
-			
-			vk::Format                  m_format;
-			uint32_t                    m_layers;
-			vk::ImageLayout             m_layout;
-			bool 						m_storage;
-		private:
-			friend ImageManager;
-		};
 	private:
+		BufferManager* m_bufferManager;
 		
-		Core* m_core;
-		BufferManager& m_bufferManager;
-		
-		std::vector<Image> m_images;
-		std::vector<Image> m_swapchainImages;
+		std::vector<ImageEntry> m_swapchainImages;
 		int m_currentSwapchainInputImage;
 		
-		explicit ImageManager(BufferManager& bufferManager) noexcept;
+		bool init(Core& core, BufferManager& bufferManager);
+		
+		[[nodiscard]]
+		uint64_t getIdFrom(const ImageHandle& handle) const override;
+		
+		[[nodiscard]]
+		ImageHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
 		
 		/**
 		 * Destroys and deallocates image represented by a given
@@ -67,18 +67,29 @@ namespace vkcv {
 		 *
 		 * @param id Image handle id
 		 */
-		void destroyImageById(uint64_t id);
+		void destroyById(uint64_t id) override;
+		
+		[[nodiscard]]
+		const BufferManager& getBufferManager() const;
+		
+		[[nodiscard]]
+		BufferManager& getBufferManager();
 
 		void recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle);
-
+		
+	protected:
+		[[nodiscard]]
+		virtual const ImageEntry& operator[](const ImageHandle& handle) const override;
+		
+		[[nodiscard]]
+		virtual ImageEntry& operator[](const ImageHandle& handle) override;
+		
 	public:
-		~ImageManager() noexcept;
-		ImageManager(ImageManager&& other) = delete;
-		ImageManager(const ImageManager& other) = delete;
-
-		ImageManager& operator=(ImageManager&& other) = delete;
-		ImageManager& operator=(const ImageManager& other) = delete;
+		ImageManager() noexcept;
+		
+		~ImageManager() noexcept override;
 		
+		[[nodiscard]]
 		ImageHandle createImage(
 			uint32_t        width, 
 			uint32_t        height, 
@@ -103,22 +114,28 @@ namespace vkcv {
 										 size_t mipLevel = 0,
 										 bool arrayView = false) const;
 
-		void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout);
-		
-		void recordImageLayoutTransition(
-			const ImageHandle& handle,
-			uint32_t mipLevelCount,
-			uint32_t mipLevelOffset,
-			vk::ImageLayout newLayout, 
-			vk::CommandBuffer cmdBuffer);
+		void switchImageLayoutImmediate(const ImageHandle& handle,
+										vk::ImageLayout newLayout);
+		
+		void recordImageLayoutTransition(const ImageHandle& handle,
+										 uint32_t mipLevelCount,
+										 uint32_t mipLevelOffset,
+										 vk::ImageLayout newLayout,
+										 vk::CommandBuffer cmdBuffer);
 
-		void recordImageMemoryBarrier(
-			const ImageHandle& handle,
-			vk::CommandBuffer cmdBuffer);
+		void recordImageMemoryBarrier(const ImageHandle& handle,
+									  vk::CommandBuffer cmdBuffer);
 
-		void fillImage(const ImageHandle& handle, const void* data, size_t size);
-		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream, const ImageHandle& handle);
-		void recordMSAAResolve(vk::CommandBuffer cmdBuffer, ImageHandle src, ImageHandle dst);
+		void fillImage(const ImageHandle& handle,
+					   const void* data,
+					   size_t size);
+		
+		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream,
+													  const ImageHandle& handle);
+		
+		void recordMSAAResolve(vk::CommandBuffer cmdBuffer,
+							   const ImageHandle& src,
+							   const ImageHandle& dst);
 
 		[[nodiscard]]
 		uint32_t getImageWidth(const ImageHandle& handle) const;
@@ -143,12 +160,16 @@ namespace vkcv {
 
 		void setCurrentSwapchainImageIndex(int index);
 		
-		void setSwapchainImages(const std::vector<vk::Image>& images, const std::vector<vk::ImageView>& views,
-								uint32_t width, uint32_t height, vk::Format format);
+		void setSwapchainImages(const std::vector<vk::Image>& images,
+								const std::vector<vk::ImageView>& views,
+								uint32_t width,
+								uint32_t height,
+								vk::Format format);
 
 		// if manual vulkan work, e.g. ImGui integration, changes an image layout this function must be used
 		// to update the internal image state
-		void updateImageLayoutManual(const vkcv::ImageHandle& handle, const vk::ImageLayout layout);
+		void updateImageLayoutManual(const vkcv::ImageHandle& handle,
+									 const vk::ImageLayout layout);
 
 	};
 }
\ No newline at end of file
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index 5eb727c7..20ba600c 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -1,5 +1,6 @@
 #include "PassManager.hpp"
 #include "vkcv/Image.hpp"
+#include "vkcv/Core.hpp"
 
 namespace vkcv
 {
@@ -27,21 +28,31 @@ namespace vkcv
                 return vk::AttachmentLoadOp::eDontCare;
         }
     }
-
-    PassManager::PassManager(vk::Device device) noexcept :
-    m_Device{device},
-    m_Passes{}
-    {}
-
-    PassManager::~PassManager() noexcept
-    {
-    	for (uint64_t id = 0; id < m_Passes.size(); id++) {
-			destroyPassById(id);
-    	}
+	
+	uint64_t PassManager::getIdFrom(const PassHandle &handle) const {
+		return handle.getId();
+	}
+	
+	PassHandle PassManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return PassHandle(id, destroy);
+	}
+	
+	void PassManager::destroyById(uint64_t id) {
+		auto& pass = getById(id);
+		
+		if (pass.m_Handle) {
+			getCore().getContext().getDevice().destroy(pass.m_Handle);
+			pass.m_Handle = nullptr;
+		}
+	}
+	
+	PassManager::PassManager() noexcept : HandleManager<PassEntry, PassHandle>() {}
+	
+	PassManager::~PassManager() noexcept {
+    	clear();
     }
 
-    PassHandle PassManager::createPass(const PassConfig &config)
-    {
+    PassHandle PassManager::createPass(const PassConfig &config) {
         // description of all {color, input, depth/stencil} attachments of the render pass
         std::vector<vk::AttachmentDescription> attachmentDescriptions{};
 
@@ -106,50 +117,19 @@ namespace vkcv
             0,
             {});
 
-        vk::RenderPass renderPass = m_Device.createRenderPass(passInfo);
+        vk::RenderPass renderPass = getCore().getContext().getDevice().createRenderPass(passInfo);
 
-        const uint64_t id = m_Passes.size();
-        m_Passes.push_back({ renderPass, config });
-        return PassHandle(id, [&](uint64_t id) { destroyPassById(id); });
+        return add({ renderPass, config });
     }
 
-    vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
-    {
-    	const uint64_t id = handle.getId();
-    	
-    	if (id >= m_Passes.size()) {
-    		return nullptr;
-    	}
-    	
-    	auto& pass = m_Passes[id];
-    	
+    vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const {
+    	auto& pass = (*this)[handle];
         return pass.m_Handle;
     }
     
     const PassConfig& PassManager::getPassConfig(const PassHandle &handle) const {
-		const uint64_t id = handle.getId();
-	
-		if (id >= m_Passes.size()) {
-			static PassConfig emptyConfig = PassConfig({});
-			return emptyConfig;
-		}
-	
-		auto& pass = m_Passes[id];
-	
+		auto& pass = (*this)[handle];
 		return pass.m_Config;
     }
     
-    void PassManager::destroyPassById(uint64_t id) {
-    	if (id >= m_Passes.size()) {
-    		return;
-    	}
-    	
-    	auto& pass = m_Passes[id];
-	
-		if (pass.m_Handle) {
-			m_Device.destroy(pass.m_Handle);
-			pass.m_Handle = nullptr;
-		}
-    }
-    
 }
diff --git a/src/vkcv/PassManager.hpp b/src/vkcv/PassManager.hpp
index 1ef976e0..3ab69b0c 100644
--- a/src/vkcv/PassManager.hpp
+++ b/src/vkcv/PassManager.hpp
@@ -2,39 +2,44 @@
 
 #include <vulkan/vulkan.hpp>
 #include <vector>
-#include "vkcv/Handles.hpp"
+
+#include "HandleManager.hpp"
 #include "vkcv/PassConfig.hpp"
 
 namespace vkcv
 {
 	
+	struct PassEntry {
+		vk::RenderPass m_Handle;
+		PassConfig m_Config;
+	};
+	
 	/**
 	 * @brief Class to manage the creation and destruction of passes.
 	 */
-    class PassManager
-    {
+    class PassManager : public HandleManager<PassEntry, PassHandle> {
+		friend class Core;
     private:
-    	struct Pass {
-			vk::RenderPass m_Handle;
-			PassConfig m_Config;
-    	};
-    	
-        vk::Device m_Device;
-        std::vector<Pass> m_Passes;
-        
-        void destroyPassById(uint64_t id);
+		[[nodiscard]]
+		uint64_t getIdFrom(const PassHandle& handle) const override;
+	
+		[[nodiscard]]
+		PassHandle createById(uint64_t id, const HandleDestroyFunction& destroy) override;
+	
+		/**
+		 * Destroys and deallocates pass represented by a given
+		 * pass handle id.
+		 *
+		 * @param id Pass handle id
+		 */
+		void destroyById(uint64_t id) override;
         
     public:
-        PassManager() = delete; // no default ctor
-        explicit PassManager(vk::Device device) noexcept; // ctor
-        ~PassManager() noexcept; // dtor
-
-        PassManager(const PassManager &other) = delete; // copy-ctor
-        PassManager(PassManager &&other) = delete; // move-ctor;
-
-        PassManager & operator=(const PassManager &other) = delete; // copy-assign op
-        PassManager & operator=(PassManager &&other) = delete; // move-assign op
-
+		PassManager() noexcept;
+		
+        ~PassManager() noexcept override; // dtor
+	
+		[[nodiscard]]
         PassHandle createPass(const PassConfig &config);
 
         [[nodiscard]]
-- 
GitLab