diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4079e35a9921c08885c0a04eae28449c9fcb629c..318f3e931e557421a5e9275735174cdee7947453 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,13 +1,24 @@
+variables:
+  RUN:
+    value: "all"
+    description: "The tests that should run. Possible values: ubuntu, win, all."
+  GIT_DEPTH: 1
+
 stages:
   - build
   - deploy
 
 build_ubuntu_gcc:
+  only:
+    variables:
+      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
     - ubuntu-gcc
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
+  timeout: 10m
+  retry: 1
   script:
     - mkdir debug
     - cd debug
@@ -21,11 +32,16 @@ build_ubuntu_gcc:
     expire_in: never
 
 build_win10_msvc:
+  only:
+    variables:
+      - $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
     - win10-msvc
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
+  timeout: 10m
+  retry: 1
   script:
     - cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\'
     - .\Launch-VsDevShell.ps1
@@ -37,6 +53,8 @@ build_win10_msvc:
 
 deploy_doc_develop:
   only:
+    variables:
+      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
     refs:
       - develop
   stage: deploy
@@ -52,6 +70,9 @@ deploy_doc_develop:
     - echo "Check it out at https://vkcv.de/develop"
 
 deploy_doc_branch:
+  only:
+    variables:
+      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
   except:
     refs:
       - develop
diff --git a/.gitmodules b/.gitmodules
index 4b9737e41ad03ecb661709c24531482f3d0e056d..983b753744e8767da0ec3c959c32a3766ee346f6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,9 @@
 [submodule "lib/glfw"]
 	path = lib/glfw
 	url = https://github.com/glfw/glfw.git
+[submodule "lib/SPIRV-Cross"]
+	path = lib/SPIRV-Cross
+	url = https://github.com/KhronosGroup/SPIRV-Cross.git
 [submodule "modules/asset_loader/lib/fx-gltf"]
 	path = modules/asset_loader/lib/fx-gltf
 	url = https://github.com/jessey-git/fx-gltf.git
@@ -12,4 +15,4 @@
 	url = https://github.com/nothings/stb.git
 [submodule "modules/camera/lib/glm"]
 	path = modules/camera/lib/glm
-	url = https://github.com/g-truc/glm.git
+	url = https://github.com/g-truc/glm.git
\ No newline at end of file
diff --git a/README.md b/README.md
index 4289a74308776017f099d048cf749c9693e6609f..29c45f2660cb29d8ded6cdac14d1bf2db6b2ffc3 100644
--- a/README.md
+++ b/README.md
@@ -11,5 +11,17 @@ More information about Git LFS [here](https://git-lfs.github.com/).
 
 ## Build
 
+ [![pipeline status](https://gitlab.uni-koblenz.de/vulkan2021/vkcv-framework/badges/develop/pipeline.svg)](https://gitlab.uni-koblenz.de/vulkan2021/vkcv-framework/-/commits/develop)
+
 Git submodules are used for libraries. 
 To download the submodules either clone using `git clone --recurse-submodules` or after `git clone` use `git submodule init` and `git submodule update`.
+
+## Documentation
+
+The documentation for the develop-branch can be found here:  
+https://vkcv.de/develop/  
+
+The documentation concerning the respective merge request is listed here:  
+https://vkcv.de/branch/  
+
+It is automatically generated and uploaded using the CI pipeline.
diff --git a/config/Libraries.cmake b/config/Libraries.cmake
index 13667936dd100be91ab43b73e6774e7db3da876c..e04aa3575a34632eb75c929bf4640305cd93e298 100644
--- a/config/Libraries.cmake
+++ b/config/Libraries.cmake
@@ -16,6 +16,7 @@ set(vkcv_config_msg " - Library: ")
 # load dependencies via separate cmake file
 include(${vkcv_config_lib}/GLFW.cmake)    # glfw-x11 / glfw-wayland					# libglfw3-dev
 include(${vkcv_config_lib}/Vulkan.cmake)  # vulkan-intel / vulkan-radeon / nvidia	# libvulkan-dev
+include(${vkcv_config_lib}/SPIRV_Cross.cmake)  # SPIRV-Cross	                    # libspirv_cross_c_shared
 
 # cleanup of compiler flags
 if (vkcv_flags)
diff --git a/config/Sources.cmake b/config/Sources.cmake
index cb73f57c2ca7278765ef0c8d01989c09a445c7b5..8fc0aa9a2605a629596e26d5eeb0772164e6ec7a 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -24,6 +24,12 @@ set(vkcv_sources
 		${vkcv_include}/vkcv/BufferManager.hpp
 		${vkcv_source}/vkcv/BufferManager.cpp
 
+		${vkcv_include}/vkcv/Image.hpp
+		${vkcv_source}/vkcv/Image.cpp
+
+		${vkcv_source}/vkcv/ImageManager.hpp
+		${vkcv_source}/vkcv/ImageManager.cpp
+
 		${vkcv_include}/vkcv/SwapChain.hpp
 		${vkcv_source}/vkcv/SwapChain.cpp
 
@@ -50,9 +56,9 @@ set(vkcv_sources
         
         ${vkcv_source}/vkcv/ImageLayoutTransitions.hpp
         ${vkcv_source}/vkcv/ImageLayoutTransitions.cpp
-        
-        ${vkcv_source}/vkcv/Framebuffer.hpp
-        ${vkcv_source}/vkcv/Framebuffer.cpp
+
+		${vkcv_include}/vkcv/VertexLayout.hpp
+		${vkcv_source}/vkcv/VertexLayout.cpp
 
 		${vkcv_include}/vkcv/Event.hpp
 
@@ -61,4 +67,9 @@ set(vkcv_sources
 
 		${vkcv_include}/vkcv/DescriptorConfig.hpp
 		${vkcv_source}/vkcv/DescriptorConfig.cpp
+		
+		${vkcv_source}/vkcv/SamplerManager.hpp
+		${vkcv_source}/vkcv/SamplerManager.cpp
+        
+        ${vkcv_include}/vkcv/DescriptorWrites.hpp
 )
diff --git a/config/lib/SPIRV_Cross.cmake b/config/lib/SPIRV_Cross.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..751ee883c47e0eab081a13e5805ced6f2daa7e30
--- /dev/null
+++ b/config/lib/SPIRV_Cross.cmake
@@ -0,0 +1,22 @@
+find_package(spirv_cross_c_shared QUIET)
+
+if (spirv-cross_FOUND)
+    list(APPEND vkcv_libraries spirv-cross-cpp)
+
+    message(${vkcv_config_msg} " SPIRV Cross    - " ${SPIRV_CROSS_VERSION})
+else()
+    if (EXISTS "${vkcv_lib_path}/SPIRV-Cross")
+        set(SPIRV_CROSS_CLI OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_TESTS OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_ENABLE_C_API OFF CACHE INTERNAL "")
+        set(SPIRV_CROSS_SKIP_INSTALL ON CACHE INTERNAL "")
+    
+        add_subdirectory(${vkcv_lib}/SPIRV-Cross)
+
+        list(APPEND vkcv_libraries spirv-cross-cpp)
+
+        message(${vkcv_config_msg} " SPIRV Cross    - " ${SPIRV_CROSS_VERSION})
+    else()
+        message(WARNING "SPIRV-Cross is required..! Update the submodules!")
+    endif ()
+endif ()
\ No newline at end of file
diff --git a/include/vkcv/Buffer.hpp b/include/vkcv/Buffer.hpp
index 5c21b1ce1d2d84afad6816fd8bf5f66a82b80941..d327067ce409e845bcac5e4c9f56e7de59df89c2 100644
--- a/include/vkcv/Buffer.hpp
+++ b/include/vkcv/Buffer.hpp
@@ -4,58 +4,76 @@
  * @file vkcv/Buffer.hpp
  * @brief template buffer class, template for type security, implemented here because template classes can't be written in .cpp
  */
+#include "Handles.hpp"
 #include "BufferManager.hpp"
 
+#include <vector>
+
 namespace vkcv {
 
 	template<typename T>
 	class Buffer {
+		friend class Core;
 	public:
 		// explicit destruction of default constructor
 		Buffer<T>() = delete;
-
-		BufferType getType() {
+		
+		[[nodiscard]]
+		const BufferHandle& getHandle() const {
+			return m_handle;
+		}
+		
+		[[nodiscard]]
+		BufferType getType() const {
 			return m_type;
 		};
 		
-		size_t getCount() {
+		[[nodiscard]]
+		size_t getCount() const {
 			return m_count;
 		}
 		
-		size_t getSize() {
+		[[nodiscard]]
+		size_t getSize() const {
 			return m_count * sizeof(T);
 		}
 		
-		void fill(T* data, size_t count = 0, size_t offset = 0) {
-			 m_manager->fillBuffer(m_handle_id, data, count * sizeof(T), offset * sizeof(T));
+		void fill(const T* data, size_t count = 0, size_t offset = 0) {
+			 m_manager->fillBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
+		}
+		
+		void fill(const std::vector<T>& vector, size_t offset = 0) {
+			fill( static_cast<const T*>(vector.data()), static_cast<size_t>(vector.size()), offset);
 		}
 		
+		[[nodiscard]]
 		T* map(size_t offset = 0, size_t count = 0) {
-			return reinterpret_cast<T*>(m_manager->mapBuffer(m_handle_id, offset * sizeof(T), count * sizeof(T)));
+			return reinterpret_cast<T*>(m_manager->mapBuffer(m_handle, offset * sizeof(T), count * sizeof(T)));
 		}
 
 		void unmap() {
-			m_manager->unmapBuffer(m_handle_id);
-		}
-		
-		static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType) {
-			return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType), type, count, memoryType);
+			m_manager->unmapBuffer(m_handle);
 		}
 
 	private:
 		BufferManager* const m_manager;
-		const uint64_t m_handle_id;
+		const BufferHandle m_handle;
 		const BufferType m_type;
 		const size_t m_count;
 		const BufferMemoryType m_memoryType;
 		
-		Buffer<T>(BufferManager* manager, uint64_t id, BufferType type, size_t count, BufferMemoryType memoryType) :
+		Buffer<T>(BufferManager* manager, BufferHandle handle, BufferType type, size_t count, BufferMemoryType memoryType) :
 				m_manager(manager),
-				m_handle_id(id),
+				m_handle(handle),
 				m_type(type),
 				m_count(count),
 				m_memoryType(memoryType)
 		{}
 		
+		[[nodiscard]]
+		static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType) {
+			return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType), type, count, memoryType);
+		}
+		
 	};
 }
diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp
index abda720f98f649d64b17a15848ae99320e11ae3e..322873b348dab7c45d446b5053d61379dfde0b6b 100644
--- a/include/vkcv/BufferManager.hpp
+++ b/include/vkcv/BufferManager.hpp
@@ -3,6 +3,8 @@
 #include <vector>
 #include <vulkan/vulkan.hpp>
 
+#include "Handles.hpp"
+
 namespace vkcv
 {
 	enum class BufferType {
@@ -29,14 +31,14 @@ namespace vkcv
 		{
 			vk::Buffer m_handle;
 			vk::DeviceMemory m_memory;
-			size_t m_size;
+			size_t m_size = 0;
 			void* m_mapped = nullptr;
-			bool m_mappable;
+			bool m_mappable = false;
 		};
 		
 		Core* m_core;
 		std::vector<Buffer> m_buffers;
-		uint64_t m_stagingBuffer;
+		BufferHandle m_stagingBuffer;
 		
 		BufferManager() noexcept;
 		
@@ -53,72 +55,82 @@ namespace vkcv
 		
 		/**
 		 * Creates and allocates a new buffer and returns its
-		 * unique buffer handle id.
+		 * unique buffer handle.
 		 *
 		 * @param type Type of buffer
 		 * @param size Size of buffer in bytes
 		 * @param memoryType Type of buffers memory
-		 * @return New buffer handle id
+		 * @return New buffer handle
 		 */
-		uint64_t createBuffer(BufferType type, size_t size, BufferMemoryType memoryType);
+		BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType);
 		
 		/**
 		 * Returns the Vulkan buffer handle of a buffer
-		 * represented by a given buffer handle id.
+		 * represented by a given buffer handle.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 * @return Vulkan buffer handle
 		 */
 		[[nodiscard]]
-		vk::Buffer getBuffer(uint64_t id) const;
+		vk::Buffer getBuffer(const BufferHandle& handle) const;
+		
+		/**
+		 * Returns the size of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param handle Buffer handle
+		 * @return Size of the buffer
+		 */
+		[[nodiscard]]
+		size_t getBufferSize(const BufferHandle& handle) const;
 		
 		/**
 		 * Returns the Vulkan device memory handle of a buffer
 		 * represented by a given buffer handle id.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 * @return Vulkan device memory handle
 		 */
 		[[nodiscard]]
-		vk::DeviceMemory getDeviceMemory(uint64_t id) const;
+		vk::DeviceMemory getDeviceMemory(const BufferHandle& handle) const;
 		
 		/**
 		 * Fills a buffer represented by a given buffer
-		 * handle id with custom data.
+		 * handle with custom data.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 * @param data Pointer to data
 		 * @param size Size of data in bytes
 		 * @param offset Offset to fill in data in bytes
 		 */
-		void fillBuffer(uint64_t id, void* data, size_t size, size_t offset);
+		void fillBuffer(const BufferHandle& handle, const void* data, size_t size, size_t offset);
 		
 		/**
 		 * Maps memory to a buffer represented by a given
-		 * buffer handle id and returns it.
+		 * buffer handle and returns it.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 * @param offset Offset of mapping in bytes
 		 * @param size Size of mapping in bytes
 		 * @return Pointer to mapped memory
 		 */
-		void* mapBuffer(uint64_t id, size_t offset, size_t size);
+		void* mapBuffer(const BufferHandle& handle, size_t offset, size_t size);
 		
 		/**
 		 * Unmaps memory from a buffer represented by a given
-		 * buffer handle id.
+		 * buffer handle.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 */
-		void unmapBuffer(uint64_t id);
+		void unmapBuffer(const BufferHandle& handle);
 	
 		/**
 		 * Destroys and deallocates buffer represented by a given
-		 * buffer handle id.
+		 * buffer handle.
 		 *
-		 * @param id Buffer handle id
+		 * @param handle Buffer handle
 		 */
-		void destroyBuffer(uint64_t id);
+		void destroyBuffer(const BufferHandle& handle);
 		
 	};
 	
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 66db4776d053352d8ccb2eea5e09c3fb77b68561..cf75b8e754513da774ec1841c8c4cc700b99ee2d 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -13,19 +13,29 @@
 #include "vkcv/PassConfig.hpp"
 #include "vkcv/Handles.hpp"
 #include "vkcv/Buffer.hpp"
+#include "vkcv/Image.hpp"
 #include "vkcv/PipelineConfig.hpp"
 #include "CommandResources.hpp"
 #include "SyncResources.hpp"
 #include "Result.hpp"
 #include "vkcv/DescriptorConfig.hpp"
+#include "Sampler.hpp"
+#include "DescriptorWrites.hpp"
 
 namespace vkcv
 {
+	struct VertexBufferBinding {
+		vk::DeviceSize	offset;
+		BufferHandle	buffer;
+	};
+
     // forward declarations
     class PassManager;
     class PipelineManager;
     class DescriptorManager;
     class BufferManager;
+    class SamplerManager;
+    class ImageManager;
 
 	struct SubmitInfo {
 		QueueType queueType;
@@ -55,19 +65,21 @@ namespace vkcv
 
         Context m_Context;
 
-        SwapChain m_swapchain;
-        std::vector<vk::ImageView> m_swapchainImageViews;
-        const Window& m_window;
+        SwapChain					m_swapchain;
+        std::vector<vk::ImageView>	m_swapchainImageViews;
+        const Window&				m_window;
 
-        std::unique_ptr<PassManager> m_PassManager;
-        std::unique_ptr<PipelineManager> m_PipelineManager;
-        std::unique_ptr<DescriptorManager> m_DescriptorManager;
-        std::unique_ptr<BufferManager> m_BufferManager;
+        std::unique_ptr<PassManager>		m_PassManager;
+        std::unique_ptr<PipelineManager>	m_PipelineManager;
+        std::unique_ptr<DescriptorManager>	m_DescriptorManager;
+        std::unique_ptr<BufferManager>		m_BufferManager;
+        std::unique_ptr<SamplerManager>		m_SamplerManager;
+        std::unique_ptr<ImageManager>		m_ImageManager;
 
-		CommandResources m_CommandResources;
-		SyncResources m_SyncResources;
-		uint32_t m_currentSwapchainImageIndex;
-		std::vector<vk::Framebuffer> m_TemporaryFramebuffers;
+		CommandResources				m_CommandResources;
+		SyncResources					m_SyncResources;
+		uint32_t						m_currentSwapchainImageIndex;
+		std::vector<vk::Framebuffer>	m_TemporaryFramebuffers;
 
         /**
          * recreates the swapchain
@@ -160,7 +172,7 @@ namespace vkcv
         PassHandle createPass(const PassConfig &config);
 
         /**
-            * Creates a #Buffer with data-type T and @p bufferType 
+            * Creates a #Buffer with data-type T and @p bufferType
             * @param type Type of Buffer created
             * @param count Count of elements of type T
             * @param memoryType Type of Buffers memory
@@ -170,13 +182,41 @@ namespace vkcv
         Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL) {
         	return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType);
         }
+        
+        /**
+         * Creates a Sampler with given attributes.
+         *
+         * @param magFilter Magnifying filter
+         * @param minFilter Minimizing filter
+         * @param mipmapMode Mipmapping filter
+         * @param addressMode Address mode
+         * @return Sampler handle
+         */
+        [[nodiscard]]
+        SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
+									SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode);
+
+        /**
+         * Creates an #Image with a given format, width, height and depth.
+         *
+         * @param format Image format
+         * @param width Image width
+         * @param height Image height
+         * @param depth Image depth
+         * @return Image-Object
+         */
+        [[nodiscard]]
+        Image createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1);
 
         /** TODO:
          *   @param setDescriptions
          *   @return
          */
         [[nodiscard]]
-        ResourcesHandle createResourceDescription(const std::vector<DescriptorSet> &descriptorSets);
+        ResourcesHandle createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets);
+		void writeResourceDescription(ResourcesHandle handle, size_t setIndex, const DescriptorWrites& writes);
+
+		vk::DescriptorSetLayout getDescritorSetLayout(ResourcesHandle handle, size_t setIndex);
 
 		/**
 		 * @brief start recording command buffers and increment frame index
@@ -186,8 +226,18 @@ namespace vkcv
 		/**
 		 * @brief render a beautiful triangle
 		*/
-		void renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle,
-			const int width, const int height, const size_t pushConstantSize, const void* pushConstantData);
+		void renderMesh(
+			const PassHandle						renderpassHandle, 
+			const PipelineHandle					pipelineHandle,
+			const int								width, 
+			const int								height, 
+			const size_t							pushConstantSize, 
+			const void*								pushConstantData, 
+			const std::vector<VertexBufferBinding>	&vertexBufferBindings, 
+			const BufferHandle						indexBuffer, 
+			const size_t							indexCount,
+			const vkcv::ResourcesHandle				resourceHandle,
+			const size_t							resourceDescriptorSetIndex);
 
 		/**
 		 * @brief end recording and present image
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
index 8116259757fe623f97a2814991b226ee27efca50..161273db1ec3bd0be290ecdd1042d12d181d303e 100644
--- a/include/vkcv/DescriptorConfig.hpp
+++ b/include/vkcv/DescriptorConfig.hpp
@@ -11,7 +11,8 @@ namespace vkcv
         UNIFORM_BUFFER,
         STORAGE_BUFFER,
         SAMPLER,
-        IMAGE
+        IMAGE_SAMPLED,
+		IMAGE_STORAGE
     };    
     
     /*
@@ -40,10 +41,9 @@ namespace vkcv
     * @param[in] a number of bindings that were created beforehand
     * @param[in] the number of (identical) sets that should be created from the attached bindings
     */
-    struct DescriptorSet
+    struct DescriptorSetConfig
     {
-        DescriptorSet() = delete;
-        explicit DescriptorSet(std::vector<DescriptorBinding> bindings) noexcept;
+        explicit DescriptorSetConfig(std::vector<DescriptorBinding> bindings) noexcept;
 
         std::vector<DescriptorBinding> bindings;
     };
diff --git a/include/vkcv/DescriptorWrites.hpp b/include/vkcv/DescriptorWrites.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d67e8e3233e184b207d109e652adeca43407d7e0
--- /dev/null
+++ b/include/vkcv/DescriptorWrites.hpp
@@ -0,0 +1,43 @@
+#pragma once
+#include "Handles.hpp"
+#include <vector>
+
+namespace vkcv {
+	struct SampledImageDescriptorWrite {
+		inline SampledImageDescriptorWrite(uint32_t binding, ImageHandle image) : binding(binding), image(image) {};
+		uint32_t	binding;
+		ImageHandle	image;
+	};
+
+	struct StorageImageDescriptorWrite {
+		inline StorageImageDescriptorWrite(uint32_t binding, ImageHandle image) : binding(binding), image(image) {};
+		uint32_t	binding;
+		ImageHandle	image;
+	};
+
+	struct UniformBufferDescriptorWrite {
+		inline UniformBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {};
+		uint32_t		binding;
+		BufferHandle	buffer;
+	};
+
+	struct StorageBufferDescriptorWrite {
+		inline StorageBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {};
+		uint32_t		binding;
+		BufferHandle	buffer;
+	};
+
+	struct SamplerDescriptorWrite {
+		inline SamplerDescriptorWrite(uint32_t binding, SamplerHandle sampler) : binding(binding), sampler(sampler) {};
+		uint32_t		binding;
+		SamplerHandle	sampler;
+	};
+
+	struct DescriptorWrites {
+		std::vector<SampledImageDescriptorWrite>		sampledImageWrites;
+		std::vector<StorageImageDescriptorWrite>		storageImageWrites;
+		std::vector<UniformBufferDescriptorWrite>	uniformBufferWrites;
+		std::vector<StorageBufferDescriptorWrite>	storageBufferWrites;
+		std::vector<SamplerDescriptorWrite>			samplerWrites;
+	};
+}
\ No newline at end of file
diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp
index ad48a1ecbd41f75286cc33e88e62699468a5f11f..58f795f0c99a0cd1b05045f9f401a26c0aac1b88 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -5,13 +5,76 @@
  * @brief Central header file for all possible handles that the framework will hand out.
  */
 
-#include <cstdint>
+#include <iostream>
 
 namespace vkcv
 {
+	
+	class Handle {
+		friend std::ostream& operator << (std::ostream& out, const Handle& handle);
+		
+	private:
+		uint64_t m_id;
+	
+	protected:
+		Handle();
+		
+		explicit Handle(uint64_t id);
+		
+		[[nodiscard]]
+		uint64_t getId() const;
+	
+	public:
+		virtual ~Handle() = default;
+		
+		Handle(const Handle& other) = default;
+		Handle(Handle&& other) = default;
+		
+		Handle& operator=(const Handle& other) = default;
+		Handle& operator=(Handle&& other) = default;
+		
+		explicit operator bool() const;
+		bool operator!() const;
+		
+	};
+	
+	std::ostream& operator << (std::ostream& out, const Handle& handle);
+	
     // Handle returned for any buffer created with the core/context objects
-    struct BufferHandle     {uint64_t id;};
-    struct PassHandle       {uint64_t id;};
-    struct PipelineHandle   {uint64_t id;};
-    struct ResourcesHandle  {uint64_t id;};
+    class BufferHandle : public Handle {
+    	friend class BufferManager;
+	private:
+		using Handle::Handle;
+    };
+	
+	class PassHandle : public Handle {
+		friend class PassManager;
+	private:
+		using Handle::Handle;
+	};
+	
+	class PipelineHandle : public Handle {
+		friend class PipelineManager;
+	private:
+		using Handle::Handle;
+	};
+	
+	class ResourcesHandle : public Handle {
+		friend class DescriptorManager;
+	private:
+		using Handle::Handle;
+	};
+	
+	class SamplerHandle : public Handle {
+		friend class SamplerManager;
+	private:
+		using Handle::Handle;
+	};
+
+	class ImageHandle : public Handle {
+		friend class ImageManager;
+	private:
+		using Handle::Handle;
+	};
+	
 }
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d56fac9c0e442faeff1898443d737df365c9ed45
--- /dev/null
+++ b/include/vkcv/Image.hpp
@@ -0,0 +1,53 @@
+#pragma once
+/**
+ * @authors Lars Hoerttrich
+ * @file vkcv/Buffer.hpp
+ * @brief class for image handles
+ */
+#include "vulkan/vulkan.hpp"
+
+#include "Handles.hpp"
+
+namespace vkcv {
+	
+	class ImageManager;
+	class Image {
+		friend class Core;
+	public:
+		[[nodiscard]]
+		vk::Format getFormat() const;
+		
+		[[nodiscard]]
+		uint32_t getWidth() const;
+		
+		[[nodiscard]]
+		uint32_t getHeight() const;
+		
+		[[nodiscard]]
+		uint32_t getDepth() const;
+		
+		[[nodiscard]]
+		vk::ImageLayout getLayout() const;
+
+		[[nodiscard]]
+		vkcv::ImageHandle getHandle() const;
+		
+		void switchLayout(vk::ImageLayout newLayout);
+		
+		void fill(void* data, size_t size = SIZE_MAX);
+	private:
+		ImageManager* const m_manager;
+		const ImageHandle m_handle;
+		const vk::Format m_format;
+		const uint32_t m_width;
+		const uint32_t m_height;
+		const uint32_t m_depth;
+		vk::ImageLayout m_layout;
+
+		Image(ImageManager* manager, const ImageHandle& handle, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
+		
+		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
+		
+	};
+	
+}
diff --git a/include/vkcv/PassConfig.hpp b/include/vkcv/PassConfig.hpp
index b8e80c67c2b70e3a0e6e2732b950ccaed38da3bf..d9a5bcd83acca5f5ba86b4e6ce6973acbed89de6 100644
--- a/include/vkcv/PassConfig.hpp
+++ b/include/vkcv/PassConfig.hpp
@@ -53,7 +53,6 @@ namespace vkcv
 
     struct PassConfig
     {
-        PassConfig() = delete;
         explicit PassConfig(std::vector<AttachmentDescription> attachments) noexcept;
         std::vector<AttachmentDescription> attachments{};
     };
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index 1ad6be8d1979c8a89f7de9dbe24ff13b5f5bb3fa..a8e1334f3ee5d6774b9fed35edd685f19747814d 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -1,27 +1,19 @@
+#pragma once
 /**
  * @authors Mara Vogt, Mark Mints
  * @file src/vkcv/Pipeline.hpp
  * @brief Pipeline class to handle shader stages
  */
 
-#ifndef VKCV_PIPELINECONFIG_HPP
-#define VKCV_PIPELINECONFIG_HPP
-
 #include <vector>
 #include <cstdint>
 #include "vkcv/Handles.hpp"
 #include "ShaderProgram.hpp"
+#include <vkcv/VertexLayout.hpp>
 
 namespace vkcv {
 
-    class PipelineConfig {
-
-    public:
-        /**
-         *  Default constructer is deleted!
-         */
-        PipelineConfig() = delete;
-
+    struct PipelineConfig {
         /**
          *  Constructor for the pipeline. Creates a pipeline using @p vertexCode, @p fragmentCode as well as the
          *  dimensions of the application window @p width and @p height. A handle for the Render Pass is also needed, @p passHandle.
@@ -31,13 +23,20 @@ namespace vkcv {
          * @param width width of the application window
          * @param passHandle handle for Render Pass
          */
-        PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle);
-
-		ShaderProgram m_ShaderProgram;
-        uint32_t m_Height;
-        uint32_t m_Width;
-        PassHandle m_PassHandle;
+        PipelineConfig(
+			const ShaderProgram&						shaderProgram, 
+			uint32_t									width, 
+			uint32_t									height, 
+			PassHandle									&passHandle,
+			const std::vector<VertexAttribute>			&vertexAttributes,
+			const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts);
+
+		ShaderProgram							m_ShaderProgram;
+        uint32_t								m_Height;
+        uint32_t								m_Width;
+        PassHandle								m_PassHandle;
+		std::vector<VertexAttribute>			m_vertexAttributes;
+		std::vector<vk::DescriptorSetLayout>	m_descriptorLayouts;
     };
 
-}
-#endif //VKCV_PIPELINECONFIG_HPP
+}
\ No newline at end of file
diff --git a/include/vkcv/Sampler.hpp b/include/vkcv/Sampler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..007ed5ae4737275ddacbc5881a2c4c202b8806a4
--- /dev/null
+++ b/include/vkcv/Sampler.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace vkcv {
+
+	enum class SamplerFilterType {
+		NEAREST = 1,
+		LINEAR = 2
+	};
+	
+	enum class SamplerMipmapMode {
+		NEAREST = 1,
+		LINEAR = 2
+	};
+	
+	enum class SamplerAddressMode {
+		REPEAT = 1,
+		MIRRORED_REPEAT = 2,
+		CLAMP_TO_EDGE = 3,
+		MIRROR_CLAMP_TO_EDGE = 4
+	};
+
+}
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index 172e906fca457c6245855639275054514958b69d..ef5d1f00ea3eeb97d97d8824439ded1ed326f33c 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -10,6 +10,8 @@
 #include <iostream>
 #include <filesystem>
 #include <vulkan/vulkan.hpp>
+#include <spirv_cross.hpp>
+#include "vkcv/VertexLayout.hpp"
 
 namespace vkcv {
 
@@ -53,8 +55,13 @@ namespace vkcv {
 
         bool existsShader(ShaderStage shaderStage) const;
 
+        void reflectShader(ShaderStage shaderStage);
+
+        const VertexLayout &getVertexLayout() const;
+
 	private:
         std::unordered_map<ShaderStage, Shader> m_Shaders;
 
+        VertexLayout m_VertexLayout;
 	};
 }
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ee0ad8ef56d5284af2be4c81b7ea2f0d052d5a6f
--- /dev/null
+++ b/include/vkcv/VertexLayout.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <unordered_map>
+#include <vector>
+#include <iostream>
+
+namespace vkcv{
+
+	/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
+	enum class PrimitiveType : uint32_t {
+		UNDEFINED = 0,
+		POSITION = 1,
+		NORMAL = 2,
+		TEXCOORD_0 = 3
+	};
+	/* This struct describes one vertex attribute of a vertex buffer. */
+	typedef struct {
+		PrimitiveType type;			// POSITION, NORMAL, ...
+		uint32_t offset;			// offset in bytes
+		uint32_t length;			// length of ... in bytes
+		uint32_t stride;			// stride in bytes
+		uint16_t componentType;		// eg. 5126 for float
+		uint8_t  componentCount;	// eg. 3 for vec3
+	} VertexAttribute;
+
+    enum class VertexFormat{
+        FLOAT,
+        FLOAT2,
+        FLOAT3,
+        FLOAT4,
+        INT,
+        INT2,
+        INT3,
+        INT4
+    };
+
+	uint32_t getFormatSize(VertexFormat format);
+
+    struct VertexInputAttachment{
+        VertexInputAttachment() = delete;
+        VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept;
+
+        uint32_t location;
+        uint32_t binding;
+        VertexFormat format;
+        uint32_t offset;
+    };
+
+    struct VertexLayout{
+        VertexLayout() noexcept;
+        VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept;
+        std::unordered_map<uint32_t, VertexInputAttachment> attachmentMap;
+        uint32_t stride;
+    };
+}
\ No newline at end of file
diff --git a/lib/SPIRV-Cross b/lib/SPIRV-Cross
new file mode 160000
index 0000000000000000000000000000000000000000..ff61890722a91e97c44940494be5b6eed0d5ff5b
--- /dev/null
+++ b/lib/SPIRV-Cross
@@ -0,0 +1 @@
+Subproject commit ff61890722a91e97c44940494be5b6eed0d5ff5b
diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt
index 8d4c0d6c104187de2d807cceceff529d83d236d6..d2a9b817ea68c7851fd2123f76b378d8a4d85ac0 100644
--- a/modules/asset_loader/CMakeLists.txt
+++ b/modules/asset_loader/CMakeLists.txt
@@ -31,7 +31,7 @@ include(config/FX_GLTF.cmake)
 include(config/STB.cmake)
 
 # link the required libraries to the module
-target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries})
+target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv)
 
 # including headers of dependencies and the VkCV framework
 target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes})
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 6df014563a8991e464e02eb10f210c079913d3cb..24687e846ff9eae3275de357331a825f0b4ed2c3 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 #include <cstdint>
+#include <vkcv/VertexLayout.hpp>
 
 /* These macros define limits of the following structs. Implementations can
  * test against these limits when performing sanity checks. The main constraint
@@ -49,8 +50,6 @@ enum PrimitiveMode {
 	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
 	TRIANGLEFAN
 };
-/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
-enum PrimitiveType { POSITION=1, NORMAL, TEXCOORD_0 };
 /* The indices in the index buffer can be of different bit width. */
 enum IndexType { UINT32=0, UINT16=1, UINT8=2 };
 
@@ -58,16 +57,6 @@ typedef struct {
 	// TODO not yet needed for the first (unlit) triangle
 } Material;
 
-/* This struct describes one vertex attribute of a vertex buffer. */
-typedef struct {
-	PrimitiveType type;		// POSITION, NORMAL, ...
-	uint32_t offset;		// offset in bytes
-	uint32_t length;		// length of ... in bytes
-	uint32_t stride;		// stride in bytes
-	uint16_t componentType;		// eg. 5126 for float
-	uint8_t  componentCount;	// eg. 3 for vec3
-} VertexAttribute;
-
 /* This struct represents one (possibly the only) part of a mesh. There is
  * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
  * common but not always used). If there is no index buffer, this is indicated
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index d0949cbc3454283b0084c57abf95a06a036c4e9b..e660b442d0b9a0208f95c9d753ef19e926bcac44 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -85,12 +85,12 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		VertexAttribute attribute;
 
 		if (attrib.first == "POSITION") {
-			attribute.type = POSITION;
+			attribute.type = PrimitiveType::POSITION;
 			posAccessor = accessor;
 		} else if (attrib.first == "NORMAL") {
-			attribute.type = NORMAL;
+			attribute.type = PrimitiveType::NORMAL;
 		} else if (attrib.first == "TEXCOORD_0") {
-			attribute.type = TEXCOORD_0;
+			attribute.type = PrimitiveType::TEXCOORD_0;
 		} else {
 			return 0;
 		}
@@ -126,15 +126,16 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		}
 	}
 
-	const fx::gltf::BufferView& vertexBufferView = object.bufferViews[posAccessor.bufferView];
-	const fx::gltf::Buffer& vertexBuffer = object.buffers[vertexBufferView.buffer];
+	const fx::gltf::BufferView&	vertexBufferView	= object.bufferViews[posAccessor.bufferView];
+	const fx::gltf::Buffer&		vertexBuffer		= object.buffers[vertexBufferView.buffer];
 	
+	// FIXME: This only works when all vertex attributes are in one buffer
 	std::vector<uint8_t> vertexBufferData;
-	vertexBufferData.resize(vertexBufferView.byteLength);
+	vertexBufferData.resize(vertexBuffer.byteLength);
 	{
-		const size_t off = vertexBufferView.byteOffset;
+		const size_t off = 0;
 		const void *const ptr = ((char*)vertexBuffer.data.data()) + off;
-		if (!memcpy(vertexBufferData.data(), ptr, vertexBufferView.byteLength)) {
+		if (!memcpy(vertexBufferData.data(), ptr, vertexBuffer.byteLength)) {
 			std::cerr << "ERROR copying vertex buffer data.\n";
 			return 0;
 		}
diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt
index ae9c5604cb83c6f3a16773e896521117460839f7..eb0f028db38707272f9fbcf61662633f2868eedc 100644
--- a/projects/first_mesh/CMakeLists.txt
+++ b/projects/first_mesh/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include})
+target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries})
+target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/first_mesh/resources/shaders/compile.bat b/projects/first_mesh/resources/shaders/compile.bat
new file mode 100644
index 0000000000000000000000000000000000000000..b4521235c40fe5fb163bab874560c2f219b7517f
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/compile.bat
@@ -0,0 +1,3 @@
+%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
+%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
+pause
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..087e4e22fb2fcec27d99b3ff2aa1a705fe755796
Binary files /dev/null and b/projects/first_mesh/resources/shaders/frag.spv differ
diff --git a/projects/first_mesh/resources/shaders/shader.frag b/projects/first_mesh/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..71a1de69c57c0d7b7d4665095410e7acaf8dbd62
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.frag
@@ -0,0 +1,14 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform texture2D  meshTexture;
+layout(set=0, binding=1) uniform sampler    textureSampler;
+
+void main()	{
+	outColor = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/shader.vert b/projects/first_mesh/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..76855152253b48b7400f016d063ed4f0e507435e
--- /dev/null
+++ b/projects/first_mesh/resources/shaders/shader.vert
@@ -0,0 +1,19 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    passUV      = inUV;
+}
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/vert.spv b/projects/first_mesh/resources/shaders/vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..374c023e14b351eb43cbcda5951cbb8b3d6f96a1
Binary files /dev/null and b/projects/first_mesh/resources/shaders/vert.spv differ
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index b37429e1f7080b70aaab514125f86fbe03f329b5..107cd432e1bc902692bb5b1fa694120ddf24038b 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -1,75 +1,170 @@
 #include <iostream>
-#include <stdio.h>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
 #include <vkcv/asset/asset_loader.hpp>
 
-
 int main(int argc, const char** argv) {
+	const char* applicationName = "First Mesh";
+
+	const int windowWidth = 800;
+	const int windowHeight = 600;
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		false
+	);
+
+	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
 	vkcv::asset::Mesh mesh;
-	
-	const char *path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
 	int result = vkcv::asset::loadMesh(path, mesh);
-	
+
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
-	} else {
+	}
+	else {
 		std::cout << "Mesh loading failed: " << result << std::endl;
 		return 1;
 	}
 
-	/* Demonstration of how to use the vkcv::asset::Mesh struct. */
-	const char *primitive_modes[] = {
-		"points", "lines", "line loop", "line strip", "triangles",
-		"triangle strip", "triangle fan"
-	};
-	const char *primitive_types[] = {
-		"unknown", "position", "normal", "texcoord0"
+	assert(mesh.vertexGroups.size() > 0);
+	auto vertexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			mesh.vertexGroups[0].vertexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+
+	auto indexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			mesh.vertexGroups[0].indexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentLayout::UNDEFINED,
+		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
+		vkcv::AttachmentLayout::PRESENTATION,
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchainImageFormat()
+	);
+	
+	const vkcv::AttachmentDescription depth_attachment(
+			vkcv::AttachmentLayout::UNDEFINED,
+			vkcv::AttachmentLayout::DEPTH_STENCIL_ATTACHMENT,
+			vkcv::AttachmentLayout::DEPTH_STENCIL_ATTACHMENT,
+			vkcv::AttachmentOperation::STORE,
+			vkcv::AttachmentOperation::CLEAR,
+			vk::Format::eD32Sfloat
+	);
+
+	vkcv::PassConfig trianglePassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
+
+	if (!trianglePass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram triangleShaderProgram{};
+	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
+	
+	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
+	
+	std::sort(attributes.begin(), attributes.end(), [](const vkcv::VertexAttribute& x, const vkcv::VertexAttribute& y) {
+		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+	});
+
+	vkcv::DescriptorSetConfig setConfig({
+		vkcv::DescriptorBinding(vkcv::DescriptorType::IMAGE_SAMPLED,	1, vkcv::ShaderStage::FRAGMENT),
+		vkcv::DescriptorBinding(vkcv::DescriptorType::SAMPLER,			1, vkcv::ShaderStage::FRAGMENT)
+	});
+	vkcv::ResourcesHandle set = core.createResourceDescription({ setConfig });
+
+	const vkcv::PipelineConfig trianglePipelineDefinition(
+		triangleShaderProgram, 
+		windowWidth,
+		windowHeight,
+		trianglePass,
+		mesh.vertexGroups[0].vertexBuffer.attributes,
+		{ core.getDescritorSetLayout(set, 0) });
+	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+	
+	if (!trianglePipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
+	texture.fill(mesh.texture_hack.img);
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+	std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[0].offset, vertexBuffer.getHandle() },
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[1].offset, vertexBuffer.getHandle() },
+		{ mesh.vertexGroups[0].vertexBuffer.attributes[2].offset, vertexBuffer.getHandle() }
 	};
-	printf("Mesh %s (%s) has %lu vertex group(s) and %lu material(s):\n",
-			mesh.name.c_str(), path, mesh.vertexGroups.size(),
-			mesh.materials.size());
-	for (size_t i = 0; i < mesh.vertexGroups.size(); i++) {
-		printf("--- vertex group %lu ---\n", i);
-		const auto &vg = mesh.vertexGroups[i];
-		printf("primitive mode: %d (%s)\n", vg.mode,
-				primitive_modes[vg.mode]);
-		printf("index buffer: %lu bytes for %lu indices ",
-				vg.indexBuffer.data.size(), vg.numIndices);
-		const auto itype = vg.indexBuffer.type;
-		printf("(%s @ %p)\n",
-				itype == vkcv::asset::UINT32 ? "UINT32" :
-				itype == vkcv::asset::UINT16 ? "UINT16" :
-				"UINT8", vg.indexBuffer.data.data());
-		printf("\tindices: ");
-		const size_t n = vg.numIndices;
-		if (vg.indexBuffer.type == vkcv::asset::UINT32) {
-			uint32_t *idx = (uint32_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT16) {
-			uint16_t *idx = (uint16_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else
-		if (vg.indexBuffer.type == vkcv::asset::UINT8) {
-			uint8_t *idx = (uint8_t*)vg.indexBuffer.data.data();
-			for (size_t j = 0; j < n; j++) printf("%u ", idx[j]);
-		} else {
-			fprintf(stderr, "ERROR Invalid IndexType: %d\n",
-					vg.indexBuffer.type);
-			return 0;
-		}
-		printf("\n");
-		printf("vertex buffer: %lu bytes for %lu vertices with %lu "
-				"attributes (starting at %p)\n",
-				vg.vertexBuffer.data.size(), vg.numVertices,
-				vg.vertexBuffer.attributes.size(),
-				vg.vertexBuffer.data.data());
-		printf("attributes:\toffset\tlength\tstride\tcomponents\n");
-		for (const auto att : vg.vertexBuffer.attributes) {
-			printf("%11s\t%u\t%u\t%u\t%hhux%hu\n",
-					primitive_types[att.type],
-					att.offset, att.length, att.stride,
-					att.componentCount, att.componentType);
-		}
+
+	vkcv::DescriptorWrites setWrites;
+	setWrites.sampledImageWrites	= { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
+	setWrites.samplerWrites			= { vkcv::SamplerDescriptorWrite(1, sampler) };
+	core.writeResourceDescription(set, 0, setWrites);
+
+	auto start = std::chrono::system_clock::now();
+	while (window.isWindowOpen()) {
+		core.beginFrame();
+		window.pollEvents();
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = end - start;
+		start = end;
+		cameraManager.getCamera().updateView(std::chrono::duration<double>(deltatime).count());
+		const glm::mat4 mvp = cameraManager.getCamera().getProjection() * cameraManager.getCamera().getView();
+
+		core.renderMesh(
+			trianglePass,
+			trianglePipeline,
+			windowWidth,
+			windowHeight,
+			sizeof(mvp),
+			&mvp,
+			vertexBufferBindings,
+			indexBuffer.getHandle(),
+			mesh.vertexGroups[0].numIndices,
+			set,
+			0
+		);
+
+		core.endFrame();
 	}
 	
 	return 0;
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 192e3449063c59cda865c87980a9f0740bed75fa..6b4a83c9769d88fcd0310ee3d646b225bc9f0615 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -5,70 +5,79 @@
 #include <chrono>
 
 int main(int argc, const char** argv) {
-    const char* applicationName = "First Triangle";
+	const char* applicationName = "First Triangle";
 
 	const int windowWidth = 800;
 	const int windowHeight = 600;
-    vkcv::Window window = vkcv::Window::create(
+	vkcv::Window window = vkcv::Window::create(
 		applicationName,
 		windowWidth,
 		windowHeight,
 		false
-    );
+	);
 
-    vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
-    cameraManager.getTrackballCamera().setPosition(glm::vec3(0.0f,0.5f,0.0f));
-    cameraManager.getTrackballCamera().setCenter(glm::vec3(0.0f,0.0f,-1.0f));
+	vkcv::CameraManager cameraManager(window, windowWidth, windowHeight);
 
-    window.initEvents();
+	window.initEvents();
 
 	vkcv::Core core = vkcv::Core::create(
-            window,
-            applicationName,
+		window,
+		applicationName,
 		VK_MAKE_VERSION(0, 0, 1),
-            {vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
 		{},
-		{"VK_KHR_swapchain"}
+		{ "VK_KHR_swapchain" }
 	);
 
-	const auto &context = core.getContext();
+	const auto& context = core.getContext();
 	const vk::Instance& instance = context.getInstance();
 	const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
 	const vk::Device& device = context.getDevice();
-	
+
 	struct vec3 {
 		float x, y, z;
 	};
-	
+
 	const size_t n = 5027;
-	
-	auto buffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
-	vec3 vec_data [n];
-	
+
+	auto testBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	vec3 vec_data[n];
+
 	for (size_t i = 0; i < n; i++) {
 		vec_data[i] = { 42, static_cast<float>(i), 7 };
 	}
-	
-	buffer.fill(vec_data);
-	
+
+	testBuffer.fill(vec_data);
+
+	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	uint16_t indices[3] = { 0, 1, 2 };
+	triangleIndexBuffer.fill(&indices[0], sizeof(indices));
+
 	/*vec3* m = buffer.map();
 	m[0] = { 0, 0, 0 };
 	m[1] = { 0, 0, 0 };
 	m[2] = { 0, 0, 0 };
 	buffer.unmap();*/
 
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerFilterType::NEAREST,
+		vkcv::SamplerMipmapMode::NEAREST,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
 	std::cout << "Physical device: " << physicalDevice.getProperties().deviceName << std::endl;
 
 	switch (physicalDevice.getProperties().vendorID) {
-		case 0x1002: std::cout << "Running AMD huh? You like underdogs, are you a Linux user?" << std::endl; break;
-		case 0x10DE: std::cout << "An NVidia GPU, how predictable..." << std::endl; break;
-		case 0x8086: std::cout << "Poor child, running on an Intel GPU, probably integrated..."
-			"or perhaps you are from the future with a dedicated one?" << std::endl; break;
-		case 0x13B5: std::cout << "ARM? What the hell are you running on, next thing I know you're trying to run Vulkan on a leg..." << std::endl; break;
-		default: std::cout << "Unknown GPU vendor?! Either you're on an exotic system or your driver is broken..." << std::endl;
+	case 0x1002: std::cout << "Running AMD huh? You like underdogs, are you a Linux user?" << std::endl; break;
+	case 0x10DE: std::cout << "An NVidia GPU, how predictable..." << std::endl; break;
+	case 0x8086: std::cout << "Poor child, running on an Intel GPU, probably integrated..."
+		"or perhaps you are from the future with a dedicated one?" << std::endl; break;
+	case 0x13B5: std::cout << "ARM? What the hell are you running on, next thing I know you're trying to run Vulkan on a leg..." << std::endl; break;
+	default: std::cout << "Unknown GPU vendor?! Either you're on an exotic system or your driver is broken..." << std::endl;
 	}
 
-    // an example attachment for passes that output to the window
+	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentLayout::UNDEFINED,
 		vkcv::AttachmentLayout::COLOR_ATTACHMENT,
@@ -77,10 +86,10 @@ int main(int argc, const char** argv) {
 		vkcv::AttachmentOperation::CLEAR,
 		core.getSwapchainImageFormat());
 
-	vkcv::PassConfig trianglePassDefinition({present_color_attachment});
+	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
 	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
 
-	if (trianglePass.id == 0)
+	if (!trianglePass)
 	{
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
@@ -89,32 +98,25 @@ int main(int argc, const char** argv) {
 	vkcv::ShaderProgram triangleShaderProgram{};
 	triangleShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/vert.spv"));
 	triangleShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/frag.spv"));
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::VERTEX);
+	triangleShaderProgram.reflectShader(vkcv::ShaderStage::FRAGMENT);
 
-	const vkcv::PipelineConfig trianglePipelineDefinition(triangleShaderProgram, windowWidth, windowHeight, trianglePass);
+	const vkcv::PipelineConfig trianglePipelineDefinition(
+		triangleShaderProgram,
+		windowWidth,
+		windowHeight,
+		trianglePass,
+		{},
+		{});
 	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
-	if (trianglePipeline.id == 0)
+	
+	if (!trianglePipeline)
 	{
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	//just an example
-	//creates 20 descriptor sets, each containing bindings for 50 uniform buffers, images, and samplers
-	std::vector<vkcv::DescriptorSet> sets;
-
-	for (uint32_t i = 0; i < 20; i++)
-	{
-		vkcv::DescriptorBinding uniformBufBinding(vkcv::DescriptorType::UNIFORM_BUFFER, 50, vkcv::ShaderStage::VERTEX);
-        vkcv::DescriptorBinding storageBufBinding(vkcv::DescriptorType::STORAGE_BUFFER, 50, vkcv::ShaderStage::VERTEX);
-        vkcv::DescriptorBinding imageBinding(vkcv::DescriptorType::IMAGE, 50, vkcv::ShaderStage::VERTEX);
-        vkcv::DescriptorBinding samplerBinding(vkcv::DescriptorType::SAMPLER, 50, vkcv::ShaderStage::VERTEX);
-
-        vkcv::DescriptorSet set({uniformBufBinding, storageBufBinding, imageBinding, samplerBinding});
-
-		sets.push_back(set);
-        auto resourceHandle = core.createResourceDescription(sets);
-        std::cout << "Resource " << resourceHandle.id << " created." << std::endl;
-	}
+	std::vector<vkcv::VertexBufferBinding> vertexBufferBindings;
 
 	/*
 	 * BufferHandle triangleVertices = core.createBuffer(vertices);
@@ -144,7 +146,19 @@ int main(int argc, const char** argv) {
         cameraManager.getTrackballCamera().updateView(std::chrono::duration<double>(deltatime).count());
         const glm::mat4 mvp = cameraManager.getTrackballCamera().getProjection() * cameraManager.getTrackballCamera().getView();
 
-	    core.renderTriangle(trianglePass, trianglePipeline, windowWidth, windowHeight, sizeof(mvp), &mvp);
+	    core.renderMesh(
+			trianglePass,
+			trianglePipeline,
+			windowWidth,
+			windowHeight,
+			sizeof(mvp),
+			&mvp,
+			vertexBufferBindings,
+			triangleIndexBuffer.getHandle(),
+			3,
+			vkcv::ResourcesHandle(),
+			0);
+	    
 	    core.endFrame();
 	}
 	return 0;
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index cb7c7356aef589ee8aef03b904d48021bdb75057..ab34a3df149c96d30c9ee4e0665ba47765d19751 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -9,7 +9,7 @@
 namespace vkcv {
 	
 	BufferManager::BufferManager() noexcept :
-		m_core(nullptr), m_buffers(), m_stagingBuffer(UINT64_MAX)
+		m_core(nullptr), m_buffers(), m_stagingBuffer(BufferHandle())
 	{
 	}
 	
@@ -22,37 +22,38 @@ namespace vkcv {
 	}
 	
 	BufferManager::~BufferManager() noexcept {
-		for (size_t id = 0; id < m_buffers.size(); id++) {
-			destroyBuffer(id);
+		for (uint64_t id = 0; id < m_buffers.size(); id++) {
+			destroyBuffer(BufferHandle(id));
 		}
 	}
 	
 	/**
-	 * @brief searches memory type index for buffer allocation, inspired by vulkan tutorial and "https://github.com/KhronosGroup/Vulkan-Hpp/blob/master/samples/utils/utils.hpp"
+	 * @brief searches memory type index for buffer allocation, combines requirements of buffer and application
 	 * @param physicalMemoryProperties Memory Properties of physical device
-	 * @param typeBits
+	 * @param typeBits Bit field for suitable memory types
 	 * @param requirements Property flags that are required
 	 * @return memory type index for Buffer
 	 */
-	uint32_t searchMemoryType(const vk::PhysicalDeviceMemoryProperties& physicalMemoryProperties, uint32_t typeBits, vk::MemoryPropertyFlags requirements) {
-		uint32_t memoryTypeIndex = 0;
-		
-		for (uint32_t i = 0; i < physicalMemoryProperties.memoryTypeCount; i++)
-		{
-			if ((typeBits & 1) &&
-				((physicalMemoryProperties.memoryTypes[i].propertyFlags & requirements) == requirements))
-			{
-				memoryTypeIndex = i;
-				break;
-			}
-			
-			typeBits >>= 1;
+	uint32_t searchBufferMemoryType(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);
 		}
-		
-		return memoryTypeIndex;
+
+		// failed to find memory type
+		return -1;
 	}
 	
-	uint64_t BufferManager::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType) {
+	BufferHandle BufferManager::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType) {
 		vk::BufferCreateFlags createFlags;
 		vk::BufferUsageFlags usageFlags;
 		
@@ -69,6 +70,9 @@ namespace vkcv {
 			case BufferType::STAGING:
 				usageFlags = vk::BufferUsageFlagBits::eTransferSrc;
 				break;
+			case BufferType::INDEX:
+				usageFlags = vk::BufferUsageFlagBits::eIndexBuffer;
+				break;
 			default:
 				// TODO: maybe an issue
 				break;
@@ -103,7 +107,7 @@ namespace vkcv {
 				break;
 		}
 		
-		const uint32_t memoryTypeIndex = searchMemoryType(
+		const uint32_t memoryTypeIndex = searchBufferMemoryType(
 				physicalDevice.getMemoryProperties(),
 				requirements.memoryTypeBits,
 				memoryTypeFlags
@@ -115,11 +119,11 @@ namespace vkcv {
 		
 		const uint64_t id = m_buffers.size();
 		m_buffers.push_back({ buffer, memory, size, nullptr, mappable });
-		return id;
+		return BufferHandle{ id };
 	}
 	
 	struct StagingStepInfo {
-		void* data;
+		const void* data;
 		size_t size;
 		size_t offset;
 		
@@ -148,7 +152,7 @@ namespace vkcv {
 		const vk::Device& device = core->getContext().getDevice();
 		
 		void* mapped = device.mapMemory(info.stagingMemory, 0, mapped_size);
-		memcpy(mapped, reinterpret_cast<char*>(info.data) + info.stagingPosition, mapped_size);
+		memcpy(mapped, reinterpret_cast<const char*>(info.data) + info.stagingPosition, mapped_size);
 		device.unmapMemory(info.stagingMemory);
 		
 		SubmitInfo submitInfo;
@@ -178,7 +182,9 @@ namespace vkcv {
 		);
 	}
 	
-	vk::Buffer BufferManager::getBuffer(uint64_t id) const {
+	vk::Buffer BufferManager::getBuffer(const BufferHandle& handle) const {
+		const uint64_t id = handle.getId();
+		
 		if (id >= m_buffers.size()) {
 			return nullptr;
 		}
@@ -188,7 +194,21 @@ namespace vkcv {
 		return buffer.m_handle;
 	}
 	
-	vk::DeviceMemory BufferManager::getDeviceMemory(uint64_t id) const {
+	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];
+		
+		return buffer.m_size;
+	}
+	
+	vk::DeviceMemory BufferManager::getDeviceMemory(const BufferHandle& handle) const {
+		const uint64_t id = handle.getId();
+		
 		if (id >= m_buffers.size()) {
 			return nullptr;
 		}
@@ -198,7 +218,9 @@ namespace vkcv {
 		return buffer.m_memory;
 	}
 	
-	void BufferManager::fillBuffer(uint64_t id, void *data, size_t size, size_t offset) {
+	void BufferManager::fillBuffer(const BufferHandle& handle, const void *data, size_t size, size_t offset) {
+		const uint64_t id = handle.getId();
+		
 		if (size == 0) {
 			size = SIZE_MAX;
 		}
@@ -226,7 +248,7 @@ namespace vkcv {
 			memcpy(mapped, data, max_size);
 			device.unmapMemory(buffer.m_memory);
 		} else {
-			auto& stagingBuffer = m_buffers[m_stagingBuffer];
+			auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ];
 			
 			StagingStepInfo info;
 			info.data = data;
@@ -246,7 +268,9 @@ namespace vkcv {
 		}
 	}
 	
-	void* BufferManager::mapBuffer(uint64_t id, size_t offset, size_t size) {
+	void* BufferManager::mapBuffer(const BufferHandle& handle, size_t offset, size_t size) {
+		const uint64_t id = handle.getId();
+		
 		if (size == 0) {
 			size = SIZE_MAX;
 		}
@@ -272,7 +296,9 @@ namespace vkcv {
 		return buffer.m_mapped;
 	}
 	
-	void BufferManager::unmapBuffer(uint64_t id) {
+	void BufferManager::unmapBuffer(const BufferHandle& handle) {
+		const uint64_t id = handle.getId();
+		
 		if (id >= m_buffers.size()) {
 			return;
 		}
@@ -289,7 +315,9 @@ namespace vkcv {
 		buffer.m_mapped = nullptr;
 	}
 	
-	void BufferManager::destroyBuffer(uint64_t id) {
+	void BufferManager::destroyBuffer(const BufferHandle& handle) {
+		const uint64_t id = handle.getId();
+		
 		if (id >= m_buffers.size()) {
 			return;
 		}
@@ -300,10 +328,12 @@ namespace vkcv {
 		
 		if (buffer.m_memory) {
 			device.freeMemory(buffer.m_memory);
+			buffer.m_memory = nullptr;
 		}
 		
 		if (buffer.m_handle) {
 			device.destroyBuffer(buffer.m_handle);
+			buffer.m_handle = nullptr;
 		}
 	}
 
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 0ee414fb974676b1d2df820e69218f5bb247341b..0b0d3a24f0398a5efa17a4c33110adabbb97c15e 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -10,10 +10,11 @@
 #include "PassManager.hpp"
 #include "PipelineManager.hpp"
 #include "vkcv/BufferManager.hpp"
+#include "SamplerManager.hpp"
+#include "ImageManager.hpp"
 #include "DescriptorManager.hpp"
 #include "Surface.hpp"
 #include "ImageLayoutTransitions.hpp"
-#include "Framebuffer.hpp"
 
 namespace vkcv
 {
@@ -95,11 +96,15 @@ namespace vkcv
             m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)},
             m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)),
 			m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())},
+			m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))),
+			m_ImageManager{std::unique_ptr<ImageManager>(new ImageManager(*m_BufferManager))},
             m_CommandResources(commandResources),
             m_SyncResources(syncResources)
 	{
     	m_BufferManager->m_core = this;
     	m_BufferManager->init();
+    	
+    	m_ImageManager->m_core = this;
 	}
 
 	Core::~Core() noexcept {
@@ -118,8 +123,7 @@ namespace vkcv
 
     PipelineHandle Core::createGraphicsPipeline(const PipelineConfig &config)
     {
-        const vk::RenderPass &pass = m_PassManager->getVkPass(config.m_PassHandle);
-        return m_PipelineManager->createPipeline(config, pass);
+        return m_PipelineManager->createPipeline(config, *m_PassManager);
     }
 
 
@@ -161,40 +165,117 @@ namespace vkcv
 		m_Context.getDevice().waitIdle();	// FIMXE: this is a sin against graphics programming, but its getting late - Alex
 		destroyTemporaryFramebuffers();
 	}
+	
+	vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass& renderpass,
+									  const int width, const int height, const std::vector<vk::ImageView>& attachments) {
+		const vk::FramebufferCreateFlags flags = {};
+		const vk::FramebufferCreateInfo createInfo(flags, renderpass, attachments.size(), attachments.data(), width, height, 1);
+		return device.createFramebuffer(createInfo);
+	}
 
-	void Core::renderTriangle(const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, 
-		const int width, const int height, const size_t pushConstantSize, const void *pushConstantData) {
+	void Core::renderMesh(
+		const PassHandle						renderpassHandle, 
+		const PipelineHandle					pipelineHandle, 
+		const int								width, 
+		const int								height, 
+		const size_t							pushConstantSize, 
+		const void								*pushConstantData,
+		const std::vector<VertexBufferBinding>& vertexBufferBindings, 
+		const BufferHandle						indexBuffer, 
+		const size_t							indexCount,
+		const vkcv::ResourcesHandle				resourceHandle,
+		const size_t							resourceDescriptorSetIndex
+		) {
 
 		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
 			return;
 		}
 
 		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
+		const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle);
+		
+		ImageHandle depthImage;
+		
+		for (const auto& attachment : passConfig.attachments) {
+			if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
+				depthImage = m_ImageManager->createImage(width, height, 1, attachment.format);
+				break;
+			}
+		}
+		
 		const vk::ImageView imageView	= m_swapchainImageViews[m_currentSwapchainImageIndex];
 		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
         const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
+		const vk::Buffer vulkanIndexBuffer	= m_BufferManager->getBuffer(indexBuffer);
 
-		const vk::Framebuffer framebuffer = createFramebuffer(m_Context.getDevice(), renderpass, width, height, imageView);
+		std::vector<vk::ImageView> attachments;
+		attachments.push_back(imageView);
+		
+		if (depthImage) {
+			attachments.push_back(m_ImageManager->getVulkanImageView(depthImage));
+		}
+		
+		const vk::Framebuffer framebuffer = createFramebuffer(
+				m_Context.getDevice(),
+				renderpass,
+				width,
+				height,
+				attachments
+		);
+		
 		m_TemporaryFramebuffers.push_back(framebuffer);
 
+		auto &bufferManager = m_BufferManager;
+
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
-		submitCommands(submitInfo, [renderpass, renderArea, imageView, framebuffer, pipeline, pipelineLayout, pushConstantSize, pushConstantData](const vk::CommandBuffer& cmdBuffer) {
-
-			const std::array<float, 4> clearColor = { 0.f, 0.f, 0.f, 1.f };
-			const vk::ClearValue clearValues(clearColor);
 
-			const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, 1, &clearValues);
+		submitCommands(submitInfo, [&](const vk::CommandBuffer& cmdBuffer) {
+			std::vector<vk::ClearValue> clearValues;
+			
+			for (const auto& attachment : passConfig.attachments) {
+				if (attachment.load_operation == AttachmentOperation::CLEAR) {
+					float clear = 0.0f;
+					
+					if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
+						clear = 1.0f;
+					}
+					
+					clearValues.emplace_back(std::array<float, 4>{
+							clear,
+							clear,
+							clear,
+							1.f
+					});
+				}
+			}
+
+			const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data());
 			const vk::SubpassContents subpassContents = {};
 			cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
 
 			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
-            cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
-			cmdBuffer.draw(3, 1, 0, 0, {});
+
+			for (uint32_t i = 0; i < vertexBufferBindings.size(); i++) {
+				const auto &vertexBinding = vertexBufferBindings[i];
+				const auto vertexBuffer = bufferManager->getBuffer(vertexBinding.buffer);
+				cmdBuffer.bindVertexBuffers(i, (vertexBuffer), (vertexBinding.offset));
+			}
+			
+			if (resourceHandle) {
+				const vk::DescriptorSet descriptorSet = m_DescriptorManager->getDescriptorSet(resourceHandle, resourceDescriptorSetIndex);
+				cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, descriptorSet, nullptr);
+			}
+			
+			cmdBuffer.bindIndexBuffer(vulkanIndexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
+			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, pushConstantSize, pushConstantData);
+			cmdBuffer.drawIndexed(indexCount, 1, 0, 0, {});
 			cmdBuffer.endRenderPass();
-		}, nullptr);
+		}, [&]() {
+			m_ImageManager->destroyImage(depthImage);
+		});
 	}
 
 	void Core::endFrame() {
@@ -255,9 +336,33 @@ namespace vkcv
 			finish();
 		}
 	}
+	
+	SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
+									  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode) {
+    	return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
+    }
+    
+	Image Core::createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+	{
+    	return Image::create(m_ImageManager.get(), format, width, height, depth);
+	}
 
-    ResourcesHandle Core::createResourceDescription(const std::vector<DescriptorSet> &descriptorSets)
+    ResourcesHandle Core::createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets)
     {
         return m_DescriptorManager->createResourceDescription(descriptorSets);
     }
+
+	void Core::writeResourceDescription(ResourcesHandle handle, size_t setIndex, const DescriptorWrites &writes) {
+		m_DescriptorManager->writeResourceDescription(
+			handle, 
+			setIndex, 
+			writes, 
+			*m_ImageManager, 
+			*m_BufferManager, 
+			*m_SamplerManager);
+	}
+
+	vk::DescriptorSetLayout Core::getDescritorSetLayout(ResourcesHandle handle, size_t setIndex) {
+		return m_DescriptorManager->getDescriptorSetLayout(handle, setIndex);
+	}
 }
diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorConfig.cpp
index f437ab6833edca09f58499b6d74307606bba8999..c4f6e326560e91747d206fecc983525a5b7bb6dc 100644
--- a/src/vkcv/DescriptorConfig.cpp
+++ b/src/vkcv/DescriptorConfig.cpp
@@ -14,7 +14,7 @@ namespace vkcv {
         shaderStage{shaderStage}
     {};
 
-    DescriptorSet::DescriptorSet(std::vector<DescriptorBinding> bindings) noexcept :
+    DescriptorSetConfig::DescriptorSetConfig(std::vector<DescriptorBinding> bindings) noexcept :
         bindings{std::move(bindings)}
     {};
 }
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index bcebb142af2f29c23e77185112cbb83a827e41ba..cf689b44a3865585fcc2d52620badbb4919da644 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -8,7 +8,7 @@ namespace vkcv
     descriptorSetLayouts{std::move(layouts)}
     {}
     DescriptorManager::DescriptorManager(vk::Device device) noexcept:
-        m_Device{ device }, m_NextResourceDescriptionID{ 1 }
+        m_Device{ device }, m_NextResourceDescriptionID{ 0 }
     {
         /**
          * Allocate a set size for the initial pool, namely 1000 units of each descriptor type below.
@@ -40,7 +40,7 @@ namespace vkcv
         m_Device.destroy(m_Pool);
     }
 
-    ResourcesHandle DescriptorManager::createResourceDescription(const std::vector<DescriptorSet> &descriptorSets)
+    ResourcesHandle DescriptorManager::createResourceDescription(const std::vector<DescriptorSetConfig> &descriptorSets)
     {
         std::vector<vk::DescriptorSet> vk_sets;
         std::vector<vk::DescriptorSetLayout> vk_setLayouts;
@@ -64,7 +64,7 @@ namespace vkcv
             if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &layout) != vk::Result::eSuccess)
             {
                 std::cout << "FAILED TO CREATE DESCRIPTOR SET LAYOUT" << std::endl;
-                return ResourcesHandle{0};
+                return ResourcesHandle();
             };
             vk_setLayouts.push_back(layout);
         }
@@ -79,12 +79,158 @@ namespace vkcv
             for(const auto &layout : vk_setLayouts)
                 m_Device.destroy(layout);
 
-            return ResourcesHandle{0};
+            return ResourcesHandle();
         };
 
         m_ResourceDescriptions.emplace_back(vk_sets, vk_setLayouts);
-        return ResourcesHandle{m_NextResourceDescriptionID++};
+        return ResourcesHandle(m_NextResourceDescriptionID++);
     }
+    
+    struct WriteDescriptorSetInfo {
+		size_t imageInfoIndex;
+		size_t bufferInfoIndex;
+		uint32_t binding;
+		vk::DescriptorType type;
+    };
+
+	void DescriptorManager::writeResourceDescription(
+		ResourcesHandle			handle, 
+		size_t					setIndex,
+		const DescriptorWrites	&writes,
+		const ImageManager		&imageManager, 
+		const BufferManager		&bufferManager,
+		const SamplerManager	&samplerManager) {
+
+		vk::DescriptorSet set = m_ResourceDescriptions[handle.getId()].descriptorSets[setIndex];
+
+		std::vector<vk::DescriptorImageInfo> imageInfos;
+		std::vector<vk::DescriptorBufferInfo> bufferInfos;
+		
+		std::vector<WriteDescriptorSetInfo> writeInfos;
+
+		for (const auto& write : writes.sampledImageWrites) {
+			const vk::DescriptorImageInfo imageInfo(
+				nullptr,
+				imageManager.getVulkanImageView(write.image),
+				vk::ImageLayout::eShaderReadOnlyOptimal
+			);
+			
+			imageInfos.push_back(imageInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					imageInfos.size(),
+					0,
+					write.binding,
+					vk::DescriptorType::eSampledImage,
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto& write : writes.storageImageWrites) {
+			const vk::DescriptorImageInfo imageInfo(
+				nullptr,
+				imageManager.getVulkanImageView(write.image),
+				vk::ImageLayout::eGeneral
+			);
+			
+			imageInfos.push_back(imageInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					imageInfos.size(),
+					0,
+					write.binding,
+					vk::DescriptorType::eStorageImage
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto& write : writes.uniformBufferWrites) {
+			const vk::DescriptorBufferInfo bufferInfo(
+				bufferManager.getBuffer(write.buffer),
+				static_cast<uint32_t>(0),
+				bufferManager.getBufferSize(write.buffer)
+			);
+			
+			bufferInfos.push_back(bufferInfo);
+
+			WriteDescriptorSetInfo vulkanWrite = {
+					0,
+					bufferInfos.size(),
+					write.binding,
+					vk::DescriptorType::eUniformBuffer
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto& write : writes.storageBufferWrites) {
+			const vk::DescriptorBufferInfo bufferInfo(
+				bufferManager.getBuffer(write.buffer),
+				static_cast<uint32_t>(0),
+				bufferManager.getBufferSize(write.buffer)
+			);
+			
+			bufferInfos.push_back(bufferInfo);
+			
+			WriteDescriptorSetInfo vulkanWrite = {
+					0,
+					bufferInfos.size(),
+					write.binding,
+					vk::DescriptorType::eStorageBuffer
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto& write : writes.samplerWrites) {
+			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,
+					vk::DescriptorType::eSampler
+			};
+			
+			writeInfos.push_back(vulkanWrite);
+		}
+		
+		std::vector<vk::WriteDescriptorSet> vulkanWrites;
+		
+		for (const auto& write : writeInfos) {
+			vk::WriteDescriptorSet vulkanWrite(
+					set,
+					write.binding,
+					static_cast<uint32_t>(0),
+					1,
+					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);
+	}
+
+	vk::DescriptorSet DescriptorManager::getDescriptorSet(ResourcesHandle handle, size_t index) {
+		return m_ResourceDescriptions[handle.getId()].descriptorSets[index];
+	}
+
+	vk::DescriptorSetLayout DescriptorManager::getDescriptorSetLayout(ResourcesHandle handle, size_t index) {
+		return m_ResourceDescriptions[handle.getId()].descriptorSetLayouts[index];
+	}
 
     vk::DescriptorType DescriptorManager::convertDescriptorTypeFlag(DescriptorType type) {
         switch (type)
@@ -95,9 +241,12 @@ namespace vkcv
                 return vk::DescriptorType::eStorageBuffer;
             case DescriptorType::SAMPLER:
                 return vk::DescriptorType::eSampler;
-            case DescriptorType::IMAGE:
+            case DescriptorType::IMAGE_SAMPLED:
                 return vk::DescriptorType::eSampledImage;
+			case DescriptorType::IMAGE_STORAGE:
+				return vk::DescriptorType::eStorageImage;
             default:
+				std::cerr << "Error: DescriptorManager::convertDescriptorTypeFlag, unknown DescriptorType" << std::endl;
                 return vk::DescriptorType::eUniformBuffer;
         }
     }
diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp
index 744f557051bee3a6524abcf16822c3faa9c78576..937fb278cda241e9153881d5c27662ac929781cd 100644
--- a/src/vkcv/DescriptorManager.hpp
+++ b/src/vkcv/DescriptorManager.hpp
@@ -2,6 +2,11 @@
 
 #include "vkcv/Handles.hpp"
 #include "vkcv/DescriptorConfig.hpp"
+#include "vkcv/DescriptorWrites.hpp"
+
+#include "ImageManager.hpp"
+#include "vkcv/BufferManager.hpp"
+#include "SamplerManager.hpp"
 
 namespace vkcv
 {
@@ -18,11 +23,22 @@ namespace vkcv
 		* @param[in] vector of filled vkcv::DescriptorSet structs
 		* @return index into that objects a resource handle
 		*/
-        ResourcesHandle createResourceDescription(const std::vector<DescriptorSet> & descriptorSets);
+        ResourcesHandle createResourceDescription(const std::vector<DescriptorSetConfig> & descriptorSets);
+
+		void writeResourceDescription(
+			ResourcesHandle			handle,
+			size_t					setIndex,
+			const DescriptorWrites& writes,
+			const ImageManager& imageManager,
+			const BufferManager& bufferManager,
+			const SamplerManager& samplerManager);
+
+		vk::DescriptorSet		getDescriptorSet(ResourcesHandle handle, size_t index);
+		vk::DescriptorSetLayout getDescriptorSetLayout(ResourcesHandle handle, size_t index);
 
 	private:
-		vk::Device m_Device;
-        vk::DescriptorPool m_Pool;
+		vk::Device			m_Device;
+        vk::DescriptorPool	m_Pool;
 
 
 		/**
diff --git a/src/vkcv/Framebuffer.cpp b/src/vkcv/Framebuffer.cpp
deleted file mode 100644
index 3b3e8000668e460d476b211984e9e12249f066c0..0000000000000000000000000000000000000000
--- a/src/vkcv/Framebuffer.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "Framebuffer.hpp"
-
-namespace vkcv {
-	vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass renderpass, 
-		const int width, const int height, const vk::ImageView imageView) {
-		const vk::FramebufferCreateFlags flags = {};
-		const uint32_t attachmentCount = 1;	// TODO: proper value
-		const vk::FramebufferCreateInfo createInfo(flags, renderpass, attachmentCount, &imageView, width, height, 1);
-		return device.createFramebuffer(createInfo, nullptr, {});
-	}
-}
\ No newline at end of file
diff --git a/src/vkcv/Framebuffer.hpp b/src/vkcv/Framebuffer.hpp
deleted file mode 100644
index 7d5d718adbde0c3f8eb8d97c539fb73f7771987f..0000000000000000000000000000000000000000
--- a/src/vkcv/Framebuffer.hpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-
-namespace vkcv{
-	vk::Framebuffer createFramebuffer(const vk::Device device, const vk::RenderPass renderpass,
-		const int width, const int height, const vk::ImageView imageView);
-}
\ No newline at end of file
diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp
index 1337a132ae0b8721bd2776e24212f73dfb94ae46..bd465d4e71da31c554d82ceb4b8bce7b0ee04129 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -1 +1,33 @@
 #include "vkcv/Handles.hpp"
+
+namespace vkcv {
+	
+	Handle::Handle() :
+		m_id(UINT64_MAX)
+	{}
+	
+	Handle::Handle(uint64_t id) :
+		m_id(id)
+	{}
+	
+	uint64_t Handle::getId() const {
+		return m_id;
+	}
+	
+	Handle::operator bool() const {
+		return (m_id < UINT64_MAX);
+	}
+	
+	bool Handle::operator!() const {
+		return (m_id == UINT64_MAX);
+	}
+	
+	std::ostream& operator << (std::ostream& out, const Handle& handle) {
+		if (handle) {
+			return out << "[Handle: " << handle.getId() << "]";
+		} else {
+			return out << "[Handle: none]";
+		}
+	}
+	
+}
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7104f784231418da4f1c26364459dbf735fdde97
--- /dev/null
+++ b/src/vkcv/Image.cpp
@@ -0,0 +1,62 @@
+/**
+ * @authors Lars Hoerttrich
+ * @file vkcv/Image.cpp
+ * @brief class for image handles
+ */
+#include "vkcv/Image.hpp"
+#include "ImageManager.hpp"
+
+namespace vkcv{
+	
+	Image Image::create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+	{
+		return Image(manager, manager->createImage(width, height, depth, format), format, width, height, depth);
+	}
+	
+	vk::Format Image::getFormat() const {
+		return m_format;
+	}
+	
+	uint32_t Image::getWidth() const {
+		return m_width;
+	}
+	
+	uint32_t Image::getHeight() const {
+		return m_height;
+	}
+	
+	uint32_t Image::getDepth() const {
+		return m_depth;
+	}
+	
+	vk::ImageLayout Image::getLayout() const {
+		return m_layout;
+	}
+
+	void Image::switchLayout(vk::ImageLayout newLayout)
+	{
+		m_manager->switchImageLayout(m_handle, m_layout, newLayout);
+		m_layout = newLayout;
+	}
+
+	vkcv::ImageHandle Image::getHandle() const {
+		return m_handle;
+	}
+	
+	void Image::fill(void *data, size_t size) {
+		m_manager->fillImage(m_handle, data, size);
+	}
+	
+	Image::Image(ImageManager* manager, const ImageHandle& handle,
+			  	 vk::Format format, uint32_t width, uint32_t height, uint32_t depth) :
+		m_manager(manager),
+		m_handle(handle),
+		m_format(format),
+		m_width(width),
+		m_height(height),
+		m_depth(depth),
+		m_layout(vk::ImageLayout::eUndefined)
+	{
+	}
+
+}
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3896d6bc4abdd24264ad5d468b49ebf08bd20be7
--- /dev/null
+++ b/src/vkcv/ImageManager.cpp
@@ -0,0 +1,397 @@
+/**
+ * @authors Lars Hoerttrich
+ * @file vkcv/ImageManager.cpp
+ * @brief class creating and managing images
+ */
+#include "ImageManager.hpp"
+#include "vkcv/Core.hpp"
+
+#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);
+		}
+
+		// failed to find memory type
+		return -1;
+	}
+
+	ImageManager::ImageManager(BufferManager& bufferManager) noexcept :
+		m_core(nullptr), m_bufferManager(bufferManager), m_images()
+	{
+	}
+
+	ImageManager::~ImageManager() noexcept {
+		for (uint64_t id = 0; id < m_images.size(); id++) {
+			destroyImage(ImageHandle(id));
+		}
+	}
+	
+	bool isDepthImageFormat(vk::Format format) {
+		if ((format == vk::Format::eD16Unorm) || (format == vk::Format::eD16UnormS8Uint) ||
+			(format == vk::Format::eD24UnormS8Uint) || (format == vk::Format::eD32Sfloat) ||
+			(format == vk::Format::eD32SfloatS8Uint)) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	ImageHandle ImageManager::createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format)
+	{
+		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
+		
+		const vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(format);
+		
+		vk::ImageCreateFlags createFlags;
+		vk::ImageUsageFlags imageUsageFlags = (
+				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst
+		);
+		
+		const bool isDepthFormat = isDepthImageFormat(format);
+		
+		if (isDepthFormat) {
+			imageUsageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
+		}
+
+		const vk::Device& device = m_core->getContext().getDevice();
+
+		vk::ImageType imageType = vk::ImageType::e3D;
+		vk::ImageViewType imageViewType = vk::ImageViewType::e3D;
+		
+		if (depth <= 1) {
+			if (height <= 1) {
+				imageType = vk::ImageType::e1D;
+				imageViewType = vk::ImageViewType::e1D;
+			} else {
+				imageType = vk::ImageType::e2D;
+				imageViewType = vk::ImageViewType::e2D;
+			}
+		}
+		
+		vk::ImageTiling imageTiling = vk::ImageTiling::eOptimal;
+		
+		if (!formatProperties.optimalTilingFeatures) {
+			if (!formatProperties.linearTilingFeatures)
+				return ImageHandle();
+			
+			imageTiling = vk::ImageTiling::eLinear;
+		}
+		
+		const vk::ImageFormatProperties imageFormatProperties = physicalDevice.getImageFormatProperties(
+				format, imageType, imageTiling, imageUsageFlags
+		);
+		
+		const uint32_t mipLevels = std::min<uint32_t>(1, imageFormatProperties.maxMipLevels);
+		const uint32_t arrayLayers = std::min<uint32_t>(1, imageFormatProperties.maxArrayLayers);
+		
+		const vk::ImageCreateInfo imageCreateInfo(
+			createFlags,
+			imageType,
+			format,
+			vk::Extent3D(width, height, depth),
+			mipLevels,
+			arrayLayers,
+			vk::SampleCountFlagBits::e1,
+			imageTiling,
+			imageUsageFlags,
+			vk::SharingMode::eExclusive,
+			{},
+			vk::ImageLayout::eUndefined
+		);
+
+		vk::Image image = device.createImage(imageCreateInfo);
+		
+		const vk::MemoryRequirements requirements = device.getImageMemoryRequirements(image);
+
+		vk::MemoryPropertyFlags memoryTypeFlags = vk::MemoryPropertyFlagBits::eDeviceLocal;
+
+		const uint32_t memoryTypeIndex = searchImageMemoryType(
+			physicalDevice.getMemoryProperties(),
+			requirements.memoryTypeBits,
+			memoryTypeFlags
+		);
+
+		vk::DeviceMemory memory = device.allocateMemory(vk::MemoryAllocateInfo(requirements.size, memoryTypeIndex));
+		device.bindImageMemory(image, memory, 0);
+
+		vk::ImageAspectFlags aspectFlags;
+		
+		if (isDepthFormat) {
+			aspectFlags = vk::ImageAspectFlagBits::eDepth;
+		} else {
+			aspectFlags = vk::ImageAspectFlagBits::eColor;
+		}
+		
+		const vk::ImageViewCreateInfo imageViewCreateInfo (
+				{},
+				image,
+				imageViewType,
+				format,
+				vk::ComponentMapping(
+						vk::ComponentSwizzle::eIdentity,
+						vk::ComponentSwizzle::eIdentity,
+						vk::ComponentSwizzle::eIdentity,
+						vk::ComponentSwizzle::eIdentity
+				),
+				vk::ImageSubresourceRange(
+						aspectFlags,
+						0,
+						mipLevels,
+						0,
+						arrayLayers
+				)
+		);
+		
+		vk::ImageView view = device.createImageView(imageViewCreateInfo);
+		
+		const uint64_t id = m_images.size();
+		m_images.push_back({ image, memory, view, width, height, depth, format, arrayLayers, mipLevels });
+		return ImageHandle(id);
+	}
+	
+	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return nullptr;
+		}
+		
+		auto& image = m_images[id];
+		
+		return image.m_handle;
+	}
+	
+	vk::DeviceMemory ImageManager::getVulkanDeviceMemory(const ImageHandle &handle) const {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return nullptr;
+		}
+		
+		auto& image = m_images[id];
+		
+		return image.m_memory;
+	}
+	
+	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle) const {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return nullptr;
+		}
+		
+		auto& image = m_images[id];
+		
+		return image.m_view;
+	}
+	
+	void ImageManager::switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return;
+		}
+		
+		auto& image = m_images[id];
+		
+		//alternativly we could use switch case for every variable to set
+		vk::AccessFlags sourceAccessMask;
+		vk::PipelineStageFlags sourceStage;
+		
+		vk::AccessFlags destinationAccessMask;
+		vk::PipelineStageFlags destinationStage;
+		
+		if ((oldLayout == vk::ImageLayout::eUndefined) &&
+			(newLayout == vk::ImageLayout::eTransferDstOptimal))
+		{
+			destinationAccessMask = vk::AccessFlagBits::eTransferWrite;
+			
+			sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
+			destinationStage = vk::PipelineStageFlagBits::eTransfer;
+		}
+		else if ((oldLayout == vk::ImageLayout::eTransferDstOptimal) &&
+				 (newLayout == vk::ImageLayout::eShaderReadOnlyOptimal))
+		{
+			sourceAccessMask = vk::AccessFlagBits::eTransferWrite;
+			destinationAccessMask = vk::AccessFlagBits::eShaderRead;
+			
+			sourceStage = vk::PipelineStageFlagBits::eTransfer;
+			destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
+		}
+		
+		vk::ImageAspectFlags aspectFlags;
+		
+		if (isDepthImageFormat(image.m_format)) {
+			aspectFlags = vk::ImageAspectFlagBits::eDepth;
+		} else {
+			aspectFlags = vk::ImageAspectFlagBits::eColor;
+		}
+		
+		vk::ImageSubresourceRange imageSubresourceRange(
+				aspectFlags,
+				0,
+				image.m_levels,
+				0,
+				image.m_layers
+		);
+		
+		vk::ImageMemoryBarrier imageMemoryBarrier(
+			sourceAccessMask,
+			destinationAccessMask,
+			oldLayout,
+			newLayout,
+			VK_QUEUE_FAMILY_IGNORED,
+			VK_QUEUE_FAMILY_IGNORED,
+			image.m_handle,
+			imageSubresourceRange
+		);
+		
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Graphics;
+		
+		m_core->submitCommands(
+			submitInfo,
+			[sourceStage, destinationStage, imageMemoryBarrier](const vk::CommandBuffer& commandBuffer) {
+				commandBuffer.pipelineBarrier(
+					sourceStage,
+					destinationStage,
+					{},
+					nullptr,
+					nullptr,
+					imageMemoryBarrier
+				);
+			},
+			nullptr
+		);
+	}
+	
+	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
+	{
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return;
+		}
+		
+		auto& image = m_images[id];
+		
+		switchImageLayout(
+				handle,
+				vk::ImageLayout::eUndefined,
+				vk::ImageLayout::eTransferDstOptimal
+		);
+		
+		uint32_t channels = 4; // TODO: check image.m_format
+		const size_t image_size = (
+				image.m_width * image.m_height * image.m_depth * channels
+		);
+		
+		const size_t max_size = std::min(size, image_size);
+		
+		BufferHandle bufferHandle = m_bufferManager.createBuffer(
+				BufferType::STAGING, max_size, BufferMemoryType::HOST_VISIBLE
+		);
+		
+		m_bufferManager.fillBuffer(bufferHandle, data, max_size, 0);
+		
+		vk::Buffer stagingBuffer = m_bufferManager.getBuffer(bufferHandle);
+		
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Transfer;
+		
+		m_core->submitCommands(
+				submitInfo,
+				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
+					vk::ImageAspectFlags aspectFlags;
+					
+					if (isDepthImageFormat(image.m_format)) {
+						aspectFlags = vk::ImageAspectFlagBits::eDepth;
+					} else {
+						aspectFlags = vk::ImageAspectFlagBits::eColor;
+					}
+					
+					const vk::BufferImageCopy region (
+							0,
+							0,
+							0,
+							vk::ImageSubresourceLayers(
+									aspectFlags,
+									0,
+									0,
+									image.m_layers
+							),
+							vk::Offset3D(0, 0, 0),
+							vk::Extent3D(image.m_width, image.m_height, image.m_depth)
+					);
+					
+					commandBuffer.copyBufferToImage(
+							stagingBuffer,
+							image.m_handle,
+							vk::ImageLayout::eTransferDstOptimal,
+							1,
+							&region
+					);
+				},
+				[&]() {
+					switchImageLayout(
+							handle,
+							vk::ImageLayout::eTransferDstOptimal,
+							vk::ImageLayout::eShaderReadOnlyOptimal
+					);
+					
+					m_bufferManager.destroyBuffer(bufferHandle);
+				}
+		);
+	}
+
+	void ImageManager::destroyImage(const ImageHandle& handle)
+	{
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_images.size()) {
+			return;
+		}
+		
+		auto& image = m_images[id];
+
+		const vk::Device& device = m_core->getContext().getDevice();
+		
+		if (image.m_view) {
+			device.destroyImageView(image.m_view);
+			image.m_view = nullptr;
+		}
+
+		if (image.m_memory) {
+			device.freeMemory(image.m_memory);
+			image.m_memory = nullptr;
+		}
+
+		if (image.m_handle) {
+			device.destroyImage(image.m_handle);
+			image.m_handle = nullptr;
+		}
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7dc6746f37b7d03900302afbd0536b909f9e48fc
--- /dev/null
+++ b/src/vkcv/ImageManager.hpp
@@ -0,0 +1,70 @@
+#pragma once
+/**
+ * @authors Lars Hoerttrich
+ * @file vkcv/ImageManager.hpp
+ * @brief class creating and managing images
+ */
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/BufferManager.hpp"
+#include "vkcv/Handles.hpp"
+
+namespace vkcv {
+	
+	class ImageManager
+	{
+		friend class Core;
+	private:
+		struct Image
+		{
+			vk::Image m_handle;
+			vk::DeviceMemory m_memory;
+			vk::ImageView m_view;
+			uint32_t m_width = 0;
+			uint32_t m_height = 0;
+			uint32_t m_depth = 0;
+			vk::Format m_format;
+			uint32_t m_layers = 1;
+			uint32_t m_levels = 1;
+		};
+		
+		Core* m_core;
+		BufferManager& m_bufferManager;
+		
+		std::vector<Image> m_images;
+		
+		ImageManager(BufferManager& bufferManager) noexcept;
+		
+	public:
+		~ImageManager() noexcept;
+		ImageManager(ImageManager&& other) = delete;
+		ImageManager(const ImageManager& other) = delete;
+
+		ImageManager& operator=(ImageManager&& other) = delete;
+		ImageManager& operator=(const ImageManager& other) = delete;
+		
+		ImageHandle createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format);
+		
+		[[nodiscard]]
+		vk::Image getVulkanImage(const ImageHandle& handle) const;
+		
+		[[nodiscard]]
+		vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle& handle) const;
+		
+		[[nodiscard]]
+		vk::ImageView getVulkanImageView(const ImageHandle& handle) const;
+		
+		void switchImageLayout(const ImageHandle& handle, vk::ImageLayout oldLayout, vk::ImageLayout newLayout);
+		void fillImage(const ImageHandle& handle, void* data, size_t size);
+
+		/**
+		 * Destroys and deallocates image represented by a given
+		 * buffer handle.
+		 *
+		 * @param handle Image handle
+		 */
+		void destroyImage(const ImageHandle& handle);
+		
+	};
+}
\ No newline at end of file
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index 820bf3ff8a84edb9d070c22c60327cf7cb661ee7..26e5f290d04ebaf16940cd99386253b5ab3622cc 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -49,17 +49,17 @@ namespace vkcv
 
     PassManager::PassManager(vk::Device device) noexcept :
     m_Device{device},
-    m_RenderPasses{},
-    m_NextPassId{1}
+    m_Passes{},
+    m_NextPassId(0)
     {}
 
     PassManager::~PassManager() noexcept
     {
-        for(const auto &pass : m_RenderPasses)
-            m_Device.destroy(pass);
-
-        m_RenderPasses.clear();
-        m_NextPassId = 1;
+        for(const auto &pass : m_Passes)
+            m_Device.destroy(pass.m_Handle);
+	
+		m_Passes.clear();
+        m_NextPassId = 0;
     }
 
     PassHandle PassManager::createPass(const PassConfig &config)
@@ -90,46 +90,74 @@ namespace vkcv
                 colorAttachmentReferences.push_back(attachmentRef);
             }
 
-            vk::AttachmentDescription attachmentDesc({},
-                                                     format,
-                                                     vk::SampleCountFlagBits::e1,
-                                                     getVKLoadOpFromAttachOp(config.attachments[i].load_operation),
-                                                     getVkStoreOpFromAttachOp(config.attachments[i].store_operation),
-                                                     vk::AttachmentLoadOp::eDontCare,
-                                                     vk::AttachmentStoreOp::eDontCare,
-                                                     getVkLayoutFromAttachLayout(config.attachments[i].layout_initial),
-                                                     getVkLayoutFromAttachLayout(config.attachments[i].layout_final));
+            vk::AttachmentDescription attachmentDesc(
+            		{},
+            		format,
+            		vk::SampleCountFlagBits::e1,
+            		getVKLoadOpFromAttachOp(config.attachments[i].load_operation),
+            		getVkStoreOpFromAttachOp(config.attachments[i].store_operation),
+            		vk::AttachmentLoadOp::eDontCare,
+            		vk::AttachmentStoreOp::eDontCare,
+            		getVkLayoutFromAttachLayout(config.attachments[i].layout_initial),
+            		getVkLayoutFromAttachLayout(config.attachments[i].layout_final)
+			);
+            
             attachmentDescriptions.push_back(attachmentDesc);
         }
-        vk::SubpassDescription subpassDescription({},
-                                                  vk::PipelineBindPoint::eGraphics,
-                                                  0,
-                                                  {},
-                                                  static_cast<uint32_t>(colorAttachmentReferences.size()),
-                                                  colorAttachmentReferences.data(),
-                                                  {},
-                                                  pDepthAttachment,
-                                                  0,
-                                                  {});
-
-        vk::RenderPassCreateInfo passInfo({},
-                                          static_cast<uint32_t>(attachmentDescriptions.size()),
-                                          attachmentDescriptions.data(),
-                                          1,
-                                          &subpassDescription,
-                                          0,
-                                          {});
+        
+        const vk::SubpassDescription subpassDescription(
+        		{},
+        		vk::PipelineBindPoint::eGraphics,
+        		0,
+        		{},
+        		static_cast<uint32_t>(colorAttachmentReferences.size()),
+        		colorAttachmentReferences.data(),
+        		{},
+        		pDepthAttachment,
+        		0,
+        		{}
+        );
 
-        vk::RenderPass vkObject{nullptr};
-        if(m_Device.createRenderPass(&passInfo, nullptr, &vkObject) != vk::Result::eSuccess)
-            return PassHandle{0};
+        const vk::RenderPassCreateInfo passInfo(
+        		{},
+        		static_cast<uint32_t>(attachmentDescriptions.size()),
+        		attachmentDescriptions.data(),
+        		1,
+        		&subpassDescription,
+        		0,
+        		{}
+	  	);
 
-        m_RenderPasses.push_back(vkObject);
-            return PassHandle{m_NextPassId++};
+        vk::RenderPass renderPass = m_Device.createRenderPass(passInfo);
+	
+		m_Passes.push_back({ renderPass, config });
+		return PassHandle(m_NextPassId++);
     }
 
     vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
     {
-        return m_RenderPasses[handle.id - 1];
+    	const uint64_t id = handle.getId();
+    	
+    	if (id >= m_Passes.size()) {
+    		return nullptr;
+    	}
+    	
+    	auto& pass = m_Passes[id];
+    	
+        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];
+	
+		return pass.m_Config;
     }
+    
 }
diff --git a/src/vkcv/PassManager.hpp b/src/vkcv/PassManager.hpp
index b6be2cb13d8d24bdb9759f8878917f99e31afbec..bfc20fe25ace95bd8d94832b953b6b14ab9cadee 100644
--- a/src/vkcv/PassManager.hpp
+++ b/src/vkcv/PassManager.hpp
@@ -10,8 +10,13 @@ namespace vkcv
     class PassManager
     {
     private:
+    	struct Pass {
+			vk::RenderPass m_Handle;
+			PassConfig m_Config;
+    	};
+    	
         vk::Device m_Device;
-        std::vector<vk::RenderPass> m_RenderPasses;
+        std::vector<Pass> m_Passes;
         uint64_t m_NextPassId;
     public:
         PassManager() = delete; // no default ctor
@@ -28,5 +33,9 @@ namespace vkcv
 
         [[nodiscard]]
         vk::RenderPass getVkPass(const PassHandle &handle) const;
+        
+        [[nodiscard]]
+        const PassConfig& getPassConfig(const PassHandle &handle) const;
+        
     };
 }
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
index c2b1415f10188f082d8eca576c2143e82f99e7fa..d317258470bde76e8b8ba8e1f9bc684ea469b6c0 100644
--- a/src/vkcv/PipelineConfig.cpp
+++ b/src/vkcv/PipelineConfig.cpp
@@ -8,10 +8,19 @@
 
 namespace vkcv {
 
-    PipelineConfig::PipelineConfig(const ShaderProgram& shaderProgram, uint32_t width, uint32_t height, PassHandle &passHandle):
+    PipelineConfig::PipelineConfig(
+		const ShaderProgram&						shaderProgram, 
+		uint32_t									width, 
+		uint32_t									height, 
+		PassHandle									&passHandle, 
+		const std::vector<VertexAttribute>			&vertexAttributes,
+		const std::vector<vk::DescriptorSetLayout>	&descriptorLayouts)
+		:
 		m_ShaderProgram(shaderProgram),
 		m_Height(height),
 		m_Width(width),
-		m_PassHandle(passHandle)
+		m_PassHandle(passHandle),
+		m_vertexAttributes(vertexAttributes),
+		m_descriptorLayouts(descriptorLayouts)
 		{}
 }
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 576cb47397a29184160423fdba6d1a09104a5566..1b0d665e9011b9c652a69e35c295a701a6029dd2 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -7,7 +7,7 @@ namespace vkcv
     m_Device{device},
     m_Pipelines{},
     m_PipelineLayouts{},
-    m_NextPipelineId{1}
+    m_NextPipelineId{0}
     {}
 
     PipelineManager::~PipelineManager() noexcept
@@ -20,17 +20,34 @@ namespace vkcv
 
         m_Pipelines.clear();
         m_PipelineLayouts.clear();
-        m_NextPipelineId = 1;
+        m_NextPipelineId = 0;
     }
 
-    PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, const vk::RenderPass &pass)
+	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
+	vk::Format vertexFormatToVulkanFormat(const VertexFormat format) {
+		switch (format) {
+		case VertexFormat::FLOAT: return vk::Format::eR32Sfloat;
+		case VertexFormat::FLOAT2: return vk::Format::eR32G32Sfloat;
+		case VertexFormat::FLOAT3: return vk::Format::eR32G32B32Sfloat;
+		case VertexFormat::FLOAT4: return vk::Format::eR32G32B32A32Sfloat;
+		case VertexFormat::INT: return vk::Format::eR32Sint;
+		case VertexFormat::INT2: return vk::Format::eR32G32Sint;
+		case VertexFormat::INT3: return vk::Format::eR32G32B32Sint;
+		case VertexFormat::INT4: return vk::Format::eR32G32B32A32Sint;
+		default: std::cerr << "Warning: Unknown vertex format" << std::endl; return vk::Format::eUndefined;
+		}
+	}
+
+    PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, PassManager& passManager)
     {
+		const vk::RenderPass &pass = passManager.getVkPass(config.m_PassHandle);
+    	
         const bool existsVertexShader = config.m_ShaderProgram.existsShader(ShaderStage::VERTEX);
         const bool existsFragmentShader = config.m_ShaderProgram.existsShader(ShaderStage::FRAGMENT);
         if (!(existsVertexShader && existsFragmentShader))
         {
             std::cout << "Core::createGraphicsPipeline requires vertex and fragment shader code" << std::endl;
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         // vertex shader stage
@@ -38,7 +55,7 @@ namespace vkcv
         vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data()));
         vk::ShaderModule vertexModule{};
         if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess)
-            return PipelineHandle{0};
+            return PipelineHandle();
 
         vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo(
                 {},
@@ -55,7 +72,7 @@ namespace vkcv
         if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess)
         {
             m_Device.destroy(vertexModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo(
@@ -67,15 +84,38 @@ namespace vkcv
         );
 
         // vertex input state
-        vk::VertexInputBindingDescription vertexInputBindingDescription(0, 12, vk::VertexInputRate::eVertex);
-        vk::VertexInputAttributeDescription vertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, 0);
 
+        // Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers
+        std::vector<vk::VertexInputAttributeDescription>	vertexAttributeDescriptions;
+		std::vector<vk::VertexInputBindingDescription>		vertexBindingDescriptions;
+
+        VertexLayout layout = config.m_ShaderProgram.getVertexLayout();
+        std::unordered_map<uint32_t, VertexInputAttachment> attachments = layout.attachmentMap;
+
+		for (int i = 0; i < attachments.size(); i++) {
+			VertexInputAttachment &attachment = attachments.at(i);
+
+            uint32_t	location		= attachment.location;
+            uint32_t	binding			= i;
+            vk::Format	vertexFormat	= vertexFormatToVulkanFormat(attachment.format);
+
+			//FIXME: hoping that order is the same and compatible: add explicit mapping and validation
+			const VertexAttribute attribute = config.m_vertexAttributes[i];
+
+            vertexAttributeDescriptions.push_back({location, binding, vertexFormatToVulkanFormat(attachment.format), 0});
+			vertexBindingDescriptions.push_back(vk::VertexInputBindingDescription(
+				binding,
+				attribute.stride + getFormatSize(attachment.format),
+				vk::VertexInputRate::eVertex));
+        }
+
+        // Handover Containers to PipelineVertexInputStateCreateIngo Struct
         vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
-                {},			// no vertex input until vertex buffer is implemented
-                0,			// 1,
-                nullptr,	// &vertexInputBindingDescription,
-                0,			// 1,
-                nullptr		// &vertexInputAttributeDescription
+                {},
+                vertexBindingDescriptions.size(),
+                vertexBindingDescriptions.data(),
+                vertexAttributeDescriptions.size(),
+                vertexAttributeDescriptions.data()
         );
 
         // input assembly state
@@ -146,19 +186,43 @@ namespace vkcv
         // pipeline layout
         vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
 			{},
-			{},
+			(config.m_descriptorLayouts),
 			(pushConstantRange));
         vk::PipelineLayout vkPipelineLayout{};
         if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
-
-        // graphics pipeline create
+	
+		const vk::PipelineDepthStencilStateCreateInfo depthStencilCreateInfo(
+				vk::PipelineDepthStencilStateCreateFlags(),
+				true,
+				true,
+				vk::CompareOp::eLessOrEqual,
+				false,
+				false,
+				{},
+				{},
+				0.0f,
+				1.0f
+		);
+	
+		const vk::PipelineDepthStencilStateCreateInfo* p_depthStencilCreateInfo = nullptr;
+		
+		const PassConfig& passConfig = passManager.getPassConfig(config.m_PassHandle);
+		
+		for (const auto& attachment : passConfig.attachments) {
+			if (attachment.layout_final == AttachmentLayout::DEPTH_STENCIL_ATTACHMENT) {
+				p_depthStencilCreateInfo = &depthStencilCreateInfo;
+				break;
+			}
+		}
+	
+		// graphics pipeline create
         std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
-        vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
+        const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
                 {},
                 static_cast<uint32_t>(shaderStages.size()),
                 shaderStages.data(),
@@ -168,7 +232,7 @@ namespace vkcv
                 &pipelineViewportStateCreateInfo,
                 &pipelineRasterizationStateCreateInfo,
                 &pipelineMultisampleStateCreateInfo,
-                nullptr,
+				p_depthStencilCreateInfo,
                 &pipelineColorBlendStateCreateInfo,
                 nullptr,
                 vkPipelineLayout,
@@ -183,7 +247,7 @@ namespace vkcv
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
-            return PipelineHandle{0};
+            return PipelineHandle();
         }
 
         m_Device.destroy(vertexModule);
@@ -191,16 +255,16 @@ namespace vkcv
 
         m_Pipelines.push_back(vkPipeline);
         m_PipelineLayouts.push_back(vkPipelineLayout);
-        return PipelineHandle{m_NextPipelineId++};
+        return PipelineHandle(m_NextPipelineId++);
     }
 
     vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const
     {
-        return m_Pipelines.at(handle.id -1);
+        return m_Pipelines.at(handle.getId());
     }
 
     vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const
     {
-        return m_PipelineLayouts.at(handle.id - 1);
+        return m_PipelineLayouts.at(handle.getId());
     }
 }
\ No newline at end of file
diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp
index b5c0948efa13a4021f424cc576f1403a1ec26ebe..896d0df1ce10f56d291ef1accf93f9783cdd9db4 100644
--- a/src/vkcv/PipelineManager.hpp
+++ b/src/vkcv/PipelineManager.hpp
@@ -4,6 +4,7 @@
 #include <vector>
 #include "vkcv/Handles.hpp"
 #include "vkcv/PipelineConfig.hpp"
+#include "PassManager.hpp"
 
 namespace vkcv
 {
@@ -25,7 +26,7 @@ namespace vkcv
         PipelineManager & operator=(const PipelineManager &other) = delete; // copy-assign op
         PipelineManager & operator=(PipelineManager &&other) = delete; // move-assign op
 
-        PipelineHandle createPipeline(const PipelineConfig &config, const vk::RenderPass &pass);
+        PipelineHandle createPipeline(const PipelineConfig &config, PassManager& passManager);
 
         [[nodiscard]]
         vk::Pipeline getVkPipeline(const PipelineHandle &handle) const;
diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7935bbc16a54f170cb38161629c109d56cf56d3c
--- /dev/null
+++ b/src/vkcv/SamplerManager.cpp
@@ -0,0 +1,127 @@
+
+#include "SamplerManager.hpp"
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+	
+	SamplerManager::SamplerManager(const vk::Device& device) noexcept :
+		m_device(device), m_samplers()
+	{}
+	
+	SamplerManager::~SamplerManager() {
+		for (uint64_t id = 0; id < m_samplers.size(); id++) {
+			destroySampler(SamplerHandle(id));
+		}
+	}
+	
+	SamplerHandle SamplerManager::createSampler(SamplerFilterType magFilter,
+												SamplerFilterType minFilter,
+												SamplerMipmapMode mipmapMode,
+												SamplerAddressMode addressMode) {
+		vk::Filter vkMagFilter;
+		vk::Filter vkMinFilter;
+		vk::SamplerMipmapMode vkMipmapMode;
+		vk::SamplerAddressMode vkAddressMode;
+		
+		switch (magFilter) {
+			case SamplerFilterType::NEAREST:
+				vkMagFilter = vk::Filter::eNearest;
+				break;
+			case SamplerFilterType::LINEAR:
+				vkMagFilter = vk::Filter::eLinear;
+				break;
+			default:
+				return SamplerHandle();
+		}
+		
+		switch (minFilter) {
+			case SamplerFilterType::NEAREST:
+				vkMinFilter = vk::Filter::eNearest;
+				break;
+			case SamplerFilterType::LINEAR:
+				vkMinFilter = vk::Filter::eLinear;
+				break;
+			default:
+				return SamplerHandle();
+		}
+		
+		switch (mipmapMode) {
+			case SamplerMipmapMode::NEAREST:
+				vkMipmapMode = vk::SamplerMipmapMode::eNearest;
+				break;
+			case SamplerMipmapMode::LINEAR:
+				vkMipmapMode = vk::SamplerMipmapMode::eLinear;
+				break;
+			default:
+				return SamplerHandle();
+		}
+		
+		switch (addressMode) {
+			case SamplerAddressMode::REPEAT:
+				vkAddressMode = vk::SamplerAddressMode::eRepeat;
+				break;
+			case SamplerAddressMode::MIRRORED_REPEAT:
+				vkAddressMode = vk::SamplerAddressMode::eMirroredRepeat;
+				break;
+			case SamplerAddressMode::CLAMP_TO_EDGE:
+				vkAddressMode = vk::SamplerAddressMode::eClampToEdge;
+				break;
+			case SamplerAddressMode::MIRROR_CLAMP_TO_EDGE:
+				vkAddressMode = vk::SamplerAddressMode::eMirrorClampToEdge;
+				break;
+			default:
+				return SamplerHandle();
+		}
+		
+		const vk::SamplerCreateInfo samplerCreateInfo (
+				vk::SamplerCreateFlags(),
+				vkMagFilter,
+				vkMinFilter,
+				vkMipmapMode,
+				vkAddressMode,
+				vkAddressMode,
+				vkAddressMode,
+				0.0f,
+				false,
+				16.0f,
+				false,
+				vk::CompareOp::eAlways,
+				0.0f,
+				1.0f,
+				vk::BorderColor::eIntOpaqueBlack,
+				false
+		);
+		
+		const vk::Sampler sampler = m_device.createSampler(samplerCreateInfo);
+		
+		const uint64_t id = m_samplers.size();
+		m_samplers.push_back(sampler);
+		return SamplerHandle(id);
+	}
+	
+	vk::Sampler SamplerManager::getVulkanSampler(const SamplerHandle &handle) const {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_samplers.size()) {
+			return nullptr;
+		}
+		
+		return m_samplers[id];
+	}
+	
+	void SamplerManager::destroySampler(const SamplerHandle &handle) {
+		const uint64_t id = handle.getId();
+		
+		if (id >= m_samplers.size()) {
+			return;
+		}
+		
+		auto& sampler = m_samplers[id];
+		
+		if (sampler) {
+			m_device.destroySampler(sampler);
+			sampler = nullptr;
+		}
+	}
+
+}
diff --git a/src/vkcv/SamplerManager.hpp b/src/vkcv/SamplerManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..41f58b2f33daaf8b08c785c05c7f14184cf47958
--- /dev/null
+++ b/src/vkcv/SamplerManager.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/Handles.hpp"
+#include "vkcv/Sampler.hpp"
+
+namespace vkcv {
+	
+	class Core;
+	
+	class SamplerManager {
+		friend class Core;
+	private:
+		vk::Device m_device;
+		std::vector<vk::Sampler> m_samplers;
+		
+		explicit SamplerManager(const vk::Device& device) noexcept;
+		
+	public:
+		~SamplerManager();
+		
+		SamplerManager(const SamplerManager& other) = delete;
+		SamplerManager(SamplerManager&& other) = delete;
+		
+		SamplerManager& operator=(const SamplerManager& other) = delete;
+		SamplerManager& operator=(SamplerManager&& other) = delete;
+		
+		SamplerHandle createSampler(SamplerFilterType magFilter,
+							  		SamplerFilterType minFilter,
+							  		SamplerMipmapMode mipmapMode,
+							  		SamplerAddressMode addressMode);
+		
+		[[nodiscard]]
+		vk::Sampler getVulkanSampler(const SamplerHandle& handle) const;
+		
+		void destroySampler(const SamplerHandle& handle);
+	
+	};
+	
+}
diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 87ccdefbfec0b4891d3152d30aa6c9f6c8c0d5ea..5185b8b402eae5cd514689ba51a06e1a437271bf 100644
--- a/src/vkcv/ShaderProgram.cpp
+++ b/src/vkcv/ShaderProgram.cpp
@@ -27,8 +27,46 @@ namespace vkcv {
         return buffer;
 	}
 
+	VertexFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
+        switch (basetype) {
+            case spirv_cross::SPIRType::Int:
+                switch (vecsize) {
+                    case 1:
+                        return VertexFormat::INT;
+                    case 2:
+                        return VertexFormat::INT2;
+                    case 3:
+                        return VertexFormat::INT3;
+                    case 4:
+                        return VertexFormat::INT4;
+                    default:
+                        break;
+                }
+                break;
+            case spirv_cross::SPIRType::Float:
+                switch (vecsize) {
+                    case 1:
+                        return VertexFormat::FLOAT;
+                    case 2:
+                        return VertexFormat::FLOAT2;
+                    case 3:
+                        return VertexFormat::FLOAT3;
+                    case 4:
+                        return VertexFormat::FLOAT4;
+                    default:
+                        break;
+                }
+                break;
+            default:
+                break;
+        }
+        std::cout << "Shader Program Reflection: unknown Vertex Format" << std::endl;
+        return VertexFormat::FLOAT;
+	}
+
 	ShaderProgram::ShaderProgram() noexcept :
-	m_Shaders{}
+	m_Shaders{},
+    m_VertexLayout{}
 	{}
 
 	bool ShaderProgram::addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath)
@@ -59,4 +97,40 @@ namespace vkcv {
 	    else
 	        return true;
     }
+
+    void ShaderProgram::reflectShader(ShaderStage shaderStage)
+    {
+        auto shaderCodeChar = m_Shaders.at(shaderStage).shaderCode;
+        std::vector<uint32_t> shaderCode;
+
+        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++) {
+            shaderCode.push_back(((uint32_t*) shaderCodeChar.data())[i]);
+        }
+
+        spirv_cross::Compiler comp(move(shaderCode));
+        spirv_cross::ShaderResources resources = comp.get_shader_resources();
+
+		if (shaderStage == ShaderStage::VERTEX) {
+			std::vector<VertexInputAttachment> inputVec;
+			uint32_t offset = 0;
+
+			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++) {
+				auto& u = resources.stage_inputs[i];
+				const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
+
+				VertexInputAttachment input = VertexInputAttachment(comp.get_decoration(u.id, spv::DecorationLocation),
+					0,
+					convertFormat(base_type.basetype, base_type.vecsize),
+					offset);
+				inputVec.push_back(input);
+				offset += base_type.vecsize * base_type.width / 8;
+			}
+
+			m_VertexLayout = VertexLayout(inputVec);
+		}
+    }
+
+    const VertexLayout& ShaderProgram::getVertexLayout() const{
+        return m_VertexLayout;
+	}
 }
diff --git a/src/vkcv/VertexLayout.cpp b/src/vkcv/VertexLayout.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b06c6743e1e19a5e282af248ab6b590eb97529fd
--- /dev/null
+++ b/src/vkcv/VertexLayout.cpp
@@ -0,0 +1,53 @@
+//
+// Created by Charlotte on 28.05.2021.
+//
+
+#include "vkcv/VertexLayout.hpp"
+
+namespace vkcv {
+    uint32_t getFormatSize(VertexFormat format) {
+        switch (format) {
+            case VertexFormat::FLOAT:
+                return 4;
+            case VertexFormat::FLOAT2:
+                return 8;
+            case VertexFormat::FLOAT3:
+                return 12;
+            case VertexFormat::FLOAT4:
+                return 16;
+            case VertexFormat::INT:
+                return 4;
+            case VertexFormat::INT2:
+                return 8;
+            case VertexFormat::INT3:
+                return 12;
+            case VertexFormat::INT4:
+                return 16;
+            default:
+                break;
+        }
+        std::cout << "VertexLayout: No format given" << std::endl;
+        return 0;
+    }
+
+    VertexInputAttachment::VertexInputAttachment(uint32_t location, uint32_t binding, VertexFormat format, uint32_t offset) noexcept:
+            location{location},
+            binding{binding},
+            format{format},
+            offset{offset}
+            {}
+
+    VertexLayout::VertexLayout() noexcept :
+    stride{0},
+    attachmentMap()
+    {}
+
+    VertexLayout::VertexLayout(const std::vector<VertexInputAttachment> &inputs) noexcept {
+        stride = 0;
+        for (const auto &input : inputs) {
+            attachmentMap.insert(std::make_pair(input.location, input));
+            stride += getFormatSize(input.format);
+        }
+    }
+
+}
\ No newline at end of file