diff --git a/config/Sources.cmake b/config/Sources.cmake
index 54bb3485ed975669668d987787975f019aa6358b..7ae106e2538c66179ab1ed50408551c43b785bc3 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -21,6 +21,8 @@ set(vkcv_sources
 
 		${vkcv_include}/vkcv/Buffer.hpp
 		
+		${vkcv_include}/vkcv/PushConstants.hpp
+		
 		${vkcv_include}/vkcv/BufferManager.hpp
 		${vkcv_source}/vkcv/BufferManager.cpp
 
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index cbbe1e908cdb74891ab9bfe4416c03e487e76b26..cd3676f45bf0891de97ab88ff74cdd980f6920da 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -223,9 +223,9 @@ namespace vkcv
 			Multisampling   multisampling = Multisampling::None);
 
         [[nodiscard]]
-        const uint32_t getImageWidth(ImageHandle imageHandle);
+        uint32_t getImageWidth(ImageHandle imageHandle);
         [[nodiscard]]
-        const uint32_t getImageHeight(ImageHandle imageHandle);
+        uint32_t getImageHeight(ImageHandle imageHandle);
 
         /** TODO:
          *   @param setDescriptions
@@ -246,7 +246,7 @@ namespace vkcv
             const CommandStreamHandle       cmdStreamHandle,
 			const PassHandle                renderpassHandle, 
 			const PipelineHandle            pipelineHandle,
-			const PushConstantData          &pushConstantData,
+			const PushConstants             &pushConstants,
 			const std::vector<DrawcallInfo> &drawcalls,
 			const std::vector<ImageHandle>  &renderTargets);
 
@@ -255,7 +255,7 @@ namespace vkcv
 			PipelineHandle computePipeline,
 			const uint32_t dispatchCount[3],
 			const std::vector<DescriptorSetUsage> &descriptorSetUsages,
-			const PushConstantData& pushConstantData);
+			const PushConstants& pushConstants);
 
 		/**
 		 * @brief end recording and present image
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
index c6d0dfd1bc60988afb8b6a9326a8d50d8a4ea32e..776322e6270431f9fa52fd7c3cb4551e5b4bf752 100644
--- a/include/vkcv/DescriptorConfig.hpp
+++ b/include/vkcv/DescriptorConfig.hpp
@@ -11,6 +11,7 @@ namespace vkcv
     {
         vk::DescriptorSet       vulkanHandle;
         vk::DescriptorSetLayout layout;
+        size_t                  poolIndex;
     };
 
     /*
diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
index 9f162a499a38d5633703f70eec8a8682e3328d72..572fc2b6b51735bdcd7eb77c1dd9d4a3482a1640 100644
--- a/include/vkcv/DrawcallRecording.hpp
+++ b/include/vkcv/DrawcallRecording.hpp
@@ -2,6 +2,7 @@
 #include <vulkan/vulkan.hpp>
 #include <vkcv/Handles.hpp>
 #include <vkcv/DescriptorConfig.hpp>
+#include <vkcv/PushConstants.hpp>
 
 namespace vkcv {
     struct VertexBufferBinding {
@@ -29,13 +30,6 @@ namespace vkcv {
         size_t                              indexCount;
     };
 
-    struct PushConstantData {
-        inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall) {}
-
-        void* data;
-        size_t  sizePerDrawcall;
-    };
-
     struct DrawcallInfo {
         inline DrawcallInfo(const Mesh& mesh, const std::vector<DescriptorSetUsage>& descriptorSets, const uint32_t instanceCount = 1)
             : mesh(mesh), descriptorSets(descriptorSets), instanceCount(instanceCount){}
@@ -49,7 +43,7 @@ namespace vkcv {
         const DrawcallInfo      &drawcall,
         vk::CommandBuffer       cmdBuffer,
         vk::PipelineLayout      pipelineLayout,
-        const PushConstantData  &pushConstantData,
+        const PushConstants     &pushConstants,
         const size_t            drawcallIndex);
 
 }
\ No newline at end of file
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index 85ab2b81e2718b3890ba361c988d5db0e40e84c7..3fca76f70315c0e08e404d7acd8c2010a3501c24 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -31,21 +31,21 @@ namespace vkcv {
 		uint32_t getDepth() const;
 
 		[[nodiscard]]
-		vkcv::ImageHandle getHandle() const;
+		const vkcv::ImageHandle& getHandle() const;
 
 		[[nodiscard]]
 		uint32_t getMipCount() const;
 
 		void switchLayout(vk::ImageLayout newLayout);
 		
-		void fill(void* data, size_t size = SIZE_MAX);
+		void fill(const void* data, size_t size = SIZE_MAX);
 		void generateMipChainImmediate();
 		void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream);
 	private:
 	    // TODO: const qualifier removed, very hacky!!!
 	    //  Else you cannot recreate an image. Pls fix.
 		ImageManager*       m_manager;
-		ImageHandle   m_handle;
+		ImageHandle   		m_handle;
 
 		Image(ImageManager* manager, const ImageHandle& handle);
 		
diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp
index d484711f642506926b1281a830fb2c9caf8240a2..1ae0f211e1a3255d624cf78985b0797e9d90c634 100644
--- a/include/vkcv/Logger.hpp
+++ b/include/vkcv/Logger.hpp
@@ -5,6 +5,7 @@
 namespace vkcv {
 	
 	enum class LogLevel {
+		RAW_INFO,
 		INFO,
 		WARNING,
 		ERROR
@@ -12,6 +13,7 @@ namespace vkcv {
 	
 	constexpr auto getLogOutput(LogLevel level) {
 		switch (level) {
+			case LogLevel::RAW_INFO:
 			case LogLevel::INFO:
 				return stdout;
 			default:
@@ -21,6 +23,7 @@ namespace vkcv {
 	
 	constexpr const char* getLogName(LogLevel level) {
 		switch (level) {
+			case LogLevel::RAW_INFO:
 			case LogLevel::INFO:
 				return "INFO";
 			case LogLevel::WARNING:
@@ -41,24 +44,33 @@ namespace vkcv {
 #define __PRETTY_FUNCTION__ __FUNCSIG__
 #endif
 
-#define vkcv_log(level, ...) {      \
-  char output_message [             \
-    VKCV_DEBUG_MESSAGE_LEN          \
-  ];                                \
-  snprintf(                         \
-    output_message,                 \
-    VKCV_DEBUG_MESSAGE_LEN,         \
-    __VA_ARGS__                     \
-  );                                \
-  fprintf(                          \
-    getLogOutput(level),            \
-    "[%s]: %s [%s, line %d: %s]\n", \
-  	vkcv::getLogName(level),        \
-    output_message,                 \
-    __FILE__,                       \
-    __LINE__,                       \
-    __PRETTY_FUNCTION__             \
-  );                                \
+#define vkcv_log(level, ...) {             \
+  char output_message [                    \
+    VKCV_DEBUG_MESSAGE_LEN                 \
+  ];                                       \
+  snprintf(                                \
+    output_message,                        \
+    VKCV_DEBUG_MESSAGE_LEN,                \
+    __VA_ARGS__                            \
+  );                                       \
+  if (level != vkcv::LogLevel::RAW_INFO) { \
+    fprintf(                               \
+      getLogOutput(level),                 \
+      "[%s]: %s [%s, line %d: %s]\n",      \
+      vkcv::getLogName(level),             \
+      output_message,                      \
+      __FILE__,                            \
+      __LINE__,                            \
+      __PRETTY_FUNCTION__                  \
+    );                                     \
+  } else {                                 \
+    fprintf(                               \
+      getLogOutput(level),                 \
+      "[%s]: %s\n",                        \
+      vkcv::getLogName(level),             \
+      output_message                       \
+    );                                     \
+  }                                        \
 }
 
 #else
diff --git a/include/vkcv/PushConstants.hpp b/include/vkcv/PushConstants.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d974fbe6241daf948b13929305fb24aff5ec06f5
--- /dev/null
+++ b/include/vkcv/PushConstants.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "Logger.hpp"
+
+namespace vkcv {
+	
+	class PushConstants {
+	private:
+		std::vector<uint8_t> m_data;
+		size_t m_sizePerDrawcall;
+		
+	public:
+		template<typename T>
+		PushConstants() : PushConstants(sizeof(T)) {}
+		
+		explicit PushConstants(size_t sizePerDrawcall) :
+		m_data(),
+		m_sizePerDrawcall(sizePerDrawcall) {}
+		
+		PushConstants(const PushConstants& other) = default;
+		PushConstants(PushConstants&& other) = default;
+		
+		~PushConstants() = default;
+		
+		PushConstants& operator=(const PushConstants& other) = default;
+		PushConstants& operator=(PushConstants&& other) = default;
+		
+		[[nodiscard]]
+		size_t getSizePerDrawcall() const {
+			return m_sizePerDrawcall;
+		}
+		
+		[[nodiscard]]
+		size_t getFullSize() const {
+			return m_data.size();
+		}
+		
+		[[nodiscard]]
+		size_t getDrawcallCount() const {
+			return (m_data.size() / m_sizePerDrawcall);
+		}
+		
+		void clear() {
+			m_data.clear();
+		}
+		
+		template<typename T = uint8_t>
+		bool appendDrawcall(const T& value) {
+			if (sizeof(T) != m_sizePerDrawcall) {
+				vkcv_log(LogLevel::WARNING, "Size (%lu) of value does not match the specified size per drawcall (%lu)",
+						 sizeof(value), m_sizePerDrawcall);
+				return false;
+			}
+			
+			const size_t offset = m_data.size();
+			m_data.resize(offset + sizeof(value));
+			std::memcpy(m_data.data() + offset, &value, sizeof(value));
+			return true;
+		}
+		
+		template<typename T = uint8_t>
+		T& getDrawcall(size_t index) {
+			const size_t offset = (index * m_sizePerDrawcall);
+			return *reinterpret_cast<T*>(m_data.data() + offset);
+		}
+		
+		template<typename T = uint8_t>
+		const T& getDrawcall(size_t index) const {
+			const size_t offset = (index * m_sizePerDrawcall);
+			return *reinterpret_cast<const T*>(m_data.data() + offset);
+		}
+		
+		[[nodiscard]]
+		const void* getDrawcallData(size_t index) const {
+			const size_t offset = (index * m_sizePerDrawcall);
+			return reinterpret_cast<const void*>(m_data.data() + offset);
+		}
+		
+		[[nodiscard]]
+		const void* getData() const {
+			if (m_data.empty()) {
+				return nullptr;
+			} else {
+				return m_data.data();
+			}
+		}
+		
+	};
+	
+}
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index 28b2184b2a83515a514f1428733bcf8cf1499633..802200ad5deb76decbb75e30e1fbd14bff3b7e3b 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -1,8 +1,9 @@
 
 # Add new modules here:
 add_subdirectory(asset_loader)
-add_subdirectory(material)
 add_subdirectory(camera)
 add_subdirectory(gui)
+add_subdirectory(material)
+add_subdirectory(scene)
 add_subdirectory(shader_compiler)
 add_subdirectory(testing)
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
index 9d85df7dce6d043630fd9d39287cace8530dbd6a..8a8c5df5d74cf1402bd8810172657ba77ddb2d56 100644
--- a/modules/camera/include/vkcv/camera/Camera.hpp
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -3,6 +3,8 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/matrix_transform.hpp>
 #include <glm/gtc/matrix_access.hpp>
+#include <glm/vec3.hpp>
+#include <glm/mat4x4.hpp>
 
 namespace vkcv::camera {
 
@@ -20,9 +22,6 @@ namespace vkcv::camera {
 		glm::vec3 m_up;
         glm::vec3 m_position;
         glm::vec3 m_center;
-
-        float m_pitch;
-        float m_yaw;
 	
 		/**
 		 * @brief Sets the view matrix of the camera to @p view
@@ -75,7 +74,7 @@ namespace vkcv::camera {
          * @brief Gets the current projection of the camera
          * @return The current projection matrix
          */
-        glm::mat4 getProjection() const;
+        const glm::mat4& getProjection() const;
 
         /**
          * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied
@@ -156,6 +155,20 @@ namespace vkcv::camera {
          * @param[in] center The new center point.
          */
         void setCenter(const glm::vec3& center);
+        
+        /**
+         * @brief Gets the angles of the camera.
+         * @param[out] pitch The pitch value in radians
+         * @param[out] yaw The yaw value in radians
+         */
+		void getAngles(float& pitch, float& yaw);
+  
+		/**
+		 * @brief Sets the angles of the camera.
+		 * @param pitch The new pitch value in radians
+		 * @param yaw The new yaw value in radians
+		 */
+		void setAngles(float pitch, float yaw);
 
         /**
          * @brief Gets the pitch value of the camera in degrees.
diff --git a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
index 4166bda9f6cb62e4c8f1b650557b00c6ec94b2a1..20336f7a10644c73e451c5106f37545e84eb27f7 100644
--- a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -14,6 +14,8 @@ namespace vkcv::camera {
         float m_cameraSpeed;
         float m_scrollSensitivity;
         float m_radius;
+        float m_pitch;
+        float m_yaw;
 
         /**
          * @brief Updates the current radius of @p camera in respect to the @p offset.
diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp
index 3541b1a5bc1253c6b0f2b044d757341855a5e900..87d09aa9a6e3e7dc80d5de9a95f3e1e3b72e9205 100644
--- a/modules/camera/src/vkcv/camera/Camera.cpp
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -10,8 +10,6 @@ namespace vkcv::camera {
 			glm::vec3(0.0f, 0.0f, 0.0f),
 			glm::vec3(0.0f, 1.0f, 0.0f)
 		);
-  
-		setFront(glm::normalize(m_center - m_position));
     }
 
     Camera::~Camera() = default;
@@ -44,20 +42,20 @@ namespace vkcv::camera {
         0.0f, 0.0f, 0.0f, 1.0f
     );
 
-    glm::mat4 Camera::getProjection() const {
-        return y_correction * m_projection;
+    const glm::mat4& Camera::getProjection() const {
+        return m_projection;
     }
 
     void Camera::setProjection(const glm::mat4& projection) {
-        m_projection = glm::inverse(y_correction) * projection;
+        m_projection = y_correction * projection;
     }
 
     glm::mat4 Camera::getMVP() const {
-        return y_correction * m_projection * m_view;
+        return m_projection * m_view;
     }
 
     float Camera::getFov() const {
-    	const float tanHalfFovy = 1.0f / m_projection[1][1];
+    	const float tanHalfFovy = -1.0f / m_projection[1][1];
     	float halfFovy = std::atan(tanHalfFovy);
     	
     	if (halfFovy < 0) {
@@ -73,7 +71,7 @@ namespace vkcv::camera {
 
     float Camera::getRatio() const {
     	const float aspectProduct = 1.0f / m_projection[0][0];
-		const float tanHalfFovy = 1.0f / m_projection[1][1];
+		const float tanHalfFovy = -1.0f / m_projection[1][1];
 		
         return aspectProduct / tanHalfFovy;
     }
@@ -93,16 +91,11 @@ namespace vkcv::camera {
     }
 
     glm::vec3 Camera::getFront() const {
-        glm::vec3 direction;
-        direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
-        direction.y = std::sin(glm::radians(m_pitch));
-        direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
-        return glm::normalize(direction);
+        return glm::normalize(m_center - m_position);
     }
     
     void Camera::setFront(const glm::vec3 &front) {
-		m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z));
-		m_yaw = std::atan2(front.x, front.z);
+		setCenter(m_position + front);
     }
 
     const glm::vec3& Camera::getPosition() const {
@@ -128,21 +121,47 @@ namespace vkcv::camera {
 	void Camera::setUp(const glm::vec3 &up) {
 		lookAt(m_position, m_center, up);
 	}
-
-    float Camera::getPitch() const {
-        return m_pitch;
+	
+	void Camera::getAngles(float& pitch, float& yaw) {
+		const auto front = getFront();
+		
+		pitch = std::atan2(front[1], std::sqrt(
+				front[0] * front[0] + front[2] * front[2]
+		));
+		
+		yaw = std::atan2(front[0], front[2]);
+	}
+	
+	void Camera::setAngles(float pitch, float yaw) {
+		float cosPitch = std::cos(pitch);
+		
+		setFront(glm::vec3(
+				std::sin(yaw) * cosPitch,
+				std::sin(pitch),
+				std::cos(yaw) * cosPitch
+		));
+	}
+	
+	float Camera::getPitch() const {
+    	const auto front = getFront();
+    	
+        return glm::degrees(std::atan2(front[1], std::sqrt(
+        		front[0] * front[0] + front[2] * front[2]
+		)));
     }
 
     void Camera::setPitch(float pitch) {
-        m_pitch = pitch;
+		setAngles(glm::radians(pitch), glm::radians(getYaw()));
     }
 
     float Camera::getYaw() const {
-        return m_yaw;
+		const auto front = getFront();
+	
+		return glm::degrees(std::atan2(front[0], front[2]));
     }
 
     void Camera::setYaw(float yaw) {
-        m_yaw = yaw;
+		setAngles(glm::radians(getPitch()), glm::radians(yaw));
     }
 
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
index 5460858ab48d81252787b3c0141dd72982faca7d..28ef7c6943428078589047497fc2d3b44fde5fd7 100644
--- a/modules/camera/src/vkcv/camera/PilotCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -50,12 +50,11 @@ namespace vkcv::camera {
         }
 
         // handle yaw rotation
-        float yaw = camera.getYaw() + static_cast<float>(xOffset);
-        yaw += 360.0f * (yaw < -180.0f) - 360.0f * (yaw > 180.0f);
+        float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed;
         camera.setYaw(yaw);
 
         // handle pitch rotation
-        float pitch = camera.getPitch() - static_cast<float>(yOffset);
+        float pitch = camera.getPitch() - static_cast<float>(yOffset) * m_cameraSpeed;
         pitch = glm::clamp(pitch, -89.0f, 89.0f);
         camera.setPitch(pitch);
     }
diff --git a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
index cdd66cdb7fdd650d5112fe7bb4738f1fcded7783..b149a168f061125c08103ba63fcd7a97fa13ccc3 100644
--- a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -8,6 +8,8 @@ namespace vkcv::camera {
         m_radius = 3.0f;
         m_cameraSpeed = 2.5f;
         m_scrollSensitivity = 0.2f;
+        m_pitch = 0.0f;
+        m_yaw = 0.0f;
     }
 
     void TrackballCameraController::setRadius(const float radius) {
@@ -21,14 +23,10 @@ namespace vkcv::camera {
         }
 
         // handle yaw rotation
-        float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed;
-        yaw += 360.0f * (yaw < 0.0f) - 360.0f * (yaw > 360.0f);
-        camera.setYaw(yaw);
+        m_yaw = m_yaw + static_cast<float>(xOffset) * m_cameraSpeed;
 
         // handle pitch rotation
-        float pitch = camera.getPitch() + static_cast<float>(yOffset) * m_cameraSpeed;
-        pitch += 360.0f * (pitch < 0.0f) - 360.0f * (pitch > 360.0f);
-        camera.setPitch(pitch);
+        m_pitch = m_pitch + static_cast<float>(yOffset) * m_cameraSpeed;
     }
 
     void TrackballCameraController::updateRadius(double offset, Camera &camera) {
@@ -44,14 +42,11 @@ namespace vkcv::camera {
     }
 
     void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) {
-		float yaw = camera.getYaw();
-		float pitch = camera.getPitch();
-		
 		const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
 		const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
 	
-		const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis);
-		const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis);
+		const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(m_yaw), yAxis);
+		const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(m_pitch), xAxis);
 		const glm::vec3 translation = glm::vec3(
 				rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f)
 		);
diff --git a/modules/material/CMakeLists.txt b/modules/material/CMakeLists.txt
index d5b654cc6d00ce77d93b4666f48b7a5097e674b3..ed3804531d36f9850bbb5d334e4fed9b43d92434 100644
--- a/modules/material/CMakeLists.txt
+++ b/modules/material/CMakeLists.txt
@@ -12,8 +12,6 @@ set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include)
 set(vkcv_material_sources
 		${vkcv_material_include}/vkcv/material/Material.hpp
 		${vkcv_material_source}/vkcv/material/Material.cpp
-		${vkcv_material_include}/vkcv/material/PBRMaterial.hpp
-		${vkcv_material_source}/vkcv/material/PBRMaterial.cpp
 )
 
 # adding source files to the module
diff --git a/modules/material/include/vkcv/material/Material.hpp b/modules/material/include/vkcv/material/Material.hpp
index 00b492072fa4ef8b7b41f70202d515ee4ac828fa..9b54d99828eca3738fed9ff1c4078ca9f87eaefa 100644
--- a/modules/material/include/vkcv/material/Material.hpp
+++ b/modules/material/include/vkcv/material/Material.hpp
@@ -1,14 +1,70 @@
 #pragma once
+
+#include <vector>
+
+#include <vkcv/Core.hpp>
 #include <vkcv/Handles.hpp>
 
 namespace vkcv::material {
-
+	
+	enum class MaterialType {
+		PBR_MATERIAL = 1,
+		
+		UNKNOWN = 0
+	};
+	
 	class Material {
 	private:
+		struct Texture {
+			ImageHandle m_Image;
+			SamplerHandle m_Sampler;
+			std::vector<float> m_Factors;
+		};
+		
+		MaterialType m_Type;
+		DescriptorSetHandle m_DescriptorSet;
+		std::vector<Texture> m_Textures;
+		
 	public:
-		const DescriptorSetHandle m_DescriptorSetHandle;
-	protected:
-		Material(const DescriptorSetHandle& setHandle); 
+		Material();
+		~Material() = default;
+		
+		Material(const Material& other) = default;
+		Material(Material&& other) = default;
+		
+		Material& operator=(const Material& other) = default;
+		Material& operator=(Material&& other) = default;
+		
+		[[nodiscard]]
+		MaterialType getType() const;
+		
+		[[nodiscard]]
+		const DescriptorSetHandle& getDescriptorSet() const;
+		
+		explicit operator bool() const;
+		
+		bool operator!() const;
+		
+		static const std::vector<DescriptorBinding>& getDescriptorBindings(MaterialType type);
+		
+		static Material createPBR(Core &core,
+								  const ImageHandle &colorImg,
+								  const SamplerHandle &colorSmp,
+								  const ImageHandle &normalImg,
+								  const SamplerHandle &normalSmp,
+								  const ImageHandle &metRoughImg,
+								  const SamplerHandle &metRoughSmp,
+								  const ImageHandle &occlusionImg,
+								  const SamplerHandle &occlusionSmp,
+								  const ImageHandle &emissiveImg,
+								  const SamplerHandle &emissiveSmp,
+								  const float baseColorFactor [4],
+								  float metallicFactor,
+								  float roughnessFactor,
+								  float normalScale,
+								  float occlusionStrength,
+								  const float emissiveFactor [3]);
+	
 	};
 	
 }
diff --git a/modules/material/include/vkcv/material/PBRMaterial.hpp b/modules/material/include/vkcv/material/PBRMaterial.hpp
deleted file mode 100644
index 09a5214b0e748a09ef8caefe5bf2b1a69ecbd8e1..0000000000000000000000000000000000000000
--- a/modules/material/include/vkcv/material/PBRMaterial.hpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#pragma once
-
-#include <vector>
-
-#include <vkcv/DescriptorConfig.hpp>
-#include <vkcv/Core.hpp>
-
-
-#include "Material.hpp"
-
-namespace vkcv::material
-{
-    class PBRMaterial : Material
-    {
-    private:
-        struct vec3 {
-            float x, y, z;
-        };
-        struct vec4 {
-            float x, y, z, a;
-        };
-        PBRMaterial(const ImageHandle& colorImg,
-            const SamplerHandle& colorSmp,
-            const ImageHandle& normalImg,
-            const SamplerHandle& normalSmp,
-            const ImageHandle& metRoughImg,
-            const SamplerHandle& metRoughSmp,
-            const ImageHandle& occlusionImg,
-            const SamplerHandle& occlusionSmp,
-            const ImageHandle& emissiveImg,
-            const SamplerHandle& emissiveSmp,
-            const DescriptorSetHandle& setHandle,
-            vec4 baseColorFactor,
-            float metallicFactor,
-            float roughnessFactor,
-            float normalScale,
-            float occlusionStrength,
-            vec3 emissiveFactor) noexcept;
-
-
-    public:
-        PBRMaterial() = delete;
-        
-        const ImageHandle   m_ColorTexture;
-        const SamplerHandle m_ColorSampler;
-
-        const ImageHandle   m_NormalTexture;
-        const SamplerHandle m_NormalSampler;
-
-        const ImageHandle   m_MetRoughTexture;
-        const SamplerHandle m_MetRoughSampler;
-
-        const ImageHandle m_OcclusionTexture;
-        const SamplerHandle m_OcclusionSampler;
-
-        const ImageHandle m_EmissiveTexture;
-        const SamplerHandle m_EmissiveSampler;
-
-        //
-        const vec4 m_BaseColorFactor;
-		const float m_MetallicFactor;
-		const float m_RoughnessFactor;
-		const float m_NormalScale;
-		const float m_OcclusionStrength;
-		const vec3 m_EmissiveFactor;
-
-        /*
-        * Returns the material's necessary descriptor bindings which serves as its descriptor layout
-        * The binding is in the following order:
-        * 0 - diffuse texture
-        * 1 - diffuse sampler
-        * 2 - normal texture
-        * 3 - normal sampler
-        * 4 - metallic roughness texture
-        * 5 - metallic roughness sampler
-        * 6 - occlusion texture
-        * 7 - occlusion sampler
-        * 8 - emissive texture
-        * 9 - emissive sampler
-        */
-        static std::vector<DescriptorBinding> getDescriptorBindings() noexcept;
-
-        static PBRMaterial create(
-            vkcv::Core* core,
-            ImageHandle          &colorImg,
-            SamplerHandle        &colorSmp,
-            ImageHandle          &normalImg,
-            SamplerHandle        &normalSmp,
-            ImageHandle          &metRoughImg,
-            SamplerHandle        &metRoughSmp,
-			ImageHandle			&occlusionImg,
-			SamplerHandle		&occlusionSmp,
-			ImageHandle			&emissiveImg,
-			SamplerHandle		&emissiveSmp,
-            vec4 baseColorFactor,
-            float metallicFactor,
-            float roughnessFactor,
-            float normalScale,
-            float occlusionStrength,
-            vec3 emissiveFactor);
-
-    };
-}
\ No newline at end of file
diff --git a/modules/material/src/vkcv/material/Material.cpp b/modules/material/src/vkcv/material/Material.cpp
index 9168bcfbf924e9868ceaaff74aef5d3c6b99739c..409db0b9dd83f91b6a2afbb48d74933ab1a483fc 100644
--- a/modules/material/src/vkcv/material/Material.cpp
+++ b/modules/material/src/vkcv/material/Material.cpp
@@ -2,11 +2,185 @@
 #include "vkcv/material/Material.hpp"
 
 namespace vkcv::material {
+	
+	Material::Material() {
+		m_Type = MaterialType::UNKNOWN;
+	}
 
-	//TODO
-
-	Material::Material(const DescriptorSetHandle& setHandle) : m_DescriptorSetHandle(setHandle)
+	MaterialType Material::getType() const {
+		return m_Type;
+	}
+	
+	const DescriptorSetHandle & Material::getDescriptorSet() const {
+		return m_DescriptorSet;
+	}
+	
+	Material::operator bool() const {
+		return (m_Type != MaterialType::UNKNOWN);
+	}
+	
+	bool Material::operator!() const {
+		return (m_Type == MaterialType::UNKNOWN);
+	}
+	
+	const std::vector<DescriptorBinding>& Material::getDescriptorBindings(MaterialType type)
 	{
+		static std::vector<DescriptorBinding> pbr_bindings;
+		static std::vector<DescriptorBinding> default_bindings;
+		
+		switch (type) {
+			case MaterialType::PBR_MATERIAL:
+				if (pbr_bindings.empty()) {
+					pbr_bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
+					pbr_bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
+				}
+				
+				return pbr_bindings;
+			default:
+				return default_bindings;
+		}
+	}
+	
+	static void fillImage(Image& image, float data [4]) {
+		std::vector<float> vec (image.getWidth() * image.getHeight() * image.getDepth() * 4);
+		
+		for (size_t i = 0; i < vec.size(); i++) {
+			vec[i] = data[i % 4];
+		}
+		
+		image.fill(data);
+	}
+	
+	Material Material::createPBR(Core &core,
+								 const ImageHandle &colorImg, const SamplerHandle &colorSmp,
+								 const ImageHandle &normalImg, const SamplerHandle &normalSmp,
+								 const ImageHandle &metRoughImg, const SamplerHandle &metRoughSmp,
+								 const ImageHandle &occlusionImg, const SamplerHandle &occlusionSmp,
+								 const ImageHandle &emissiveImg, const SamplerHandle &emissiveSmp,
+								 const float baseColorFactor [4],
+								 float metallicFactor,
+								 float roughnessFactor,
+								 float normalScale,
+								 float occlusionStrength,
+								 const float emissiveFactor [3]) {
+		ImageHandle images [5] = {
+				colorImg, normalImg, metRoughImg, occlusionImg, emissiveImg
+		};
+		
+		SamplerHandle samplers [5] = {
+				colorSmp, normalSmp, metRoughSmp, occlusionSmp, emissiveSmp
+		};
+		
+		if (!colorImg) {
+			vkcv::Image defaultColor = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float colorData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultColor, colorData);
+			images[0] = defaultColor.getHandle();
+		}
+		
+		if (!normalImg) {
+			vkcv::Image defaultNormal = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float normalData [4] = { 0, 0, 1, 0 };
+			fillImage(defaultNormal, normalData);
+			images[1] = defaultNormal.getHandle();
+		}
+		
+		if (!metRoughImg) {
+			vkcv::Image defaultRough = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float roughData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultRough, roughData);
+			images[2] = defaultRough.getHandle();
+		}
+		
+		if (!occlusionImg) {
+			vkcv::Image defaultOcclusion = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float occlusionData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultOcclusion, occlusionData);
+			images[3] = defaultOcclusion.getHandle();
+		}
+		
+		if (!emissiveImg) {
+			vkcv::Image defaultEmissive = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float emissiveData [4] = { 0, 0, 0, 1 };
+			fillImage(defaultEmissive, emissiveData);
+			images[4] = defaultEmissive.getHandle();
+		}
+		
+		if (!colorSmp) {
+			samplers[0] = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+		
+		if (!normalSmp) {
+			samplers[1] = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+		
+		if (!metRoughSmp) {
+			samplers[2] = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+		
+		if (!occlusionSmp) {
+			samplers[3] = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+		
+		if (!emissiveSmp) {
+			samplers[4] = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+		
+		Material material;
+		material.m_Type = MaterialType::PBR_MATERIAL;
+		
+		const auto& bindings = getDescriptorBindings(material.m_Type);
+		material.m_DescriptorSet = core.createDescriptorSet(bindings);;
+		
+		material.m_Textures.reserve(bindings.size());
+		material.m_Textures.push_back({ images[0], samplers[0], std::vector<float>(baseColorFactor, baseColorFactor+4) });
+		material.m_Textures.push_back({ images[1], samplers[1], { normalScale } });
+		material.m_Textures.push_back({ images[2], samplers[2], { metallicFactor, roughnessFactor } });
+		material.m_Textures.push_back({ images[3], samplers[3], { occlusionStrength } });
+		material.m_Textures.push_back({ images[4], samplers[4], std::vector<float>(emissiveFactor, emissiveFactor+3) });
+		
+		vkcv::DescriptorWrites setWrites;
+		
+		for (size_t i = 0; i < material.m_Textures.size(); i++) {
+			setWrites.sampledImageWrites.emplace_back(i * 2, material.m_Textures[i].m_Image);
+			setWrites.samplerWrites.emplace_back(i * 2 + 1, material.m_Textures[i].m_Sampler);
+		}
+		
+		core.writeDescriptorSet(material.m_DescriptorSet, setWrites);
+		return material;
 	}
 
 }
diff --git a/modules/material/src/vkcv/material/PBRMaterial.cpp b/modules/material/src/vkcv/material/PBRMaterial.cpp
deleted file mode 100644
index d27e755c06a39e369d22efc997a0b411d067c132..0000000000000000000000000000000000000000
--- a/modules/material/src/vkcv/material/PBRMaterial.cpp
+++ /dev/null
@@ -1,194 +0,0 @@
-#include "vkcv/material/PBRMaterial.hpp"
-
-
-namespace vkcv::material
-{
-    PBRMaterial::PBRMaterial(
-        const ImageHandle& colorImg,
-        const SamplerHandle& colorSmp,
-        const ImageHandle& normalImg,
-        const SamplerHandle& normalSmp,
-        const ImageHandle& metRoughImg,
-        const SamplerHandle& metRoughSmp,
-        const ImageHandle& occlusionImg,
-        const SamplerHandle& occlusionSmp,
-        const ImageHandle& emissiveImg,
-        const SamplerHandle& emissiveSmp,
-        const DescriptorSetHandle& setHandle,
-        vec4 baseColorFactor,
-        float metallicFactor,
-        float roughnessFactor,
-        float normalScale,
-        float occlusionStrength,
-        vec3 emissiveFactor) noexcept :
-        m_ColorTexture(colorImg),
-        m_ColorSampler(colorSmp),
-        m_NormalTexture(normalImg),
-        m_NormalSampler(normalSmp),
-        m_MetRoughTexture(metRoughImg),
-        m_MetRoughSampler(metRoughSmp),
-        m_OcclusionTexture(occlusionImg),
-        m_OcclusionSampler(occlusionSmp),
-        m_EmissiveTexture(emissiveImg),
-        m_EmissiveSampler(emissiveSmp),
-        Material(setHandle),
-        m_BaseColorFactor(baseColorFactor),
-        m_MetallicFactor(metallicFactor),
-        m_RoughnessFactor(roughnessFactor),
-        m_NormalScale(normalScale),
-        m_OcclusionStrength(occlusionStrength),
-        m_EmissiveFactor(emissiveFactor)
-    {
-    }
-
-    std::vector<DescriptorBinding> PBRMaterial::getDescriptorBindings() noexcept
-    {
-		static std::vector<DescriptorBinding> bindings;
-		
-		if (bindings.empty()) {
-			bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT);
-			bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT);
-		}
-    	
-        return bindings;
-    }
-
-    PBRMaterial PBRMaterial::create(
-        vkcv::Core* core,
-        ImageHandle& colorImg,
-        SamplerHandle& colorSmp,
-        ImageHandle& normalImg,
-        SamplerHandle& normalSmp,
-        ImageHandle& metRoughImg,
-        SamplerHandle& metRoughSmp,
-        ImageHandle& occlusionImg,
-        SamplerHandle& occlusionSmp,
-        ImageHandle& emissiveImg,
-        SamplerHandle& emissiveSmp,
-        vec4 baseColorFactor,
-        float metallicFactor,
-        float roughnessFactor,
-        float normalScale,
-        float occlusionStrength,
-        vec3 emissiveFactor)
-    {
-        //Test if Images and samplers valid, if not use default
-         if (!colorImg) {
-            vkcv::Image defaultColor = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
-            vec4 colorData{ 228, 51, 255,1 };
-            defaultColor.fill(&colorData);
-            colorImg = defaultColor.getHandle();
-        }
-        if (!normalImg) {
-            vkcv::Image defaultNormal = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
-            vec4 normalData{ 0, 0, 1,0 };
-            defaultNormal.fill(&normalData);
-            normalImg = defaultNormal.getHandle();
-        }
-        if (!metRoughImg) {
-            vkcv::Image defaultRough = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
-            vec4 roughData{ 228, 51, 255,1 };
-            defaultRough.fill(&roughData);
-            metRoughImg = defaultRough.getHandle();
-        }
-        if (!occlusionImg) {
-            vkcv::Image defaultOcclusion = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
-            vec4 occlusionData{ 228, 51, 255,1 };
-            defaultOcclusion.fill(&occlusionData);
-            occlusionImg = defaultOcclusion.getHandle();
-        }
-        if (!emissiveImg) {
-            vkcv::Image defaultEmissive = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1);
-            vec4 emissiveData{ 0, 0, 0,1 };
-            defaultEmissive.fill(&emissiveData);
-            emissiveImg = defaultEmissive.getHandle();
-        }
-        if (!colorSmp) {            
-            colorSmp = core->createSampler(
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerMipmapMode::LINEAR,
-                vkcv::SamplerAddressMode::REPEAT
-            );            
-        }
-        if (!normalSmp) {            
-            normalSmp = core->createSampler(
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerMipmapMode::LINEAR,
-                vkcv::SamplerAddressMode::REPEAT
-            );            
-        }
-        if (!metRoughSmp) {
-            metRoughSmp = core->createSampler(
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerMipmapMode::LINEAR,
-                vkcv::SamplerAddressMode::REPEAT
-            );            
-        }
-        if (!occlusionSmp) {
-            occlusionSmp = core->createSampler(
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerMipmapMode::LINEAR,
-                vkcv::SamplerAddressMode::REPEAT
-            );
-        }
-        if (!emissiveSmp) {
-            emissiveSmp = core->createSampler(
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerFilterType::LINEAR,
-                vkcv::SamplerMipmapMode::LINEAR,
-                vkcv::SamplerAddressMode::REPEAT
-            );
-        }
-        
-
-
-        //create descriptorset
-        vkcv::DescriptorSetHandle descriptorSetHandle = core->createDescriptorSet(getDescriptorBindings());
-        //writes
-        vkcv::DescriptorWrites setWrites;
-        setWrites.sampledImageWrites = {
-            vkcv::SampledImageDescriptorWrite(0, colorImg),
-            vkcv::SampledImageDescriptorWrite(2, normalImg),
-            vkcv::SampledImageDescriptorWrite(4, metRoughImg),
-            vkcv::SampledImageDescriptorWrite(6, occlusionImg),
-            vkcv::SampledImageDescriptorWrite(8, emissiveImg) };
-        setWrites.samplerWrites = {
-            vkcv::SamplerDescriptorWrite(1, colorSmp),
-            vkcv::SamplerDescriptorWrite(3, normalSmp),
-            vkcv::SamplerDescriptorWrite(5, metRoughSmp),
-            vkcv::SamplerDescriptorWrite(7, occlusionSmp),
-            vkcv::SamplerDescriptorWrite(9, emissiveSmp) };
-        core->writeDescriptorSet(descriptorSetHandle, setWrites);
-
-        return PBRMaterial(
-            colorImg,
-            colorSmp,
-            normalImg,
-            normalSmp,
-            metRoughImg,
-            metRoughSmp,
-            occlusionImg,
-            occlusionSmp,
-            emissiveImg,
-            emissiveSmp,
-            descriptorSetHandle,
-            baseColorFactor,
-            metallicFactor,
-            roughnessFactor,
-            normalScale,
-            occlusionStrength,
-            emissiveFactor);
-    }
-}
\ No newline at end of file
diff --git a/modules/scene/CMakeLists.txt b/modules/scene/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9aa76883a260d26aa6f46d6dabdc8206e4dad387
--- /dev/null
+++ b/modules/scene/CMakeLists.txt
@@ -0,0 +1,45 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_scene)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_scene_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_scene_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_scene_sources
+		${vkcv_scene_include}/vkcv/scene/Bounds.hpp
+		${vkcv_scene_source}/vkcv/scene/Bounds.cpp
+		
+		${vkcv_scene_source}/vkcv/scene/Frustum.hpp
+		${vkcv_scene_source}/vkcv/scene/Frustum.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/MeshPart.hpp
+		${vkcv_scene_source}/vkcv/scene/MeshPart.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Mesh.hpp
+		${vkcv_scene_source}/vkcv/scene/Mesh.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Node.hpp
+		${vkcv_scene_source}/vkcv/scene/Node.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Scene.hpp
+		${vkcv_scene_source}/vkcv/scene/Scene.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_scene STATIC ${vkcv_scene_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_scene vkcv)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_material_include} ${vkcv_camera_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_scene BEFORE PUBLIC ${vkcv_scene_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(vkcv_scene vkcv vkcv_asset_loader vkcv_material vkcv_camera)
\ No newline at end of file
diff --git a/modules/scene/include/vkcv/scene/Bounds.hpp b/modules/scene/include/vkcv/scene/Bounds.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..07cdf88828d786982b0fe8e7919d543557794c42
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Bounds.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <array>
+#include <iostream>
+#include <glm/vec3.hpp>
+
+namespace vkcv::scene {
+	
+	class Bounds {
+	private:
+		glm::vec3 m_min;
+		glm::vec3 m_max;
+		
+	public:
+		Bounds();
+		Bounds(const glm::vec3& min, const glm::vec3& max);
+		~Bounds() = default;
+		
+		Bounds(const Bounds& other) = default;
+		Bounds(Bounds&& other) = default;
+		
+		Bounds& operator=(const Bounds& other) = default;
+		Bounds& operator=(Bounds&& other) = default;
+		
+		void setMin(const glm::vec3& min);
+		
+		[[nodiscard]]
+		const glm::vec3& getMin() const;
+		
+		void setMax(const glm::vec3& max);
+		
+		[[nodiscard]]
+		const glm::vec3& getMax() const;
+		
+		void setCenter(const glm::vec3& center);
+		
+		[[nodiscard]]
+		glm::vec3 getCenter() const;
+		
+		void setSize(const glm::vec3& size);
+		
+		[[nodiscard]]
+		glm::vec3 getSize() const;
+		
+		[[nodiscard]]
+		std::array<glm::vec3, 8> getCorners() const;
+		
+		void extend(const glm::vec3& point);
+		
+		[[nodiscard]]
+		bool contains(const glm::vec3& point) const;
+		
+		[[nodiscard]]
+		bool contains(const Bounds& other) const;
+		
+		[[nodiscard]]
+		bool intersects(const Bounds& other) const;
+		
+		[[nodiscard]]
+		explicit operator bool() const;
+		
+		[[nodiscard]]
+		bool operator!() const;
+	
+	};
+	
+	std::ostream& operator << (std::ostream& out, const Bounds& bounds);
+	
+}
diff --git a/modules/scene/include/vkcv/scene/Mesh.hpp b/modules/scene/include/vkcv/scene/Mesh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc82af4bfabed5e8bfc286bc53cd7b89791726fc
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Mesh.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <glm/mat4x4.hpp>
+
+#include <vkcv/camera/Camera.hpp>
+
+#include "MeshPart.hpp"
+
+namespace vkcv::scene {
+	
+	typedef typename event_function<const glm::mat4&, const glm::mat4&, PushConstants&, vkcv::DrawcallInfo&>::type RecordMeshDrawcallFunction;
+	
+	class Node;
+	
+	class Mesh {
+		friend class Node;
+		
+	private:
+		Scene& m_scene;
+		std::vector<MeshPart> m_parts;
+		std::vector<DrawcallInfo> m_drawcalls;
+		glm::mat4 m_transform;
+		Bounds m_bounds;
+		
+		explicit Mesh(Scene& scene);
+		
+		void load(const asset::Scene& scene,
+				  const asset::Mesh& mesh);
+		
+		void recordDrawcalls(const glm::mat4& viewProjection,
+							 PushConstants& pushConstants,
+							 std::vector<DrawcallInfo>& drawcalls,
+							 const RecordMeshDrawcallFunction& record);
+		
+		[[nodiscard]]
+		size_t getDrawcallCount() const;
+	
+	public:
+		~Mesh();
+		
+		Mesh(const Mesh& other) = default;
+		Mesh(Mesh&& other) = default;
+		
+		Mesh& operator=(const Mesh& other);
+		Mesh& operator=(Mesh&& other) noexcept;
+		
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+	
+	};
+	
+}
diff --git a/modules/scene/include/vkcv/scene/MeshPart.hpp b/modules/scene/include/vkcv/scene/MeshPart.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0d3467c6b57fcece69eb6f0c609c604fb99907d2
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/MeshPart.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/material/Material.hpp>
+
+#include "Bounds.hpp"
+
+namespace vkcv::scene {
+	
+	class Scene;
+	class Mesh;
+	
+	class MeshPart {
+		friend class Mesh;
+	
+	private:
+		Scene& m_scene;
+		BufferHandle m_vertices;
+		std::vector<VertexBufferBinding> m_vertexBindings;
+		BufferHandle m_indices;
+		size_t m_indexCount;
+		Bounds m_bounds;
+		size_t m_materialIndex;
+		
+		explicit MeshPart(Scene& scene);
+		
+		void load(const asset::Scene& scene,
+				  const asset::VertexGroup& vertexGroup,
+				  std::vector<DrawcallInfo>& drawcalls);
+	
+	public:
+		~MeshPart();
+		
+		MeshPart(const MeshPart& other);
+		MeshPart(MeshPart&& other);
+		
+		MeshPart& operator=(const MeshPart& other);
+		MeshPart& operator=(MeshPart&& other) noexcept;
+		
+		[[nodiscard]]
+		const material::Material& getMaterial() const;
+		
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+		
+		explicit operator bool() const;
+		bool operator!() const;
+		
+	};
+
+}
diff --git a/modules/scene/include/vkcv/scene/Node.hpp b/modules/scene/include/vkcv/scene/Node.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1fcca5b9cbecf1064070d7737d008d2b108371db
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Node.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/camera/Camera.hpp>
+
+#include "Bounds.hpp"
+#include "Mesh.hpp"
+
+namespace vkcv::scene {
+	
+	class Scene;
+	
+	class Node {
+		friend class Scene;
+		
+	private:
+		Scene& m_scene;
+		
+		std::vector<Mesh> m_meshes;
+		std::vector<Node> m_nodes;
+		Bounds m_bounds;
+		
+		explicit Node(Scene& scene);
+		
+		void addMesh(const Mesh& mesh);
+		
+		void loadMesh(const asset::Scene& asset_scene, const asset::Mesh& asset_mesh);
+		
+		void recordDrawcalls(const glm::mat4& viewProjection,
+							 PushConstants& pushConstants,
+							 std::vector<DrawcallInfo>& drawcalls,
+							 const RecordMeshDrawcallFunction& record);
+		
+		void splitMeshesToSubNodes(size_t maxMeshesPerNode);
+		
+		[[nodiscard]]
+		size_t getDrawcallCount() const;
+		
+		size_t addNode();
+		
+		Node& getNode(size_t index);
+		
+		[[nodiscard]]
+		const Node& getNode(size_t index) const;
+	
+	public:
+		~Node();
+		
+		Node(const Node& other) = default;
+		Node(Node&& other) = default;
+		
+		Node& operator=(const Node& other);
+		Node& operator=(Node&& other) noexcept;
+		
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+		
+	};
+	
+}
diff --git a/modules/scene/include/vkcv/scene/Scene.hpp b/modules/scene/include/vkcv/scene/Scene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..429c0bcf729f9afb7dd76cdd58c54931862e1a4a
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Scene.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <filesystem>
+#include <mutex>
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Event.hpp>
+#include <vkcv/camera/Camera.hpp>
+#include <vkcv/material/Material.hpp>
+
+#include "Node.hpp"
+
+namespace vkcv::scene {
+	
+	class Scene {
+		friend class MeshPart;
+		
+	private:
+		struct Material {
+			size_t m_usages;
+			material::Material m_data;
+		};
+		
+		Core* m_core;
+		
+		std::vector<Material> m_materials;
+		std::vector<Node> m_nodes;
+		
+		explicit Scene(Core* core);
+		
+		size_t addNode();
+		
+		Node& getNode(size_t index);
+		
+		const Node& getNode(size_t index) const;
+		
+		void increaseMaterialUsage(size_t index);
+		
+		void decreaseMaterialUsage(size_t index);
+		
+		void loadMaterial(size_t index, const asset::Scene& scene,
+						  const asset::Material& material);
+		
+	public:
+		~Scene();
+		
+		Scene(const Scene& other);
+		Scene(Scene&& other) noexcept;
+		
+		Scene& operator=(const Scene& other);
+		Scene& operator=(Scene&& other) noexcept;
+		
+		size_t getMaterialCount() const;
+		
+		[[nodiscard]]
+		const material::Material& getMaterial(size_t index) const;
+		
+		void recordDrawcalls(CommandStreamHandle       		  &cmdStream,
+							 const camera::Camera			  &camera,
+							 const PassHandle                 &pass,
+							 const PipelineHandle             &pipeline,
+							 size_t							  pushConstantsSizePerDrawcall,
+							 const RecordMeshDrawcallFunction &record,
+							 const std::vector<ImageHandle>   &renderTargets);
+		
+		static Scene create(Core& core);
+		
+		static Scene load(Core& core, const std::filesystem::path &path);
+		
+	};
+	
+}
\ No newline at end of file
diff --git a/modules/scene/src/vkcv/scene/Bounds.cpp b/modules/scene/src/vkcv/scene/Bounds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..731d81e928deae4c27f5c857de5b94dc3180888b
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Bounds.cpp
@@ -0,0 +1,126 @@
+
+#include "vkcv/scene/Bounds.hpp"
+
+namespace vkcv::scene {
+	
+	Bounds::Bounds() :
+	m_min(glm::vec3(0)),
+	m_max(glm::vec3(0)) {}
+	
+	Bounds::Bounds(const glm::vec3 &min, const glm::vec3 &max) :
+	m_min(min),
+	m_max(max)
+	{}
+
+	void Bounds::setMin(const glm::vec3 &min) {
+		m_min = min;
+	}
+	
+	const glm::vec3 & Bounds::getMin() const {
+		return m_min;
+	}
+	
+	void Bounds::setMax(const glm::vec3 &max) {
+		m_max = max;
+	}
+	
+	const glm::vec3 & Bounds::getMax() const {
+		return m_max;
+	}
+	
+	void Bounds::setCenter(const glm::vec3 &center) {
+		const glm::vec3 size = getSize();
+		m_min = center - size / 2.0f;
+		m_max = center + size / 2.0f;
+	}
+	
+	glm::vec3 Bounds::getCenter() const {
+		return (m_min + m_max) / 2.0f;
+	}
+	
+	void Bounds::setSize(const glm::vec3 &size) {
+		const glm::vec3 center = getCenter();
+		m_min = center - size / 2.0f;
+		m_max = center + size / 2.0f;
+	}
+	
+	glm::vec3 Bounds::getSize() const {
+		return (m_max - m_min);
+	}
+	
+	std::array<glm::vec3, 8> Bounds::getCorners() const {
+		return {
+			m_min,
+			glm::vec3(m_min[0], m_min[1], m_max[2]),
+			glm::vec3(m_min[0], m_max[1], m_min[2]),
+			glm::vec3(m_min[0], m_max[1], m_max[2]),
+			glm::vec3(m_max[0], m_min[1], m_min[2]),
+			glm::vec3(m_max[0], m_min[1], m_max[2]),
+			glm::vec3(m_max[0], m_max[1], m_min[2]),
+			m_max
+		};
+	}
+	
+	void Bounds::extend(const glm::vec3 &point) {
+		m_min = glm::vec3(
+				std::min(m_min[0], point[0]),
+				std::min(m_min[1], point[1]),
+				std::min(m_min[2], point[2])
+		);
+		
+		m_max = glm::vec3(
+				std::max(m_max[0], point[0]),
+				std::max(m_max[1], point[1]),
+				std::max(m_max[2], point[2])
+		);
+	}
+	
+	bool Bounds::contains(const glm::vec3 &point) const {
+		return (
+				(point[0] >= m_min[0]) && (point[0] <= m_max[0]) &&
+				(point[1] >= m_min[1]) && (point[1] <= m_max[1]) &&
+				(point[2] >= m_min[2]) && (point[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::contains(const Bounds &other) const {
+		return (
+				(other.m_min[0] >= m_min[0]) && (other.m_max[0] <= m_max[0]) &&
+				(other.m_min[1] >= m_min[1]) && (other.m_max[1] <= m_max[1]) &&
+				(other.m_min[2] >= m_min[2]) && (other.m_max[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::intersects(const Bounds &other) const {
+		return (
+				(other.m_max[0] >= m_min[0]) && (other.m_min[0] <= m_max[0]) &&
+				(other.m_max[1] >= m_min[1]) && (other.m_min[1] <= m_max[1]) &&
+				(other.m_max[2] >= m_min[2]) && (other.m_min[2] <= m_max[2])
+		);
+	}
+	
+	Bounds::operator bool() const {
+		return (
+				(m_min[0] <= m_max[0]) &&
+				(m_min[1] <= m_max[1]) &&
+				(m_min[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::operator!() const {
+		return (
+				(m_min[0] > m_max[0]) ||
+				(m_min[1] > m_max[1]) ||
+				(m_min[2] > m_max[2])
+		);
+	}
+	
+	std::ostream& operator << (std::ostream& out, const Bounds& bounds) {
+		const auto& min = bounds.getMin();
+		const auto& max = bounds.getMax();
+		
+		return out << "[Bounds: (" << min[0] << ", " << min[1] << ", " << min[2] << ") ("
+								   << max[0] << ", " << max[1] << ", " << max[2] << ") ]";
+	}
+	
+}
diff --git a/modules/scene/src/vkcv/scene/Frustum.cpp b/modules/scene/src/vkcv/scene/Frustum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c800bb1e4baf4d0feef33c073740fb211da7bf63
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Frustum.cpp
@@ -0,0 +1,73 @@
+
+#include "Frustum.hpp"
+
+namespace vkcv::scene {
+	
+	static glm::vec3 transformPoint(const glm::mat4& transform, const glm::vec3& point, bool* negative_w) {
+		const glm::vec4 position = transform * glm::vec4(point, 1.0f);
+		
+		
+		/*
+		 * We divide by the absolute of the 4th coorditnate because
+		 * clipping is weird and points have to move to the other
+		 * side of the camera.
+		 *
+		 * We also need to collect if the 4th coordinate was negative
+		 * to know if all corners are behind the camera. So these can
+		 * be culled as well
+		 */
+		if (negative_w) {
+			const float perspective = std::abs(position[3]);
+			
+			*negative_w &= (position[3] < 0.0f);
+			
+			return glm::vec3(
+					position[0] / perspective,
+					position[1] / perspective,
+					position[2] / perspective
+			);
+		} else {
+			return glm::vec3(
+					position[0],
+					position[1],
+					position[2]
+			);
+		}
+	}
+	
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool* negative_w) {
+		const auto corners = bounds.getCorners();
+		
+		if (negative_w) {
+			*negative_w = true;
+		}
+		
+		auto projected = transformPoint(transform, corners[0], negative_w);
+		
+		Bounds result (projected, projected);
+		
+		for (size_t j = 1; j < corners.size(); j++) {
+			projected = transformPoint(transform, corners[j], negative_w);
+			result.extend(projected);
+		}
+		
+		return result;
+	}
+	
+	bool checkFrustum(const glm::mat4& transform, const Bounds& bounds) {
+		static Bounds frustum (
+				glm::vec3(-1.0f, -1.0f, -0.0f),
+				glm::vec3(+1.0f, +1.0f, +1.0f)
+		);
+		
+		bool negative_w;
+		auto box = transformBounds(transform, bounds, &negative_w);
+		
+		if (negative_w) {
+			return false;
+		} else {
+			return box.intersects(frustum);
+		}
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/Frustum.hpp b/modules/scene/src/vkcv/scene/Frustum.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..de3917575a9aed32459e6403fab1d6d8fe131b0a
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Frustum.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <glm/mat4x4.hpp>
+#include "vkcv/scene/Bounds.hpp"
+
+namespace vkcv::scene {
+	
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool* negative_w = nullptr);
+	
+	bool checkFrustum(const glm::mat4& transform, const Bounds& bounds);
+	
+}
diff --git a/modules/scene/src/vkcv/scene/Mesh.cpp b/modules/scene/src/vkcv/scene/Mesh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..53fb81713ed7e14049a21cb91c771d67f2f7086c
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Mesh.cpp
@@ -0,0 +1,132 @@
+
+#include "vkcv/scene/Mesh.hpp"
+#include "vkcv/scene/Scene.hpp"
+#include "Frustum.hpp"
+
+namespace vkcv::scene {
+	
+	Mesh::Mesh(Scene& scene) :
+	m_scene(scene) {}
+	
+	static glm::mat4 arrayTo4x4Matrix(const std::array<float,16>& array){
+		glm::mat4 matrix;
+		
+		for (int i = 0; i < 4; i++){
+			for (int j = 0; j < 4; j++){
+				matrix[i][j] = array[j * 4 + i];
+			}
+		}
+		
+		return matrix;
+	}
+	
+	void Mesh::load(const asset::Scene &scene, const asset::Mesh &mesh) {
+		m_parts.clear();
+		m_drawcalls.clear();
+		
+		m_transform = arrayTo4x4Matrix(mesh.modelMatrix);
+		
+		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
+			if ((vertexGroupIndex < 0) || (vertexGroupIndex >= scene.vertexGroups.size())) {
+				continue;
+			}
+			
+			MeshPart part (m_scene);
+			part.load(scene, scene.vertexGroups[vertexGroupIndex], m_drawcalls);
+			
+			if (!part) {
+				continue;
+			}
+			
+			auto bounds = transformBounds(m_transform, part.getBounds());
+			
+			if (m_parts.empty()) {
+				m_bounds = bounds;
+			} else {
+				m_bounds.extend(bounds.getMin());
+				m_bounds.extend(bounds.getMax());
+			}
+			
+			m_parts.push_back(part);
+		}
+	}
+	
+	Mesh::~Mesh() {
+		m_drawcalls.clear();
+		m_parts.clear();
+	}
+	
+	Mesh &Mesh::operator=(const Mesh &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_parts.resize(other.m_parts.size(), MeshPart(m_scene));
+		
+		for (size_t i = 0; i < m_parts.size(); i++) {
+			m_parts[i] = other.m_parts[i];
+		}
+		
+		m_drawcalls = std::vector<DrawcallInfo>(other.m_drawcalls);
+		m_transform = other.m_transform;
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	Mesh &Mesh::operator=(Mesh &&other) noexcept {
+		m_parts.resize(other.m_parts.size(), MeshPart(m_scene));
+		
+		for (size_t i = 0; i < m_parts.size(); i++) {
+			m_parts[i] = std::move(other.m_parts[i]);
+		}
+		
+		m_drawcalls = std::move(other.m_drawcalls);
+		m_transform = other.m_transform;
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	void Mesh::recordDrawcalls(const glm::mat4& viewProjection,
+							   PushConstants& pushConstants,
+							   std::vector<DrawcallInfo>& drawcalls,
+							   const RecordMeshDrawcallFunction& record) {
+		const glm::mat4 transform = viewProjection * m_transform;
+		
+		if (!checkFrustum(viewProjection, m_bounds)) {
+			return;
+		}
+		
+		if (m_drawcalls.size() == 1) {
+			drawcalls.push_back(m_drawcalls[0]);
+			
+			if (record) {
+				record(transform, m_transform, pushConstants, drawcalls.back());
+			}
+		} else {
+			for (size_t i = 0; i < m_parts.size(); i++) {
+				const MeshPart& part = m_parts[i];
+				
+				if (!checkFrustum(transform, part.getBounds())) {
+					continue;
+				}
+				
+				drawcalls.push_back(m_drawcalls[i]);
+				
+				if (record) {
+					record(transform, m_transform, pushConstants, drawcalls.back());
+				}
+			}
+		}
+	}
+	
+	size_t Mesh::getDrawcallCount() const {
+		return m_drawcalls.size();
+	}
+	
+	const Bounds& Mesh::getBounds() const {
+		return m_bounds;
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/MeshPart.cpp b/modules/scene/src/vkcv/scene/MeshPart.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..46e79897719d5422151ec31837a41f7e58324a71
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/MeshPart.cpp
@@ -0,0 +1,158 @@
+
+#include "vkcv/scene/MeshPart.hpp"
+#include "vkcv/scene/Scene.hpp"
+
+namespace vkcv::scene {
+	
+	MeshPart::MeshPart(Scene& scene) :
+	m_scene(scene),
+	m_vertices(),
+	m_vertexBindings(),
+	m_indices(),
+	m_indexCount(0),
+	m_bounds(),
+	m_materialIndex(std::numeric_limits<size_t>::max()) {}
+	
+	void MeshPart::load(const asset::Scene& scene,
+						const asset::VertexGroup &vertexGroup,
+						std::vector<DrawcallInfo>& drawcalls) {
+		Core& core = *(m_scene.m_core);
+		
+		auto vertexBuffer = core.createBuffer<uint8_t>(
+				BufferType::VERTEX, vertexGroup.vertexBuffer.data.size()
+		);
+		
+		vertexBuffer.fill(vertexGroup.vertexBuffer.data);
+		m_vertices = vertexBuffer.getHandle();
+		
+		auto attributes = vertexGroup.vertexBuffer.attributes;
+		
+		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+		});
+		
+		for (const auto& attribute : attributes) {
+			m_vertexBindings.emplace_back(attribute.offset, vertexBuffer.getVulkanHandle());
+		}
+		
+		auto indexBuffer = core.createBuffer<uint8_t>(
+				BufferType::INDEX, vertexGroup.indexBuffer.data.size()
+		);
+		
+		indexBuffer.fill(vertexGroup.indexBuffer.data);
+		m_indices = indexBuffer.getHandle();
+		m_indexCount = vertexGroup.numIndices;
+		
+		m_bounds.setMin(glm::vec3(
+				vertexGroup.min.x,
+				vertexGroup.min.y,
+				vertexGroup.min.z
+		));
+		
+		m_bounds.setMax(glm::vec3(
+				vertexGroup.max.x,
+				vertexGroup.max.y,
+				vertexGroup.max.z
+		));
+		
+		if ((vertexGroup.materialIndex >= 0) &&
+			(vertexGroup.materialIndex < scene.materials.size())) {
+			m_materialIndex = vertexGroup.materialIndex;
+			
+			if (!getMaterial()) {
+				m_scene.loadMaterial(m_materialIndex, scene, scene.materials[vertexGroup.materialIndex]);
+			}
+			
+			m_scene.increaseMaterialUsage(m_materialIndex);
+		} else {
+			m_materialIndex = std::numeric_limits<size_t>::max();
+		}
+		
+		if (*this) {
+			const auto& material = getMaterial();
+			const auto& descriptorSet = core.getDescriptorSet(material.getDescriptorSet());
+			
+			drawcalls.push_back(DrawcallInfo(
+					vkcv::Mesh(m_vertexBindings, indexBuffer.getVulkanHandle(), m_indexCount),
+					{ DescriptorSetUsage(0, descriptorSet.vulkanHandle) }
+			));
+		}
+	}
+	
+	MeshPart::~MeshPart() {
+		m_scene.decreaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart::MeshPart(const MeshPart &other) :
+			m_scene(other.m_scene),
+			m_vertices(other.m_vertices),
+			m_vertexBindings(other.m_vertexBindings),
+			m_indices(other.m_indices),
+			m_indexCount(other.m_indexCount),
+			m_bounds(other.m_bounds),
+			m_materialIndex(other.m_materialIndex) {
+		m_scene.increaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart::MeshPart(MeshPart &&other) :
+			m_scene(other.m_scene),
+			m_vertices(other.m_vertices),
+			m_vertexBindings(other.m_vertexBindings),
+			m_indices(other.m_indices),
+			m_indexCount(other.m_indexCount),
+			m_bounds(other.m_bounds),
+			m_materialIndex(other.m_materialIndex) {
+		m_scene.increaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart &MeshPart::operator=(const MeshPart &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_vertices = other.m_vertices;
+		m_vertexBindings = other.m_vertexBindings;
+		m_indices = other.m_indices;
+		m_indexCount = other.m_indexCount;
+		m_bounds = other.m_bounds;
+		m_materialIndex = other.m_materialIndex;
+		
+		return *this;
+	}
+	
+	MeshPart &MeshPart::operator=(MeshPart &&other) noexcept {
+		m_vertices = other.m_vertices;
+		m_vertexBindings = other.m_vertexBindings;
+		m_indices = other.m_indices;
+		m_indexCount = other.m_indexCount;
+		m_bounds = other.m_bounds;
+		m_materialIndex = other.m_materialIndex;
+		
+		return *this;
+	}
+	
+	const material::Material & MeshPart::getMaterial() const {
+		return m_scene.getMaterial(m_materialIndex);
+	}
+	
+	MeshPart::operator bool() const {
+		return (
+				(getMaterial()) &&
+				(m_vertices) &&
+				(m_indices)
+		);
+	}
+	
+	bool MeshPart::operator!() const {
+		return (
+				(!getMaterial()) ||
+				(!m_vertices) ||
+				(!m_indices)
+		);
+	}
+	
+	const Bounds &MeshPart::getBounds() const {
+		return m_bounds;
+	}
+	
+}
diff --git a/modules/scene/src/vkcv/scene/Node.cpp b/modules/scene/src/vkcv/scene/Node.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..32230099b2f693362bab69d8172a4dee56c4e304
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Node.cpp
@@ -0,0 +1,189 @@
+
+#include "vkcv/scene/Node.hpp"
+#include "vkcv/scene/Scene.hpp"
+#include "Frustum.hpp"
+
+#include <algorithm>
+
+namespace vkcv::scene {
+	
+	Node::Node(Scene& scene) :
+	m_scene(scene),
+	m_meshes(),
+	m_nodes(),
+	m_bounds() {}
+	
+	Node::~Node() {
+		m_nodes.clear();
+		m_meshes.clear();
+	}
+	
+	Node &Node::operator=(const Node &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_meshes.resize(other.m_meshes.size(), Mesh(m_scene));
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			m_meshes[i] = other.m_meshes[i];
+		}
+		
+		m_nodes.resize(other.m_nodes.size(), Node(m_scene));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+		
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	Node &Node::operator=(Node &&other) noexcept {
+		m_meshes.resize(other.m_meshes.size(), Mesh(m_scene));
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			m_meshes[i] = std::move(other.m_meshes[i]);
+		}
+		
+		m_nodes.resize(other.m_nodes.size(), Node(m_scene));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+		
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	void Node::addMesh(const Mesh& mesh) {
+		if (m_meshes.empty()) {
+			m_bounds = mesh.getBounds();
+		} else {
+			m_bounds.extend(mesh.getBounds().getMin());
+			m_bounds.extend(mesh.getBounds().getMax());
+		}
+		
+		m_meshes.push_back(mesh);
+	}
+	
+	void Node::loadMesh(const asset::Scene &asset_scene, const asset::Mesh &asset_mesh) {
+		Mesh mesh (m_scene);
+		mesh.load(asset_scene, asset_mesh);
+		addMesh(mesh);
+	}
+	
+	size_t Node::addNode() {
+		const Node node (m_scene);
+		const size_t index = m_nodes.size();
+		m_nodes.push_back(node);
+		return index;
+	}
+	
+	Node& Node::getNode(size_t index) {
+		return m_nodes[index];
+	}
+	
+	const Node& Node::getNode(size_t index) const {
+		return m_nodes[index];
+	}
+	
+	void Node::recordDrawcalls(const glm::mat4& viewProjection,
+							   PushConstants& pushConstants,
+							   std::vector<DrawcallInfo>& drawcalls,
+							   const RecordMeshDrawcallFunction& record) {
+		if (!checkFrustum(viewProjection, m_bounds)) {
+			return;
+		}
+		
+		for (auto& mesh : m_meshes) {
+			mesh.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+		
+		for (auto& node : m_nodes) {
+			node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+	}
+	
+	void Node::splitMeshesToSubNodes(size_t maxMeshesPerNode) {
+		if (m_meshes.size() <= maxMeshesPerNode) {
+			return;
+		}
+		
+		const auto split = m_bounds.getCenter();
+		int axis = 0;
+		
+		const auto size = m_bounds.getSize();
+		
+		if (size[1] > size[0]) {
+			if (size[2] > size[1]) {
+				axis = 2;
+			} else {
+				axis = 1;
+			}
+		} else
+		if (size[2] > size[0]) {
+			axis = 2;
+		}
+		
+		std::vector<size_t> left_meshes;
+		std::vector<size_t> right_meshes;
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			const auto& bounds = m_meshes[i].getBounds();
+			
+			if (bounds.getMax()[axis] <= split[axis]) {
+				left_meshes.push_back(i);
+			} else
+			if (bounds.getMin()[axis] >= split[axis]) {
+				right_meshes.push_back(i);
+			}
+		}
+		
+		if ((left_meshes.empty()) || (right_meshes.empty())) {
+			return;
+		}
+		
+		const size_t left = addNode();
+		const size_t right = addNode();
+		
+		for (size_t i : left_meshes) {
+			getNode(left).addMesh(m_meshes[i]);
+		}
+		
+		for (size_t i : right_meshes) {
+			getNode(right).addMesh(m_meshes[i]);
+			left_meshes.push_back(i);
+		}
+		
+		std::sort(left_meshes.begin(), left_meshes.end(), std::greater());
+		
+		for (size_t i : left_meshes) {
+			m_meshes.erase(m_meshes.begin() + static_cast<long>(i));
+		}
+		
+		getNode(left).splitMeshesToSubNodes(maxMeshesPerNode);
+		getNode(right).splitMeshesToSubNodes(maxMeshesPerNode);
+	}
+	
+	size_t Node::getDrawcallCount() const {
+		size_t count = 0;
+		
+		for (auto& mesh : m_meshes) {
+			count += mesh.getDrawcallCount();
+		}
+		
+		for (auto& node : m_nodes) {
+			count += node.getDrawcallCount();
+		}
+		
+		return count;
+	}
+	
+	const Bounds& Node::getBounds() const {
+		return m_bounds;
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/Scene.cpp b/modules/scene/src/vkcv/scene/Scene.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b050f67ff352296db6155247beebbb5682e542c2
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Scene.cpp
@@ -0,0 +1,273 @@
+
+#include "vkcv/scene/Scene.hpp"
+
+#include <vkcv/Logger.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+
+namespace vkcv::scene {
+	
+	Scene::Scene(Core* core) :
+	m_core(core),
+	m_materials(),
+	m_nodes() {}
+	
+	Scene::~Scene() {
+		m_nodes.clear();
+		m_materials.clear();
+	}
+	
+	Scene::Scene(const Scene &other) :
+	m_core(other.m_core),
+	m_materials(other.m_materials),
+	m_nodes() {
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+	}
+	
+	Scene::Scene(Scene &&other) noexcept :
+	m_core(other.m_core),
+	m_materials(other.m_materials),
+	m_nodes() {
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+	}
+	
+	Scene &Scene::operator=(const Scene &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_core = other.m_core;
+		m_materials = std::vector<Material>(other.m_materials);
+		
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+		
+		return *this;
+	}
+	
+	Scene &Scene::operator=(Scene &&other) noexcept {
+		m_core = other.m_core;
+		m_materials = std::move(other.m_materials);
+		
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+		
+		return *this;
+	}
+	
+	size_t Scene::addNode() {
+		const Node node (*this);
+		const size_t index = m_nodes.size();
+		m_nodes.push_back(node);
+		return index;
+	}
+	
+	Node& Scene::getNode(size_t index) {
+		return m_nodes[index];
+	}
+	
+	const Node& Scene::getNode(size_t index) const {
+		return m_nodes[index];
+	}
+	
+	void Scene::increaseMaterialUsage(size_t index) {
+		if (index < m_materials.size()) {
+			m_materials[index].m_usages++;
+		}
+	}
+	
+	void Scene::decreaseMaterialUsage(size_t index) {
+		if ((index < m_materials.size()) && (m_materials[index].m_usages > 0)) {
+			m_materials[index].m_usages--;
+		}
+	}
+	
+	size_t Scene::getMaterialCount() const {
+		return m_materials.size();
+	}
+	
+	const material::Material & Scene::getMaterial(size_t index) const {
+		static material::Material noMaterial;
+		
+		if (index >= m_materials.size()) {
+			return noMaterial;
+		}
+		
+		return m_materials[index].m_data;
+	}
+	
+	void Scene::recordDrawcalls(CommandStreamHandle       		 &cmdStream,
+								const camera::Camera			 &camera,
+								const PassHandle                 &pass,
+								const PipelineHandle             &pipeline,
+								size_t							 pushConstantsSizePerDrawcall,
+								const RecordMeshDrawcallFunction &record,
+								const std::vector<ImageHandle>   &renderTargets) {
+		PushConstants pushConstants (pushConstantsSizePerDrawcall);
+		std::vector<DrawcallInfo> drawcalls;
+		size_t count = 0;
+		
+		const glm::mat4 viewProjection = camera.getMVP();
+		
+		for (auto& node : m_nodes) {
+			count += node.getDrawcallCount();
+			node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+		
+		vkcv_log(LogLevel::RAW_INFO, "Frustum culling: %lu / %lu", drawcalls.size(), count);
+		
+		m_core->recordDrawcallsToCmdStream(
+				cmdStream,
+				pass,
+				pipeline,
+				pushConstants,
+				drawcalls,
+				renderTargets
+		);
+	}
+	
+	Scene Scene::create(Core& core) {
+		return Scene(&core);
+	}
+	
+	static void loadImage(Core& core, const asset::Scene& asset_scene,
+						  const asset::Texture& asset_texture,
+						  const vk::Format& format,
+						  ImageHandle& image, SamplerHandle& sampler) {
+		asset::Sampler* asset_sampler = nullptr;
+		
+		if ((asset_texture.sampler >= 0) && (asset_texture.sampler < asset_scene.samplers.size())) {
+			//asset_sampler = &(asset_scene.samplers[asset_texture.sampler]); // TODO
+		}
+		
+		Image img = core.createImage(format, asset_texture.w, asset_texture.h);
+		img.fill(asset_texture.data.data());
+		image = img.getHandle();
+		
+		if (asset_sampler) {
+			//sampler = core.createSampler(asset_sampler) // TODO
+		} else {
+			sampler = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT
+			);
+		}
+	}
+	
+	void Scene::loadMaterial(size_t index, const asset::Scene& scene,
+							 const asset::Material& material) {
+		if (index >= m_materials.size()) {
+			return;
+		}
+		
+		ImageHandle diffuseImg;
+		SamplerHandle diffuseSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg,diffuseSmp);
+		}
+		
+		ImageHandle normalImg;
+		SamplerHandle normalSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg,diffuseSmp);
+		}
+		
+		ImageHandle metalRoughImg;
+		SamplerHandle metalRoughSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg,diffuseSmp);
+		}
+		
+		ImageHandle occlusionImg;
+		SamplerHandle occlusionSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg,diffuseSmp);
+		}
+		
+		ImageHandle emissionImg;
+		SamplerHandle emissionSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg,diffuseSmp);
+		}
+		
+		const float colorFactors [4] = {
+				material.baseColorFactor.r,
+				material.baseColorFactor.g,
+				material.baseColorFactor.b,
+				material.baseColorFactor.a
+		};
+		
+		const float emissionFactors[4] = {
+				material.emissiveFactor.r,
+				material.emissiveFactor.g,
+				material.emissiveFactor.b
+		};
+		
+		m_materials[index].m_data = material::Material::createPBR(
+				*m_core,
+				diffuseImg, diffuseSmp,
+				normalImg, normalSmp,
+				metalRoughImg, metalRoughSmp,
+				occlusionImg, occlusionSmp,
+				emissionImg, emissionSmp,
+				colorFactors,
+				material.normalScale,
+				material.metallicFactor,
+				material.roughnessFactor,
+				material.occlusionStrength,
+				emissionFactors
+		);
+	}
+	
+	Scene Scene::load(Core& core, const std::filesystem::path &path) {
+		asset::Scene asset_scene;
+		
+		if (!asset::loadScene(path.string(), asset_scene)) {
+			vkcv_log(LogLevel::WARNING, "Scene could not be loaded")
+			return create(core);
+		}
+		
+		Scene scene = create(core);
+		
+		for (const auto& material : asset_scene.materials) {
+			scene.m_materials.push_back({
+				0, material::Material()
+			});
+		}
+		
+		const size_t root = scene.addNode();
+		
+		for (const auto& mesh : asset_scene.meshes) {
+			scene.getNode(root).loadMesh(asset_scene, mesh);
+		}
+		
+		scene.getNode(root).splitMeshesToSubNodes(128);
+		return scene;
+	}
+	
+}
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 1c6e3afe2347f6ef8ea8a62be7acbe0ea750497d..4196d55d99db115641f0a23cf5d7445bc70e52fe 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -1,8 +1,7 @@
 
 # Add new projects/examples here:
-add_subdirectory(bloom)
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
-add_subdirectory(particle_simulation)
 add_subdirectory(first_scene)
+add_subdirectory(particle_simulation)
 add_subdirectory(voxelization)
diff --git a/projects/bloom/.gitignore b/projects/bloom/.gitignore
deleted file mode 100644
index 3643183e0628e666abab193e1dd1d92c1774ac61..0000000000000000000000000000000000000000
--- a/projects/bloom/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-bloom
\ No newline at end of file
diff --git a/projects/bloom/CMakeLists.txt b/projects/bloom/CMakeLists.txt
deleted file mode 100644
index 8171938e7cb430aacce5562af44f628c11c97c54..0000000000000000000000000000000000000000
--- a/projects/bloom/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-project(bloom)
-
-# setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
-# adding source files to the project
-add_executable(bloom src/main.cpp)
-
-target_sources(bloom PRIVATE
-		src/BloomAndFlares.cpp
-		src/BloomAndFlares.hpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(bloom PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
-
-# including headers of dependencies and the VkCV framework
-target_include_directories(bloom SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
-
-# linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(bloom vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/bloom/resources/Sponza/Sponza.bin b/projects/bloom/resources/Sponza/Sponza.bin
deleted file mode 100644
index cfedd26ca5a67b6d0a47d44d13a75e14a141717a..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/Sponza.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
-size 6347696
diff --git a/projects/bloom/resources/Sponza/Sponza.gltf b/projects/bloom/resources/Sponza/Sponza.gltf
deleted file mode 100644
index 172ea07e21c94465211c860cd805355704cef230..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/Sponza.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
-size 713962
diff --git a/projects/bloom/resources/Sponza/background.png b/projects/bloom/resources/Sponza/background.png
deleted file mode 100644
index b64def129da38f4e23d89e21b4af1039008a4327..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/background.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
-size 1417666
diff --git a/projects/bloom/resources/Sponza/chain_texture.png b/projects/bloom/resources/Sponza/chain_texture.png
deleted file mode 100644
index c1e1768cff78e0614ad707eca8602a4c4edab5e5..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/chain_texture.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
-size 490895
diff --git a/projects/bloom/resources/Sponza/lion.png b/projects/bloom/resources/Sponza/lion.png
deleted file mode 100644
index c49c7f0ed31e762e19284d0d3624fbc47664e56b..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/lion.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
-size 2088728
diff --git a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png b/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png
deleted file mode 100644
index cde4c7a6511e9a5f03c63ad996437fcdba3ce2df..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
-size 1928829
diff --git a/projects/bloom/resources/Sponza/sponza_arch_diff.png b/projects/bloom/resources/Sponza/sponza_arch_diff.png
deleted file mode 100644
index bcd9bda2918d226039f9e2d03902d377b706fab6..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_arch_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
-size 1596151
diff --git a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png b/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png
deleted file mode 100644
index 59de631ffac4414cabf69b2dc794c46fc187d6cb..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
-size 1872074
diff --git a/projects/bloom/resources/Sponza/sponza_column_a_diff.png b/projects/bloom/resources/Sponza/sponza_column_a_diff.png
deleted file mode 100644
index 01a82432d3f9939bbefe850bdb900f1ff9a3f6db..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_column_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
-size 1840992
diff --git a/projects/bloom/resources/Sponza/sponza_column_b_diff.png b/projects/bloom/resources/Sponza/sponza_column_b_diff.png
deleted file mode 100644
index 10a660cce2a5a9b8997772c746058ce23e7d45d7..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_column_b_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
-size 2170433
diff --git a/projects/bloom/resources/Sponza/sponza_column_c_diff.png b/projects/bloom/resources/Sponza/sponza_column_c_diff.png
deleted file mode 100644
index bc46fd979044a938d3adca7601689e71504e48bf..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_column_c_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
-size 2066950
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png
deleted file mode 100644
index 384c8c2c051160d530eb3ac8b05c9c60752a2d2b..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
-size 9510905
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_diff.png
deleted file mode 100644
index af842e9f5fe18c1f609875e00899a6770fa4488b..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_curtain_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
-size 9189263
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png
deleted file mode 100644
index 6c9b6391a199407637fa71033d79fb58b8b4f0d7..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
-size 8785458
diff --git a/projects/bloom/resources/Sponza/sponza_details_diff.png b/projects/bloom/resources/Sponza/sponza_details_diff.png
deleted file mode 100644
index 12656686362c3e0a297e060491f33bd7351551f9..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_details_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
-size 1522643
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png
deleted file mode 100644
index 879d16ef84722a4fc13e83a771778de326e4bc54..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
-size 2229440
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_diff.png
deleted file mode 100644
index 3311287a219d2148620b87fe428fea071688d051..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_fabric_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
-size 2267382
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png
deleted file mode 100644
index de110f369004388dae4cd5067c63428db3a07834..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
-size 2258220
diff --git a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png b/projects/bloom/resources/Sponza/sponza_flagpole_diff.png
deleted file mode 100644
index 5f6e0812a0df80346318baa3cb50a6888afc58f8..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
-size 1425673
diff --git a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png b/projects/bloom/resources/Sponza/sponza_floor_a_diff.png
deleted file mode 100644
index 788ed764f79ba724f04a2d603076a5b85013e188..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
-size 1996838
diff --git a/projects/bloom/resources/Sponza/sponza_roof_diff.png b/projects/bloom/resources/Sponza/sponza_roof_diff.png
deleted file mode 100644
index c5b84261fdd1cc776a94b3ce398c7806b895f9a3..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_roof_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
-size 2320533
diff --git a/projects/bloom/resources/Sponza/sponza_thorn_diff.png b/projects/bloom/resources/Sponza/sponza_thorn_diff.png
deleted file mode 100644
index 7a9142674a7d4a6f94a48c5152cf0300743b597a..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/sponza_thorn_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
-size 635001
diff --git a/projects/bloom/resources/Sponza/vase_dif.png b/projects/bloom/resources/Sponza/vase_dif.png
deleted file mode 100644
index 61236a81cb324af8797b05099cd264cefe189e56..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/vase_dif.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
-size 1842101
diff --git a/projects/bloom/resources/Sponza/vase_hanging.png b/projects/bloom/resources/Sponza/vase_hanging.png
deleted file mode 100644
index 36a3cee71d8213225090c74f8c0dce33b9d44378..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/vase_hanging.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
-size 1432804
diff --git a/projects/bloom/resources/Sponza/vase_plant.png b/projects/bloom/resources/Sponza/vase_plant.png
deleted file mode 100644
index 7ad95e702e229f1ebd803e5203a266d15f2c07b9..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/vase_plant.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
-size 998089
diff --git a/projects/bloom/resources/Sponza/vase_round.png b/projects/bloom/resources/Sponza/vase_round.png
deleted file mode 100644
index c17953abc000c44b8991e23c136c2b67348f3d1b..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/Sponza/vase_round.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
-size 1871399
diff --git a/projects/bloom/resources/shaders/comp.spv b/projects/bloom/resources/shaders/comp.spv
deleted file mode 100644
index 85c7e74cfc0a89917bf6dd1a7ec449368274c1d3..0000000000000000000000000000000000000000
Binary files a/projects/bloom/resources/shaders/comp.spv and /dev/null differ
diff --git a/projects/bloom/resources/shaders/composite.comp b/projects/bloom/resources/shaders/composite.comp
deleted file mode 100644
index 190bed0657d70e0217bf654820d0b2b2c58f12c2..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/composite.comp
+++ /dev/null
@@ -1,38 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          blurImage;
-layout(set=0, binding=1) uniform texture2D                          lensImage;
-layout(set=0, binding=2) uniform sampler                            linearSampler;
-layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D            colorBuffer;
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-
-void main()
-{
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){
-        return;
-    }
-
-    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
-    vec2  pixel_size    = vec2(1.0f) / textureSize(sampler2D(blurImage, linearSampler), 0);
-    vec2  UV            = pixel_coord.xy * pixel_size;
-
-    vec4 composite_color = vec4(0.0f);
-
-    vec3 blur_color   = texture(sampler2D(blurImage, linearSampler), UV).rgb;
-    vec3 lens_color   = texture(sampler2D(lensImage, linearSampler), UV).rgb;
-    vec3 main_color   = imageLoad(colorBuffer, pixel_coord).rgb;
-
-    // composite blur and lens features
-    float bloom_weight = 0.25f;
-    float lens_weight  = 0.25f;
-    float main_weight = 1 - (bloom_weight + lens_weight);
-
-    composite_color.rgb = blur_color * bloom_weight +
-                          lens_color * lens_weight  +
-                          main_color * main_weight;
-
-    imageStore(colorBuffer, pixel_coord, composite_color);
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/downsample.comp b/projects/bloom/resources/shaders/downsample.comp
deleted file mode 100644
index 2ab00c7c92798769153634f3479c5b7f3fb61d94..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/downsample.comp
+++ /dev/null
@@ -1,76 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          inBlurImage;
-layout(set=0, binding=1) uniform sampler                            inImageSampler;
-layout(set=0, binding=2, r11f_g11f_b10f) uniform writeonly image2D  outBlurImage;
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-
-void main()
-{
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outBlurImage)))){
-        return;
-    }
-
-    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
-    vec2  pixel_size    = vec2(1.0f) / imageSize(outBlurImage);
-    vec2  UV            = pixel_coord.xy * pixel_size;
-    vec2  UV_offset     = UV + 0.5f * pixel_size;
-
-    vec2 color_fetches[13] = {
-        // center neighbourhood (RED)
-        vec2(-1,  1), // LT
-        vec2(-1, -1), // LB
-        vec2( 1, -1), // RB
-        vec2( 1,  1), // RT
-
-        vec2(-2, 2), // LT
-        vec2( 0, 2), // CT
-        vec2( 2, 2), // RT
-
-        vec2(0 ,-2), // LC
-        vec2(0 , 0), // CC
-        vec2(2,  0), // CR
-
-        vec2(-2, -2), // LB
-        vec2(0 , -2), // CB
-        vec2(2 , -2)  // RB
-    };
-
-    float color_weights[13] = {
-        // 0.5f
-        1.f/8.f,
-        1.f/8.f,
-        1.f/8.f,
-        1.f/8.f,
-
-        // 0.125f
-        1.f/32.f,
-        1.f/16.f,
-        1.f/32.f,
-
-        // 0.25f
-        1.f/16.f,
-        1.f/8.f,
-        1.f/16.f,
-
-        // 0.125f
-        1.f/32.f,
-        1.f/16.f,
-        1.f/32.f
-    };
-
-    vec3 sampled_color = vec3(0.0f);
-
-    for(uint i = 0; i < 13; i++)
-    {
-        vec2 color_fetch = UV_offset + color_fetches[i] * pixel_size;
-        vec3 color = texture(sampler2D(inBlurImage, inImageSampler), color_fetch).rgb;
-        color *= color_weights[i];
-        sampled_color += color;
-    }
-
-    imageStore(outBlurImage, pixel_coord, vec4(sampled_color, 1.f));
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/gammaCorrection.comp b/projects/bloom/resources/shaders/gammaCorrection.comp
deleted file mode 100644
index f89ad167c846cca8e80f69d33eda83bd6ed00d46..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/gammaCorrection.comp
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 440
-
-layout(set=0, binding=0, r11f_g11f_b10f)    uniform image2D inImage;
-layout(set=0, binding=1, rgba8)             uniform image2D outImage;
-
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-void main(){
-
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
-        return;
-    }
-    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
-    vec3 linearColor = imageLoad(inImage, uv).rgb;
-    // cheap Reinhard tone mapping
-    linearColor = linearColor/(linearColor + 1.0f);
-    vec3 gammaCorrected = pow(linearColor, vec3(1.f / 2.2f));
-    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/lensFlares.comp b/projects/bloom/resources/shaders/lensFlares.comp
deleted file mode 100644
index ce27d8850b709f61332d467914ddc944dc63109f..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/lensFlares.comp
+++ /dev/null
@@ -1,109 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          blurBuffer;
-layout(set=0, binding=1) uniform sampler                            linearSampler;
-layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D            lensBuffer;
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-vec3 sampleColorChromaticAberration(vec2 _uv)
-{
-    vec2 toCenter = (vec2(0.5) - _uv);
-
-    vec3    colorScales     = vec3(-1, 0, 1);
-    float   aberrationScale = 0.1;
-    vec3 scaleFactors = colorScales * aberrationScale;
-
-    float r = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.r).r;
-    float g = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.g).g;
-    float b = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.b).b;
-    return vec3(r, g, b);
-}
-
-// _uv assumed to be flipped UV coordinates!
-vec3 ghost_vectors(vec2 _uv)
-{
-    vec2 ghost_vec = (vec2(0.5f) - _uv);
-
-    const uint c_ghost_count = 64;
-    const float c_ghost_spacing = length(ghost_vec) / c_ghost_count;
-
-    ghost_vec *= c_ghost_spacing;
-
-    vec3 ret_color = vec3(0.0f);
-
-    for (uint i = 0; i < c_ghost_count; ++i)
-    {
-        // sample scene color
-        vec2 s_uv = fract(_uv + ghost_vec * vec2(i));
-        vec3 s = sampleColorChromaticAberration(s_uv);
-
-        // tint/weight
-        float d = distance(s_uv, vec2(0.5));
-        float weight = 1.0f - smoothstep(0.0f, 0.75f, d);
-        s *= weight;
-
-        ret_color += s;
-    }
-
-    ret_color /= c_ghost_count;
-    return ret_color;
-}
-
-vec3 halo(vec2 _uv)
-{
-    const float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y);
-    const float c_radius = 0.6f;
-    const float c_halo_thickness = 0.1f;
-
-    vec2 halo_vec = vec2(0.5) - _uv;
-    //halo_vec.x /= c_aspect_ratio;
-    halo_vec = normalize(halo_vec);
-    //halo_vec.x *= c_aspect_ratio;
-
-
-    //vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0);
-    vec2 w_uv = _uv;
-    float d = distance(w_uv, vec2(0.5)); // distance to center
-
-    float distance_to_halo = abs(d - c_radius);
-
-    float halo_weight = 0.0f;
-    if(abs(d - c_radius) <= c_halo_thickness)
-    {
-        float distance_to_border = c_halo_thickness - distance_to_halo;
-        halo_weight = distance_to_border / c_halo_thickness;
-
-        //halo_weight = clamp((halo_weight / 0.4f), 0.0f, 1.0f);
-        halo_weight = pow(halo_weight, 2.0f);
-
-
-        //halo_weight = 1.0f;
-    }
-
-    return sampleColorChromaticAberration(_uv + halo_vec) * halo_weight;
-}
-
-
-
-void main()
-{
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(lensBuffer)))){
-        return;
-    }
-
-    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
-    vec2  pixel_size    = vec2(1.0f) / imageSize(lensBuffer);
-    vec2  UV            = pixel_coord.xy * pixel_size;
-
-    vec2 flipped_UV = vec2(1.0f) - UV;
-
-    vec3 color = vec3(0.0f);
-
-    color += ghost_vectors(flipped_UV);
-    color += halo(UV);
-    color  *= 0.5f;
-
-    imageStore(lensBuffer, pixel_coord, vec4(color, 0.0f));
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/perMeshResources.inc b/projects/bloom/resources/shaders/perMeshResources.inc
deleted file mode 100644
index 95e4fb7c27009965659d14a9c72acfec950c37e3..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/perMeshResources.inc
+++ /dev/null
@@ -1,2 +0,0 @@
-layout(set=1, binding=0) uniform texture2D  albedoTexture;
-layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.frag b/projects/bloom/resources/shaders/shader.frag
deleted file mode 100644
index 3e95b4508f112c1ed9aa4a7050a98fa789dccd09..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/shader.frag
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-#extension GL_GOOGLE_include_directive : enable
-
-#include "perMeshResources.inc"
-
-layout(location = 0) in vec3 passNormal;
-layout(location = 1) in vec2 passUV;
-layout(location = 2) in vec3 passPos;
-
-layout(location = 0) out vec3 outColor;
-
-layout(set=0, binding=0) uniform sunBuffer {
-    vec3 L; float padding;
-    mat4 lightMatrix;
-};
-layout(set=0, binding=1) uniform texture2D  shadowMap;
-layout(set=0, binding=2) uniform sampler    shadowMapSampler;
-
-float shadowTest(vec3 worldPos){
-    vec4 lightPos = lightMatrix * vec4(worldPos, 1);
-    lightPos /= lightPos.w;
-    lightPos.xy = lightPos.xy * 0.5 + 0.5;
-    
-    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
-        return 1;
-    }
-    
-    lightPos.z = clamp(lightPos.z, 0, 1);
-    
-    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
-    float bias = 0.01f;
-    shadowMapSample += bias;
-    return shadowMapSample < lightPos.z ? 0 : 1;
-}
-
-void main()	{
-    vec3 N = normalize(passNormal);
-    vec3 sunColor = vec3(10);
-    vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
-    sun *= shadowTest(passPos);
-    vec3 ambient = vec3(0.05);
-    vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
-	outColor = albedo * (sun + ambient);
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.vert b/projects/bloom/resources/shaders/shader.vert
deleted file mode 100644
index 926f86af2860cb57c44d2d5ee78712b6ae155e5c..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/shader.vert
+++ /dev/null
@@ -1,22 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(location = 0) in vec3 inPosition;
-layout(location = 1) in vec3 inNormal;
-layout(location = 2) in vec2 inUV;
-
-layout(location = 0) out vec3 passNormal;
-layout(location = 1) out vec2 passUV;
-layout(location = 2) out vec3 passPos;
-
-layout( push_constant ) uniform constants{
-    mat4 mvp;
-    mat4 model;
-};
-
-void main()	{
-	gl_Position = mvp * vec4(inPosition, 1.0);
-	passNormal  = mat3(model) * inNormal;    // assuming no weird stuff like shearing or non-uniform scaling
-    passUV      = inUV;
-    passPos     = (model * vec4(inPosition, 1)).xyz;
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shadow.frag b/projects/bloom/resources/shaders/shadow.frag
deleted file mode 100644
index 848f853f556660b4900b5db7fb6fc98d57c1cd5b..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/shadow.frag
+++ /dev/null
@@ -1,6 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-void main()	{
-
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shadow.vert b/projects/bloom/resources/shaders/shadow.vert
deleted file mode 100644
index e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/shadow.vert
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(location = 0) in vec3 inPosition;
-
-layout( push_constant ) uniform constants{
-    mat4 mvp;
-};
-
-void main()	{
-	gl_Position = mvp * vec4(inPosition, 1.0);
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/upsample.comp b/projects/bloom/resources/shaders/upsample.comp
deleted file mode 100644
index 0ddeedb5b5af9e476dc19012fed6430544006c0e..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/upsample.comp
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          inUpsampleImage;
-layout(set=0, binding=1) uniform sampler                            inImageSampler;
-layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D  outUpsampleImage;
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-void main()
-{
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outUpsampleImage)))){
-        return;
-    }
-
-
-    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
-    vec2  pixel_size    = vec2(1.0f) / imageSize(outUpsampleImage);
-    vec2  UV            = pixel_coord.xy * pixel_size;
-
-    const float gauss_kernel[3] = {1.f, 2.f, 1.f};
-    const float gauss_weight = 16.f;
-
-    vec3 sampled_color = vec3(0.f);
-
-    for(int i = -1; i <= 1; i++)
-    {
-        for(int j = -1; j <= 1; j++)
-        {
-            vec2 sample_location = UV + vec2(j, i) * pixel_size;
-            vec3 color = texture(sampler2D(inUpsampleImage, inImageSampler), sample_location).rgb;
-            color *= gauss_kernel[j+1];
-            color *= gauss_kernel[i+1];
-            color /= gauss_weight;
-
-            sampled_color += color;
-        }
-    }
-
-    //vec3 prev_color = imageLoad(outUpsampleImage, pixel_coord).rgb;
-    //float bloomRimStrength = 0.75f; // adjust this to change strength of bloom
-    //sampled_color = mix(prev_color, sampled_color, bloomRimStrength);
-
-    imageStore(outUpsampleImage, pixel_coord, vec4(sampled_color, 1.f));
-}
\ No newline at end of file
diff --git a/projects/bloom/src/BloomAndFlares.cpp b/projects/bloom/src/BloomAndFlares.cpp
deleted file mode 100644
index 6f26db9de0f2c8334b6dd7e5dd6cf4b6f48baedc..0000000000000000000000000000000000000000
--- a/projects/bloom/src/BloomAndFlares.cpp
+++ /dev/null
@@ -1,274 +0,0 @@
-#include "BloomAndFlares.hpp"
-#include <vkcv/shader/GLSLCompiler.hpp>
-
-BloomAndFlares::BloomAndFlares(
-        vkcv::Core *p_Core,
-        vk::Format colorBufferFormat,
-        uint32_t width,
-        uint32_t height) :
-
-        p_Core(p_Core),
-        m_ColorBufferFormat(colorBufferFormat),
-        m_Width(width),
-        m_Height(height),
-        m_LinearSampler(p_Core->createSampler(vkcv::SamplerFilterType::LINEAR,
-                                              vkcv::SamplerFilterType::LINEAR,
-                                              vkcv::SamplerMipmapMode::LINEAR,
-                                              vkcv::SamplerAddressMode::CLAMP_TO_EDGE)),
-        m_Blur(p_Core->createImage(colorBufferFormat, width, height, 1, true, true, false)),
-        m_LensFeatures(p_Core->createImage(colorBufferFormat, width, height, 1, false, true, false))
-{
-    vkcv::shader::GLSLCompiler compiler;
-
-    // DOWNSAMPLE
-    vkcv::ShaderProgram dsProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/downsample.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         dsProg.addShader(shaderStage, path);
-                     });
-    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
-    {
-		m_DownsampleDescSets.push_back(
-                p_Core->createDescriptorSet(dsProg.getReflectedDescriptors()[0]));
-    }
-    m_DownsamplePipe = p_Core->createComputePipeline(
-            dsProg, { p_Core->getDescriptorSet(m_DownsampleDescSets[0]).layout });
-
-    // UPSAMPLE
-    vkcv::ShaderProgram usProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/upsample.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         usProg.addShader(shaderStage, path);
-                     });
-    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
-    {
-        m_UpsampleDescSets.push_back(
-                p_Core->createDescriptorSet(usProg.getReflectedDescriptors()[0]));
-    }
-    m_UpsamplePipe = p_Core->createComputePipeline(
-            usProg, { p_Core->getDescriptorSet(m_UpsampleDescSets[0]).layout });
-
-    // LENS FEATURES
-    vkcv::ShaderProgram lensProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/lensFlares.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         lensProg.addShader(shaderStage, path);
-                     });
-    m_LensFlareDescSet = p_Core->createDescriptorSet(lensProg.getReflectedDescriptors()[0]);
-    m_LensFlarePipe = p_Core->createComputePipeline(
-            lensProg, { p_Core->getDescriptorSet(m_LensFlareDescSet).layout });
-
-    // COMPOSITE
-    vkcv::ShaderProgram compProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/composite.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         compProg.addShader(shaderStage, path);
-                     });
-    m_CompositeDescSet = p_Core->createDescriptorSet(compProg.getReflectedDescriptors()[0]);
-    m_CompositePipe = p_Core->createComputePipeline(
-            compProg, { p_Core->getDescriptorSet(m_CompositeDescSet).layout });
-}
-
-void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream,
-                                        const vkcv::ImageHandle &colorAttachment)
-{
-    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
-    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-    // blur dispatch
-    uint32_t initialDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-
-    // downsample dispatch of original color attachment
-    p_Core->prepareImageForSampling(cmdStream, colorAttachment);
-    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
-
-    vkcv::DescriptorWrites initialDownsampleWrites;
-    initialDownsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorAttachment)};
-    initialDownsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-    initialDownsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), 0) };
-    p_Core->writeDescriptorSet(m_DownsampleDescSets[0], initialDownsampleWrites);
-
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_DownsamplePipe,
-            initialDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-
-    // downsample dispatches of blur buffer's mip maps
-    float mipDispatchCountX = dispatchCountX;
-    float mipDispatchCountY = dispatchCountY;
-    for(uint32_t mipLevel = 1; mipLevel < m_DownsampleDescSets.size(); mipLevel++)
-    {
-        // mip descriptor writes
-        vkcv::DescriptorWrites mipDescriptorWrites;
-        mipDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel - 1, true)};
-        mipDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-        mipDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel) };
-        p_Core->writeDescriptorSet(m_DownsampleDescSets[mipLevel], mipDescriptorWrites);
-
-        // mip dispatch calculation
-        mipDispatchCountX  /= 2.0f;
-        mipDispatchCountY /= 2.0f;
-
-        uint32_t mipDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(mipDispatchCountX)),
-                static_cast<uint32_t>(glm::ceil(mipDispatchCountY)),
-                1
-        };
-
-        if(mipDispatchCount[0] == 0)
-            mipDispatchCount[0] = 1;
-        if(mipDispatchCount[1] == 0)
-            mipDispatchCount[1] = 1;
-
-        // mip blur dispatch
-        p_Core->recordComputeDispatchToCmdStream(
-                cmdStream,
-                m_DownsamplePipe,
-                mipDispatchCount,
-                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0));
-
-        // image barrier between mips
-        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
-    }
-}
-
-void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream)
-{
-    // upsample dispatch
-    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
-
-    const uint32_t upsampleMipLevels = std::min(
-    		static_cast<uint32_t>(m_UpsampleDescSets.size() - 1),
-    		static_cast<uint32_t>(5)
-	);
-
-    // upsample dispatch for each mip map
-    for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--)
-    {
-        // mip descriptor writes
-        vkcv::DescriptorWrites mipUpsampleWrites;
-        mipUpsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel, true)};
-        mipUpsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-        mipUpsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel - 1) };
-        p_Core->writeDescriptorSet(m_UpsampleDescSets[mipLevel], mipUpsampleWrites);
-
-        auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f);
-
-        auto upsampleDispatchX  = static_cast<float>(m_Width) / mipDivisor;
-        auto upsampleDispatchY = static_cast<float>(m_Height) / mipDivisor;
-        upsampleDispatchX /= 8.0f;
-        upsampleDispatchY /= 8.0f;
-
-        const uint32_t upsampleDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(upsampleDispatchX)),
-                static_cast<uint32_t>(glm::ceil(upsampleDispatchY)),
-                1
-        };
-
-        p_Core->recordComputeDispatchToCmdStream(
-                cmdStream,
-                m_UpsamplePipe,
-                upsampleDispatchCount,
-                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0)
-        );
-        // image barrier between mips
-        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
-    }
-}
-
-void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream)
-{
-    // lens feature generation descriptor writes
-    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
-    p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle());
-
-    vkcv::DescriptorWrites lensFeatureWrites;
-    lensFeatureWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), 0)};
-    lensFeatureWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-    lensFeatureWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_LensFeatures.getHandle(), 0)};
-    p_Core->writeDescriptorSet(m_LensFlareDescSet, lensFeatureWrites);
-
-    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
-    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-    // lens feature generation dispatch
-    uint32_t lensFeatureDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_LensFlarePipe,
-            lensFeatureDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-}
-
-void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream,
-                                       const vkcv::ImageHandle &colorAttachment)
-{
-    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
-    p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle());
-    p_Core->prepareImageForStorage(cmdStream, colorAttachment);
-
-    // bloom composite descriptor write
-    vkcv::DescriptorWrites compositeWrites;
-    compositeWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle()),
-                                          vkcv::SampledImageDescriptorWrite(1, m_LensFeatures.getHandle())};
-    compositeWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(2, m_LinearSampler)};
-    compositeWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(3, colorAttachment)};
-    p_Core->writeDescriptorSet(m_CompositeDescSet, compositeWrites);
-
-    float dispatchCountX = static_cast<float>(m_Width)  / 8.0f;
-    float dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-
-    uint32_t compositeDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-
-    // bloom composite dispatch
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_CompositePipe,
-            compositeDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-}
-
-void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream,
-                                       const vkcv::ImageHandle &colorAttachment)
-{
-    execDownsamplePipe(cmdStream, colorAttachment);
-    execUpsamplePipe(cmdStream);
-    execLensFeaturePipe(cmdStream);
-    execCompositePipe(cmdStream, colorAttachment);
-}
-
-void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height)
-{
-    m_Width  = width;
-    m_Height = height;
-
-    p_Core->getContext().getDevice().waitIdle();
-    m_Blur = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, true, true, false);
-    m_LensFeatures = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, false, true, false);
-}
-
-
diff --git a/projects/bloom/src/BloomAndFlares.hpp b/projects/bloom/src/BloomAndFlares.hpp
deleted file mode 100644
index 756b1ca154ea5232df04eb09a88bb743c5bd28aa..0000000000000000000000000000000000000000
--- a/projects/bloom/src/BloomAndFlares.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-#include <vkcv/Core.hpp>
-#include <glm/glm.hpp>
-
-class BloomAndFlares{
-public:
-    BloomAndFlares(vkcv::Core *p_Core,
-                   vk::Format colorBufferFormat,
-                   uint32_t width,
-                   uint32_t height);
-
-    void execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-
-    void updateImageDimensions(uint32_t width, uint32_t height);
-
-private:
-    vkcv::Core *p_Core;
-
-    vk::Format m_ColorBufferFormat;
-    uint32_t m_Width;
-    uint32_t m_Height;
-
-    vkcv::SamplerHandle m_LinearSampler;
-    vkcv::Image m_Blur;
-    vkcv::Image m_LensFeatures;
-
-
-    vkcv::PipelineHandle                     m_DownsamplePipe;
-    std::vector<vkcv::DescriptorSetHandle>   m_DownsampleDescSets; // per mip desc set
-
-    vkcv::PipelineHandle                     m_UpsamplePipe;
-    std::vector<vkcv::DescriptorSetHandle>   m_UpsampleDescSets;   // per mip desc set
-
-    vkcv::PipelineHandle                     m_LensFlarePipe;
-    vkcv::DescriptorSetHandle                m_LensFlareDescSet;
-
-    vkcv::PipelineHandle                     m_CompositePipe;
-    vkcv::DescriptorSetHandle                m_CompositeDescSet;
-
-    void execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-    void execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream);
-    void execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream);
-    void execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-};
-
-
-
diff --git a/projects/bloom/src/main.cpp b/projects/bloom/src/main.cpp
deleted file mode 100644
index 7a17a51f1c7d638575c0b5aafcdca49b589533ef..0000000000000000000000000000000000000000
--- a/projects/bloom/src/main.cpp
+++ /dev/null
@@ -1,419 +0,0 @@
-#include <iostream>
-#include <vkcv/Core.hpp>
-#include <GLFW/glfw3.h>
-#include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
-#include <vkcv/asset/asset_loader.hpp>
-#include <vkcv/shader/GLSLCompiler.hpp>
-#include <vkcv/Logger.hpp>
-#include "BloomAndFlares.hpp"
-#include <glm/glm.hpp>
-
-int main(int argc, const char** argv) {
-	const char* applicationName = "Bloom";
-
-	uint32_t windowWidth = 1920;
-	uint32_t windowHeight = 1080;
-	
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
-
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-    
-    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
-    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
-	cameraManager.getCamera(camIndex).setYaw(180.0f);
-	
-	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
-
-	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
-	);
-
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
-	vkcv::asset::Scene scene;
-	int result = vkcv::asset::loadScene(path, scene);
-
-	if (result == 1) {
-		std::cout << "Scene loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Scene loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	// build index and vertex buffers
-	assert(!scene.vertexGroups.empty());
-	std::vector<std::vector<uint8_t>> vBuffers;
-	std::vector<std::vector<uint8_t>> iBuffers;
-
-	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
-	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-	std::vector<vkcv::asset::VertexAttribute> vAttributes;
-
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-
-		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
-		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
-
-		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
-
-		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-		});
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
-	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
-		vertexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			group.vertexBuffer.data.size()));
-		vertexBuffers.back().fill(group.vertexBuffer.data);
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
-	for (const auto& dataBuffer : iBuffers) {
-		indexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			dataBuffer.size()));
-		indexBuffers.back().fill(dataBuffer);
-	}
-
-	int vertexBufferIndex = 0;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
-			vAttributes.push_back(attribute);
-			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-		}
-		vertexBufferBindings.push_back(vBufferBindings);
-		vBufferBindings.clear();
-		vertexBufferIndex++;
-	}
-
-	const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32;
-	const vkcv::AttachmentDescription color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		colorBufferFormat
-	);
-	
-	const vk::Format depthBufferFormat = vk::Format::eD32Sfloat;
-	const vkcv::AttachmentDescription depth_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		depthBufferFormat
-	);
-
-	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment });
-	vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition);
-
-	vkcv::shader::GLSLCompiler compiler;
-
-	vkcv::ShaderProgram forwardProgram;
-	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), 
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		forwardProgram.addShader(shaderStage, path);
-	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		forwardProgram.addShader(shaderStage, path);
-	});
-
-	const std::vector<vkcv::VertexAttachment> vertexAttachments = forwardProgram.getVertexAttachments();
-
-	std::vector<vkcv::VertexBinding> vertexBindings;
-	for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
-	}
-	const vkcv::VertexLayout vertexLayout (vertexBindings);
-
-	// shadow map
-	vkcv::SamplerHandle shadowSampler = core.createSampler(
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerMipmapMode::NEAREST,
-		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
-	);
-	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
-	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
-
-	// light info buffer
-	struct LightInfo {
-		glm::vec3 direction;
-		float padding;
-		glm::mat4 lightMatrix;
-	};
-	LightInfo lightInfo;
-	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
-
-	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
-		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
-
-	vkcv::DescriptorWrites forwardDescriptorWrites;
-	forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) };
-	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
-	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
-	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
-
-	vkcv::SamplerHandle colorSampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-	// prepare per mesh descriptor sets
-	std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets;
-	std::vector<vkcv::Image> sceneImages;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		perMeshDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1]));
-
-		const auto& material = scene.materials[vertexGroup.materialIndex];
-
-		int baseColorIndex = material.baseColor;
-		if (baseColorIndex < 0) {
-			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
-			baseColorIndex = 0;
-		}
-
-		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
-
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
-		sceneImages.back().fill(sceneTexture.data.data());
-
-		vkcv::DescriptorWrites setWrites;
-		setWrites.sampledImageWrites = {
-			vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle())
-		};
-		setWrites.samplerWrites = {
-			vkcv::SamplerDescriptorWrite(1, colorSampler),
-		};
-		core.writeDescriptorSet(perMeshDescriptorSets.back(), setWrites);
-	}
-
-	const vkcv::PipelineConfig forwardPipelineConfig {
-		forwardProgram,
-		windowWidth,
-		windowHeight,
-		forwardPass,
-		vertexLayout,
-		{	core.getDescriptorSet(forwardShadingDescriptorSet).layout, 
-			core.getDescriptorSet(perMeshDescriptorSets[0]).layout },
-		true
-	};
-	
-	vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
-	
-	if (!forwardPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-
-	vkcv::ImageHandle depthBuffer       = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
-	vkcv::ImageHandle colorBuffer       = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
-
-	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	vkcv::ShaderProgram shadowShader;
-	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
-	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
-	});
-
-	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
-		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
-	};
-	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
-	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
-	const vkcv::PipelineConfig shadowPipeConfig{
-		shadowShader,
-		shadowMapResolution,
-		shadowMapResolution,
-		shadowPass,
-		vertexLayout,
-		{},
-		false
-	};
-	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
-
-	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
-	std::vector<glm::mat4> mvpLight;
-
-	// gamma correction compute shader
-	vkcv::ShaderProgram gammaCorrectionProgram;
-	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/gammaCorrection.comp",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		gammaCorrectionProgram.addShader(shaderStage, path);
-	});
-	vkcv::DescriptorSetHandle gammaCorrectionDescriptorSet = core.createDescriptorSet(gammaCorrectionProgram.getReflectedDescriptors()[0]);
-	vkcv::PipelineHandle gammaCorrectionPipeline = core.createComputePipeline(gammaCorrectionProgram,
-		{ core.getDescriptorSet(gammaCorrectionDescriptorSet).layout });
-
-    BloomAndFlares baf(&core, colorBufferFormat, windowWidth, windowHeight);
-
-
-	// model matrices per mesh
-	std::vector<glm::mat4> modelMatrices;
-	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
-	for (const auto& mesh : scene.meshes) {
-		const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]);
-		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
-			modelMatrices[vertexGroupIndex] = m;
-		}
-	}
-
-	// prepare drawcalls
-	std::vector<vkcv::Mesh> meshes;
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-		vkcv::Mesh mesh(
-			vertexBufferBindings[i], 
-			indexBuffers[i].getVulkanHandle(), 
-			scene.vertexGroups[i].numIndices);
-		meshes.push_back(mesh);
-	}
-
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
-		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
-			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
-			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
-		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
-	}
-
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (window.isWindowOpen()) {
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
-			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
-
-			baf.updateImageDimensions(swapchainWidth, swapchainHeight);
-
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
-		}
-
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-
-		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
-		
-		const float sunTheta = 0.0001f * static_cast<float>(duration.count());
-		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
-
-		const float shadowProjectionSize = 20.f;
-		glm::mat4 projectionLight = glm::ortho(
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize);
-
-		glm::mat4 vulkanCorrectionMatrix(1.f);
-		vulkanCorrectionMatrix[2][2] = 0.5;
-		vulkanCorrectionMatrix[3][2] = 0.5;
-		projectionLight = vulkanCorrectionMatrix * projectionLight;
-
-		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
-
-		lightInfo.lightMatrix = projectionLight * viewLight;
-		lightBuffer.fill({ lightInfo });
-
-		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
-		mainPassMatrices.clear();
-		mvpLight.clear();
-		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
-			mvpLight.push_back(lightInfo.lightMatrix * m);
-		}
-
-		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
-		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
-
-		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
-
-		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
-		// shadow map
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			shadowPass,
-			shadowPipe,
-			shadowPushConstantData,
-			shadowDrawcalls,
-			{ shadowMap.getHandle() });
-		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
-
-		// main pass
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-            forwardPass,
-            forwardPipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
-
-        const uint32_t gammaCorrectionLocalGroupSize = 8;
-        const uint32_t gammaCorrectionDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowWidth) / static_cast<float>(gammaCorrectionLocalGroupSize))),
-                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowHeight) / static_cast<float>(gammaCorrectionLocalGroupSize))),
-                1
-        };
-
-        baf.execWholePipeline(cmdStream, colorBuffer);
-
-        core.prepareImageForStorage(cmdStream, swapchainInput);
-        
-        // gamma correction descriptor write
-        vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
-        gammaCorrectionDescriptorWrites.storageImageWrites = {
-                vkcv::StorageImageDescriptorWrite(0, colorBuffer),
-                vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
-        core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites);
-
-        // gamma correction dispatch
-        core.recordComputeDispatchToCmdStream(
-			cmdStream, 
-			gammaCorrectionPipeline, 
-			gammaCorrectionDispatchCount,
-			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gammaCorrectionDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData(nullptr, 0));
-
-		// present and end
-		core.prepareSwapchainImageForPresent(cmdStream);
-		core.submitCommandStream(cmdStream);
-
-		core.endFrame();
-	}
-	
-	return 0;
-}
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
deleted file mode 100644
index 6e53eb8c5ec1825135778dc91b11dd6e45f44276..0000000000000000000000000000000000000000
--- a/projects/cmd_sync_test/src/main.cpp
+++ /dev/null
@@ -1,317 +0,0 @@
-#include <iostream>
-#include <vkcv/Core.hpp>
-#include <GLFW/glfw3.h>
-#include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
-#include <vkcv/asset/asset_loader.hpp>
-
-int main(int argc, const char** argv) {
-	const char* applicationName = "First Mesh";
-
-	uint32_t windowWidth = 800;
-	uint32_t windowHeight = 600;
-	
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
-
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-    
-    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
-    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
-	cameraManager.getCamera(camIndex).setYaw(180.0f);
-	
-	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
-
-	window.initEvents();
-
-	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
-	);
-
-	vkcv::asset::Scene mesh;
-
-	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadScene(path, mesh);
-
-	if (result == 1) {
-		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	assert(mesh.vertexGroups.size() > 0);
-	auto vertexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			mesh.vertexGroups[0].vertexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
-	);
-	
-	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
-
-	auto indexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			mesh.vertexGroups[0].indexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
-	);
-	
-	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
-	
-	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
-	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-	});
-
-	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-		vkcv::VertexBufferBinding(attributes[0].offset, vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(attributes[1].offset, vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(attributes[2].offset, vertexBuffer.getVulkanHandle()) };
-
-	const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
-
-	// an example attachment for passes that output to the window
-	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchain().getFormat()
-	);
-	
-	const vkcv::AttachmentDescription depth_attachment(
-			vkcv::AttachmentOperation::STORE,
-			vkcv::AttachmentOperation::CLEAR,
-			vk::Format::eD32Sfloat
-	);
-
-	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
-
-	if (!firstMeshPass) {
-		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-
-	vkcv::ShaderProgram firstMeshProgram{};
-    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-
-    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.getVertexAttachments();
-    
-    std::vector<vkcv::VertexBinding> bindings;
-    for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
-    }
-    
-    const vkcv::VertexLayout firstMeshLayout (bindings);
-
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] };
-	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
-
-	const vkcv::PipelineConfig firstMeshPipelineConfig {
-        firstMeshProgram,
-		windowWidth,
-		windowHeight,
-        firstMeshPass,
-        firstMeshLayout,
-		{ core.getDescriptorSet(descriptorSet).layout },
-		true
-	};
-	
-	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
-	
-	if (!firstMeshPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-	
-	//vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	//texture.fill(mesh.texture_hack.img);
-    vkcv::asset::Texture &tex = mesh.textures[0];
-    vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
-    texture.fill(tex.data.data());
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-    vkcv::SamplerHandle shadowSampler = core.createSampler(
-        vkcv::SamplerFilterType::NEAREST,
-        vkcv::SamplerFilterType::NEAREST,
-        vkcv::SamplerMipmapMode::NEAREST,
-        vkcv::SamplerAddressMode::CLAMP_TO_EDGE
-    );
-
-	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
-
-	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
-
-	const std::vector<glm::vec3> instancePositions = {
-		glm::vec3( 0.f, -2.f, 0.f),
-		glm::vec3( 3.f,  0.f, 0.f),
-		glm::vec3(-3.f,  0.f, 0.f),
-		glm::vec3( 0.f,  2.f, 0.f),
-		glm::vec3( 0.f, -5.f, 0.f)
-	};
-
-	std::vector<glm::mat4> modelMatrices;
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
-	for (const auto& position : instancePositions) {
-		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
-		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage },1));
-		shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {},1));
-	}
-
-	modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
-
-	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
-	std::vector<glm::mat4> mvpLight;
-
-	vkcv::ShaderProgram shadowShader;
-	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
-	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
-
-	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
-	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
-		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
-	};
-	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
-	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
-
-	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
-	const vkcv::PipelineConfig shadowPipeConfig {
-		shadowShader, 
-		shadowMapResolution, 
-		shadowMapResolution, 
-		shadowPass,
-        firstMeshLayout,
-		{},
-		false
-	};
-	
-	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
-
-	struct LightInfo {
-		glm::vec3 direction;
-		float padding;
-		glm::mat4 lightMatrix;
-	};
-	LightInfo lightInfo;
-	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
-
-	vkcv::DescriptorWrites setWrites;
-	setWrites.sampledImageWrites    = { 
-        vkcv::SampledImageDescriptorWrite(0, texture.getHandle()),
-        vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) };
-	setWrites.samplerWrites         = { 
-        vkcv::SamplerDescriptorWrite(1, sampler), 
-        vkcv::SamplerDescriptorWrite(4, shadowSampler) };
-    setWrites.uniformBufferWrites   = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) };
-	core.writeDescriptorSet(descriptorSet, setWrites);
-
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (window.isWindowOpen()) {
-		window.pollEvents();
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-		
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
-			
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
-		}
-		
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-
-		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
-		
-		const float sunTheta = 0.001f * static_cast<float>(duration.count());
-		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
-
-		const float shadowProjectionSize = 5.f;
-		glm::mat4 projectionLight = glm::ortho(
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize);
-		
-		glm::mat4 vulkanCorrectionMatrix(1.f);
-		vulkanCorrectionMatrix[2][2] = 0.5;
-		vulkanCorrectionMatrix[3][2] = 0.5;
-		projectionLight = vulkanCorrectionMatrix * projectionLight;
-
-		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
-
-		lightInfo.lightMatrix = projectionLight * viewLight;
-		lightBuffer.fill({ lightInfo });
-
-		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
-		mainPassMatrices.clear();
-		mvpLight.clear();
-		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
-			mvpLight.push_back(lightInfo.lightMatrix* m);
-		}
-
-		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
-		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
-
-		vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
-
-		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			shadowPass,
-			shadowPipe,
-			shadowPushConstantData,
-			shadowDrawcalls,
-			{ shadowMap.getHandle() });
-
-		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
-
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-            firstMeshPass,
-            firstMeshPipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
-		core.prepareSwapchainImageForPresent(cmdStream);
-		core.submitCommandStream(cmdStream);
-
-		core.endFrame();
-	}
-	
-	return 0;
-}
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index e7546fc3a143b3638cceb36869c519336ebec751..44a370be228be32f2e9d95685bb5297401605945 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -34,9 +34,8 @@ int main(int argc, const char** argv) {
 
 	if (result == 1) {
 		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
+	} else {
+		std::cerr << "Mesh loading failed: " << result << std::endl;
 		return 1;
 	}
 
@@ -74,7 +73,7 @@ int main(int argc, const char** argv) {
 	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
 
 	if (!firstMeshPass) {
-		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		std::cerr << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
@@ -113,12 +112,15 @@ int main(int argc, const char** argv) {
 	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
 	
 	if (!firstMeshPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	if (mesh.textures.empty()) {
+		std::cerr << "Error. No textures found. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 	
-	// FIXME There should be a test here to make sure there is at least 1
-	// texture in the mesh.
 	vkcv::asset::Texture &tex = mesh.textures[0];
 	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
 	texture.fill(tex.data.data());
@@ -161,7 +163,7 @@ int main(int argc, const char** argv) {
     auto start = std::chrono::system_clock::now();
     
 	while (window.isWindowOpen()) {
-        window.pollEvents();
+        vkcv::Window::pollEvents();
 		
 		if(window.getHeight() == 0 || window.getWidth() == 0)
 			continue;
@@ -185,7 +187,8 @@ int main(int argc, const char** argv) {
 		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
-		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
+		vkcv::PushConstants pushConstants (sizeof(glm::mat4));
+		pushConstants.appendDrawcall(mvp);
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
@@ -194,7 +197,7 @@ int main(int argc, const char** argv) {
 			cmdStream,
 			firstMeshPass,
 			firstMeshPipeline,
-			pushConstantData,
+			pushConstants,
 			{ drawcall },
 			renderTargets);
 		core.prepareSwapchainImageForPresent(cmdStream);
diff --git a/projects/first_scene/CMakeLists.txt b/projects/first_scene/CMakeLists.txt
index 8b90739750011a36b4c1d9e0bff7cba986074228..467663dcb916da78d0625a33c6c88a1af3403af7 100644
--- a/projects/first_scene/CMakeLists.txt
+++ b/projects/first_scene/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_scene_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
+target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_scene)
diff --git a/projects/first_scene/resources/Sponza/SponzaFloor.bin b/projects/first_scene/resources/Sponza/SponzaFloor.bin
new file mode 100644
index 0000000000000000000000000000000000000000..684251288f35070d2e7d244877fd844cc00ca632
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/SponzaFloor.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:678455aca641cb1f449aa1a5054a7cae132be81c2b333aac283053967da66df0
+size 512
diff --git a/projects/first_scene/resources/Sponza/SponzaFloor.gltf b/projects/first_scene/resources/Sponza/SponzaFloor.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..b45f1c55ef85f2aa1d4bff01df3d9625aa38c809
--- /dev/null
+++ b/projects/first_scene/resources/Sponza/SponzaFloor.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6deb75441b1138b50a6b0eec05e60df276fe8fb6d58118fdfce2090b6fbe734
+size 3139
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
index 521818732f7a60eabe9f0c2c080c6d343a71b1d8..486b21343926e1b922b73ba8ffb29428de6ea9a7 100644
--- a/projects/first_scene/src/main.cpp
+++ b/projects/first_scene/src/main.cpp
@@ -5,16 +5,9 @@
 #include <chrono>
 #include <vkcv/asset/asset_loader.hpp>
 #include <vkcv/Logger.hpp>
+#include <vkcv/scene/Scene.hpp>
+
 
-glm::mat4 arrayTo4x4Matrix(std::array<float,16> array){
-    glm::mat4 matrix;
-    for (int i = 0; i < 4; i++){
-        for (int j = 0; j < 4; j++){
-            matrix[i][j] = array[j * 4 + i];
-        }
-    }
-    return matrix;
-}
 
 int main(int argc, const char** argv) {
 	const char* applicationName = "First Scene";
@@ -32,8 +25,8 @@ int main(int argc, const char** argv) {
 	vkcv::camera::CameraManager cameraManager(window);
 	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-
-	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
+	
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(-8, 1, -0.5));
 	cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f);
 	
 	cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f);
@@ -46,66 +39,10 @@ int main(int argc, const char** argv) {
 		{},
 		{ "VK_KHR_swapchain" }
 	);
-
-	vkcv::asset::Scene scene;
-
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
-	int result = vkcv::asset::loadScene(path, scene);
-
-	if (result == 1) {
-		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	assert(!scene.vertexGroups.empty());
-	std::vector<std::vector<uint8_t>> vBuffers;
-	std::vector<std::vector<uint8_t>> iBuffers;
-
-	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
-	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-	std::vector<vkcv::asset::VertexAttribute> vAttributes;
-
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-
-		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
-		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
-
-		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
-
-		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-			});
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
-	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
-		vertexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			group.vertexBuffer.data.size()));
-		vertexBuffers.back().fill(group.vertexBuffer.data);
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
-	for (const auto& dataBuffer : iBuffers) {
-		indexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			dataBuffer.size()));
-		indexBuffers.back().fill(dataBuffer);
-	}
-
-	int vertexBufferIndex = 0;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
-			vAttributes.push_back(attribute);
-			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-		}
-		vertexBufferBindings.push_back(vBufferBindings);
-		vBufferBindings.clear();
-		vertexBufferIndex++;
-	}
+	
+	vkcv::scene::Scene scene = vkcv::scene::Scene::load(core, std::filesystem::path(
+			argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf"
+	));
 
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentOperation::STORE,
@@ -138,41 +75,8 @@ int main(int argc, const char** argv) {
 	}
 
 	const vkcv::VertexLayout sceneLayout(bindings);
-
-	uint32_t setID = 0;
-
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] };
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-	std::vector<vkcv::Image> sceneImages;
-	std::vector<vkcv::DescriptorSetHandle> descriptorSets;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
-
-		const auto& material = scene.materials[vertexGroup.materialIndex];
-
-		int baseColorIndex = material.baseColor;
-		if (baseColorIndex < 0) {
-			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
-			baseColorIndex = 0;
-		}
-
-		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
-
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
-		sceneImages.back().fill(sceneTexture.data.data());
-
-		vkcv::DescriptorWrites setWrites;
-		setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
-		setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
-		core.writeDescriptorSet(descriptorSets.back(), setWrites);
-	}
+	
+	const auto& material0 = scene.getMaterial(0);
 
 	const vkcv::PipelineConfig scenePipelineDefsinition{
 		sceneShaderProgram,
@@ -180,7 +84,7 @@ int main(int argc, const char** argv) {
 		UINT32_MAX,
 		scenePass,
 		{sceneLayout},
-		{ core.getDescriptorSet(descriptorSets[0]).layout },
+		{ core.getDescriptorSet(material0.getDescriptorSet()).layout },
 		true };
 	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition);
 	
@@ -192,26 +96,7 @@ int main(int argc, const char** argv) {
 	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-    std::vector<vkcv::DrawcallInfo> drawcalls;
-	for(int i = 0; i < scene.vertexGroups.size(); i++){
-        vkcv::Mesh renderMesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices);
-
-        vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSets[i]).vulkanHandle);
-
-	    drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage},1));
-	}
-
-	std::vector<glm::mat4> modelMatrices;
-	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
-	for (const auto &mesh : scene.meshes) {
-		const glm::mat4 m = arrayTo4x4Matrix(mesh.modelMatrix);
-		for (const auto &vertexGroupIndex : mesh.vertexGroups) {
-			modelMatrices[vertexGroupIndex] = m;
-		}
-	}
-	std::vector<glm::mat4> mvp;
-
+	
 	auto start = std::chrono::system_clock::now();
 	while (window.isWindowOpen()) {
         vkcv::Window::pollEvents();
@@ -236,25 +121,24 @@ int main(int argc, const char** argv) {
 		
 		start = end;
 		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-		glm::mat4 vp = cameraManager.getActiveCamera().getMVP();
-
-		mvp.clear();
-        for (const auto& m : modelMatrices) {
-            mvp.push_back(vp * m);
-        }
-
-		vkcv::PushConstantData pushConstantData((void*)mvp.data(), sizeof(glm::mat4));
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			scenePass,
-			scenePipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
+		auto recordMesh = [](const glm::mat4& MVP, const glm::mat4& M,
+							 vkcv::PushConstants &pushConstants,
+							 vkcv::DrawcallInfo& drawcallInfo) {
+			pushConstants.appendDrawcall(MVP);
+		};
+		
+		scene.recordDrawcalls(cmdStream,
+							  cameraManager.getActiveCamera(),
+							  scenePass,
+							  scenePipeline,
+							  sizeof(glm::mat4),
+							  recordMesh,
+							  renderTargets);
+		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
 		core.endFrame();
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 5bdd55a263f4d81d8f424c056d7d6c0b54ccb1ca..a69f9bc755f9cfa3a75d0a51dd242876aedabf79 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -27,57 +27,13 @@ int main(int argc, const char** argv) {
 		{},
 		{ "VK_KHR_swapchain" }
 	);
-	
-	vkcv::gui::GUI gui (core, window);
 
 	const auto& context = core.getContext();
-	const vk::Instance& instance = context.getInstance();
-	const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
-	const vk::Device& device = context.getDevice();
-
-	struct vec3 {
-		float x, y, z;
-	};
-
-	const size_t n = 5027;
-
-	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 };
-	}
 
-	testBuffer.fill(vec_data);
-
-	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
+	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, 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;
-	}
-
 	// an example attachment for passes that output to the window
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentOperation::STORE,
@@ -123,49 +79,9 @@ int main(int argc, const char** argv) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
-
-	// Compute Pipeline
-	vkcv::ShaderProgram computeShaderProgram{};
-	computeShaderProgram.addShader(vkcv::ShaderStage::COMPUTE, std::filesystem::path("shaders/comp.spv"));
-
-	// take care, assuming shader has exactly one descriptor set
-	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeShaderProgram.getReflectedDescriptors()[0]);
-
-	vkcv::PipelineHandle computePipeline = core.createComputePipeline(
-		computeShaderProgram, 
-		{ core.getDescriptorSet(computeDescriptorSet).layout });
-
-	struct ComputeTestBuffer {
-		float test1[10];
-		float test2[10];
-		float test3[10];
-	};
-
-	vkcv::Buffer computeTestBuffer = core.createBuffer<ComputeTestBuffer>(vkcv::BufferType::STORAGE, 1);
-
-	vkcv::DescriptorWrites computeDescriptorWrites;
-	computeDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, computeTestBuffer.getHandle()) };
-	core.writeDescriptorSet(computeDescriptorSet, computeDescriptorWrites);
-
-	/*
-	 * BufferHandle triangleVertices = core.createBuffer(vertices);
-	 * BufferHandle triangleIndices = core.createBuffer(indices);
-	 *
-	 * // triangle Model creation goes here
-	 *
-	 *
-	 * // attachment creation goes here
-	 * PassHandle trianglePass = core.CreatePass(presentationPass);
-	 *
-	 * // shader creation goes here
-	 * // material creation goes here
-	 *
-	 * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline);
-	 */
+	
 	auto start = std::chrono::system_clock::now();
 
-	vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle();
-
 	const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3);
 	vkcv::DrawcallInfo drawcall(renderMesh, {},1);
 
@@ -181,7 +97,7 @@ int main(int argc, const char** argv) {
 
 	while (window.isWindowOpen())
 	{
-        window.pollEvents();
+        vkcv::Window::pollEvents();
 
 		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
 		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
@@ -195,37 +111,21 @@ int main(int argc, const char** argv) {
 		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
-		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
+		vkcv::PushConstants pushConstants (sizeof(glm::mat4));
+		pushConstants.appendDrawcall(mvp);
+		
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
 			trianglePass,
 			trianglePipeline,
-			pushConstantData,
+			pushConstants,
 			{ drawcall },
 			{ swapchainInput });
 
-		const uint32_t dispatchSize[3] = { 2, 1, 1 };
-		const float theMeaningOfLife = 42;
-
-		core.recordComputeDispatchToCmdStream(
-			cmdStream,
-			computePipeline,
-			dispatchSize,
-			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(computeDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData((void*)&theMeaningOfLife, sizeof(theMeaningOfLife)));
-
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		
-		gui.beginGUI();
-		
-		ImGui::Begin("Hello world");
-		ImGui::Text("This is a test!");
-		ImGui::End();
-		
-		gui.endGUI();
 	    
 	    core.endFrame();
 	}
diff --git a/projects/particle_simulation/src/BloomAndFlares.cpp b/projects/particle_simulation/src/BloomAndFlares.cpp
index 23ace2bc35a2e421613718c62380f9161a408f70..98d53c2a1a2c08d40473858b47aacf34da30f7ed 100644
--- a/projects/particle_simulation/src/BloomAndFlares.cpp
+++ b/projects/particle_simulation/src/BloomAndFlares.cpp
@@ -104,7 +104,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre
             m_DownsamplePipe,
             initialDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 
     // downsample dispatches of blur buffer's mip maps
     float mipDispatchCountX = dispatchCountX;
@@ -139,7 +139,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre
                 m_DownsamplePipe,
                 mipDispatchCount,
                 {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0));
+                vkcv::PushConstants(0));
 
         // image barrier between mips
         p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
@@ -184,7 +184,7 @@ void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream
                 m_UpsamplePipe,
                 upsampleDispatchCount,
                 {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0)
+                vkcv::PushConstants(0)
         );
         // image barrier between mips
         p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
@@ -216,7 +216,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr
             m_LensFlarePipe,
             lensFeatureDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 }
 
 void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream,
@@ -249,7 +249,7 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea
             m_CompositePipe,
             compositeDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 }
 
 void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream,
diff --git a/projects/particle_simulation/src/Particle.cpp b/projects/particle_simulation/src/Particle.cpp
index 387728eb366430e4373282da785bbff47de17e7a..b80d063d382c9ae1cb63887388cce065b8289b63 100644
--- a/projects/particle_simulation/src/Particle.cpp
+++ b/projects/particle_simulation/src/Particle.cpp
@@ -3,16 +3,17 @@
 
 Particle::Particle(glm::vec3 position, glm::vec3 velocity, float lifeTime)
 : m_position(position),
-m_velocity(velocity),
-m_lifeTime(lifeTime),
-m_reset_velocity(velocity)
+  m_lifeTime(lifeTime),
+  m_velocity(velocity),
+  m_mass(1.0f),
+  m_reset_velocity(velocity)
 {}
 
 const glm::vec3& Particle::getPosition()const{
     return m_position;
 }
 
-const bool Particle::isAlive()const{
+bool Particle::isAlive()const{
     return m_lifeTime > 0.f;
 }
 
diff --git a/projects/particle_simulation/src/Particle.hpp b/projects/particle_simulation/src/Particle.hpp
index f374218fd8a08f1e1bf367bdc899a71c55ea1b78..73e7cbf517709ee03274cfd199081ade3f756545 100644
--- a/projects/particle_simulation/src/Particle.hpp
+++ b/projects/particle_simulation/src/Particle.hpp
@@ -17,7 +17,7 @@ public:
 
     void update( const float delta );
 
-    const bool isAlive()const;
+    bool isAlive()const;
 
     void setLifeTime( const float lifeTime );
 
@@ -28,7 +28,7 @@ private:
     glm::vec3 m_position;
     float m_lifeTime;
     glm::vec3 m_velocity;
-    float mass = 1.f;
+    float m_mass;
     glm::vec3 m_reset_velocity;
-    float padding_3 = 0.f;
+    float padding_3;
 };
diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp
index a22044f0d2588a43a5e7a0f6cba25d9c7460be9f..0d83644b866f5f89fb33c68f1d5a79fcee8c028a 100644
--- a/projects/particle_simulation/src/main.cpp
+++ b/projects/particle_simulation/src/main.cpp
@@ -58,12 +58,28 @@ int main(int argc, const char **argv) {
         return EXIT_FAILURE;
     }
 
-    // use space or use water
-    bool useSpace = true;
+    // use space or use water or gravity
+    std::string shaderPathCompute = "shaders/shader_space.comp";
+	std::string shaderPathFragment = "shaders/shader_space.frag";
+    
+    for (int i = 1; i < argc; i++) {
+    	if (strcmp(argv[i], "--space") == 0) {
+    		shaderPathCompute = "shaders/shader_space.comp";
+			shaderPathFragment = "shaders/shader_space.frag";
+    	} else
+		if (strcmp(argv[i], "--water") == 0) {
+			shaderPathCompute = "shaders/shader_water.comp";
+			shaderPathFragment = "shaders/shader_water.frag";
+		} else
+		if (strcmp(argv[i], "--gravity") == 0) {
+			shaderPathCompute = "shaders/shader_gravity.comp";
+			shaderPathFragment = "shaders/shader_space.frag";
+		}
+    }
 
     vkcv::shader::GLSLCompiler compiler;
     vkcv::ShaderProgram computeShaderProgram{};
-    compiler.compile(vkcv::ShaderStage::COMPUTE, useSpace ? "shaders/shader_space.comp" : "shaders/shader_water.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+    compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
         computeShaderProgram.addShader(shaderStage, path);
     });
 
@@ -81,7 +97,7 @@ int main(int argc, const char **argv) {
     compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
         particleShaderProgram.addShader(shaderStage, path);
     });
-    compiler.compile(vkcv::ShaderStage::FRAGMENT, useSpace ? "shaders/shader_space.frag" : "shaders/shader_water.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+    compiler.compile(vkcv::ShaderStage::FRAGMENT, shaderPathFragment, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
         particleShaderProgram.addShader(shaderStage, path);
     });
 
@@ -167,38 +183,16 @@ int main(int argc, const char **argv) {
     const vkcv::Mesh renderMesh({vertexBufferBindings}, particleIndexBuffer.getVulkanHandle(),
                                 particleIndexBuffer.getCount());
     vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
-    //vkcv::DrawcallInfo drawcalls(renderMesh, {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle)});
 
-    glm::vec2 pos = glm::vec2(0.f);
-    glm::vec3 spawnPosition = glm::vec3(0.f);
-    glm::vec4 tempPosition = glm::vec4(0.f);
+    auto pos = glm::vec2(0.f);
+    auto spawnPosition = glm::vec3(0.f);
 
     window.e_mouseMove.add([&](double offsetX, double offsetY) {
         pos = glm::vec2(static_cast<float>(offsetX), static_cast<float>(offsetY));
-//        std::cout << offsetX << " , " << offsetY << std::endl;
-        // borders are assumed to be 0.5
-        //pos = glm::vec2((pos.x -0.5f * static_cast<float>(window.getWidth()))/static_cast<float>(window.getWidth()), (pos.y -0.5f * static_cast<float>(window.getHeight()))/static_cast<float>(window.getHeight()));
-        //borders are assumed to be 1
         pos.x = (-2 * pos.x + static_cast<float>(window.getWidth())) / static_cast<float>(window.getWidth());
         pos.y = (-2 * pos.y + static_cast<float>(window.getHeight())) / static_cast<float>(window.getHeight());
-        glm::vec4 row1 = glm::row(cameraManager.getCamera(0).getView(), 0);
-        glm::vec4 row2 = glm::row(cameraManager.getCamera(0).getView(), 1);
-        glm::vec4 row3 = glm::row(cameraManager.getCamera(0).getView(), 2);
-        glm::vec4 camera_pos = glm::column(cameraManager.getCamera(0).getView(), 3);
-//        std::cout << "row1: " << row1.x << ", " << row1.y << ", " << row1.z << std::endl;
-//        std::cout << "row2: " << row2.x << ", " << row2.y << ", " << row2.z << std::endl;
-//        std::cout << "row3: " << row3.x << ", " << row3.y << ", " << row3.z << std::endl;
-//        std::cout << "camerapos: " << camera_pos.x << ", " << camera_pos.y << ", " << camera_pos.z << std::endl;
-//        std::cout << "camerapos: " << camera_pos.x << ", " << camera_pos.y << ", " << camera_pos.z << std::endl;
-        //glm::vec4 view_axis = glm::row(cameraManager.getCamera().getView(), 2);
-        // std::cout << "view_axis: " << view_axis.x << ", " << view_axis.y << ", " << view_axis.z << std::endl;
-        //std::cout << "Front: " << cameraManager.getCamera().getFront().x << ", " << cameraManager.getCamera().getFront().z << ", " << cameraManager.getCamera().getFront().z << std::endl;
-        glm::mat4 viewmat = cameraManager.getCamera(0).getView();
         spawnPosition = glm::vec3(pos.x, pos.y, 0.f);
-        tempPosition = glm::vec4(spawnPosition, 1.0f);
-        spawnPosition = glm::vec3(tempPosition.x, tempPosition.y, tempPosition.z);
         particleSystem.setRespawnPos(glm::vec3(-spawnPosition.x, spawnPosition.y, spawnPosition.z));
-//        std::cout << "respawn pos: " << spawnPosition.x << ", " << spawnPosition.y << ", " << spawnPosition.z << std::endl;
     });
 
     std::vector<glm::mat4> modelMatrices;
@@ -242,7 +236,7 @@ int main(int argc, const char **argv) {
     std::uniform_real_distribution<float> rdm = std::uniform_real_distribution<float>(0.95f, 1.05f);
     std::default_random_engine rdmEngine;
     while (window.isWindowOpen()) {
-        window.pollEvents();
+        vkcv::Window::pollEvents();
 
         uint32_t swapchainWidth, swapchainHeight;
         if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
@@ -255,35 +249,42 @@ int main(int argc, const char **argv) {
         auto end = std::chrono::system_clock::now();
         float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() );
         start = end;
-//        particleSystem.updateParticles(deltatime);
 
         cameraManager.update(deltatime);
 
         // split view and projection to allow for easy billboarding in shader
-        glm::mat4 renderingMatrices[2];
-        renderingMatrices[0] = cameraManager.getActiveCamera().getView();
-        renderingMatrices[1] = cameraManager.getActiveCamera().getProjection();
+        struct {
+			glm::mat4 view;
+			glm::mat4 projection;
+        } renderingMatrices;
+        
+        renderingMatrices.view = cameraManager.getActiveCamera().getView();
+        renderingMatrices.projection = cameraManager.getActiveCamera().getProjection();
 
         auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
         float random = rdm(rdmEngine);
         glm::vec2 pushData = glm::vec2(deltatime, random);
 
-        vkcv::PushConstantData pushConstantDataCompute( &pushData, sizeof(glm::vec2));
+        vkcv::PushConstants pushConstantsCompute (sizeof(glm::vec2));
+        pushConstantsCompute.appendDrawcall(pushData);
+        
         uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil(particleSystem.getParticles().size()/256.f)),1,1};
         core.recordComputeDispatchToCmdStream(cmdStream,
                                               computePipeline,
                                               computeDispatchCount,
                                               {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet).vulkanHandle)},
-                                              pushConstantDataCompute);
+											  pushConstantsCompute);
 
         core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
 
-        vkcv::PushConstantData pushConstantDataDraw((void *) &renderingMatrices[0], 2 * sizeof(glm::mat4));
+        vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices));
+        pushConstantsDraw.appendDrawcall(renderingMatrices);
+        
         core.recordDrawcallsToCmdStream(
                 cmdStream,
                 particlePass,
                 particlePipeline,
-                pushConstantDataDraw,
+				pushConstantsDraw,
                 {drawcalls},
                 { colorBuffer });
 
@@ -309,7 +310,7 @@ int main(int argc, const char **argv) {
             tonemappingPipe, 
             tonemappingDispatchCount, 
             {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptor).vulkanHandle) },
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 
         core.prepareSwapchainImageForPresent(cmdStream);
         core.submitCommandStream(cmdStream);
diff --git a/projects/voxelization/src/BloomAndFlares.cpp b/projects/voxelization/src/BloomAndFlares.cpp
index fac57735a6544c197f880f78e1f512382607d048..6cb02e9035daf7abebc047d26137d0ba973bb4f1 100644
--- a/projects/voxelization/src/BloomAndFlares.cpp
+++ b/projects/voxelization/src/BloomAndFlares.cpp
@@ -128,7 +128,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre
             m_DownsamplePipe,
             initialDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 
     // downsample dispatches of blur buffer's mip maps
     float mipDispatchCountX = dispatchCountX;
@@ -163,7 +163,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre
                 m_DownsamplePipe,
                 mipDispatchCount,
                 {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0));
+                vkcv::PushConstants(0));
 
         // image barrier between mips
         p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
@@ -208,7 +208,7 @@ void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream
                 m_UpsamplePipe,
                 upsampleDispatchCount,
                 {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0)
+                vkcv::PushConstants(0)
         );
         // image barrier between mips
         p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
@@ -243,7 +243,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr
             m_LensFlarePipe,
             lensFeatureDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
+            vkcv::PushConstants(0));
 
     // upsample dispatch
     p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle());
@@ -276,7 +276,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr
             m_UpsamplePipe,
             upsampleDispatchCount,
             { vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleLensFlareDescSets[i]).vulkanHandle) },
-            vkcv::PushConstantData(nullptr, 0)
+            vkcv::PushConstants(0)
         );
         // image barrier between mips
         p_Core->recordImageMemoryBarrier(cmdStream, m_LensFeatures.getHandle());
@@ -309,6 +309,9 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea
             static_cast<uint32_t>(glm::ceil(dispatchCountY)),
             1
     };
+	
+	vkcv::PushConstants pushConstants (sizeof(cameraForward));
+	pushConstants.appendDrawcall(cameraForward);
 
     // bloom composite dispatch
     p_Core->recordComputeDispatchToCmdStream(
@@ -316,7 +319,7 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea
             m_CompositePipe,
             compositeDispatchCount,
             {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)},
-            vkcv::PushConstantData((void*)&cameraForward, sizeof(cameraForward)));
+			pushConstants);
 }
 
 void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment, 
diff --git a/projects/voxelization/src/ShadowMapping.cpp b/projects/voxelization/src/ShadowMapping.cpp
index a330394b7bd7ff2a4b8c347bd79e676dbc70f846..32dd5457541f8f09f4d2711ea831e3c78de2303a 100644
--- a/projects/voxelization/src/ShadowMapping.cpp
+++ b/projects/voxelization/src/ShadowMapping.cpp
@@ -248,12 +248,13 @@ void ShadowMapping::recordShadowMapRendering(
 		voxelVolumeOffset,
 		voxelVolumeExtent);
 	m_lightInfoBuffer.fill({ lightInfo });
-
-	std::vector<glm::mat4> mvpLight;
+	
+	vkcv::PushConstants shadowPushConstants (sizeof(glm::mat4));
+	
 	for (const auto& m : modelMatrices) {
-		mvpLight.push_back(lightInfo.lightMatrix * m);
+		shadowPushConstants.appendDrawcall(lightInfo.lightMatrix * m);
 	}
-	const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+	
 
 	std::vector<vkcv::DrawcallInfo> drawcalls;
 	for (const auto& mesh : meshes) {
@@ -264,7 +265,7 @@ void ShadowMapping::recordShadowMapRendering(
 		cmdStream,
 		m_shadowMapPass,
 		m_shadowMapPipe,
-		shadowPushConstantData,
+		shadowPushConstants,
 		drawcalls,
 		{ m_shadowMapDepth.getHandle() });
 	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapDepth.getHandle());
@@ -276,6 +277,9 @@ void ShadowMapping::recordShadowMapRendering(
 	dispatchCount[2] = 1;
 
 	const uint32_t msaaSampleCount = msaaToSampleCount(msaa);
+	
+	vkcv::PushConstants msaaPushConstants (sizeof(msaaSampleCount));
+	msaaPushConstants.appendDrawcall(msaaSampleCount);
 
 	m_corePtr->prepareImageForStorage(cmdStream, m_shadowMap.getHandle());
 	m_corePtr->recordComputeDispatchToCmdStream(
@@ -283,7 +287,7 @@ void ShadowMapping::recordShadowMapRendering(
 		m_depthToMomentsPipe,
 		dispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_depthToMomentsDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData((void*)&msaaSampleCount, sizeof(msaaSampleCount)));
+		msaaPushConstants);
 	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle());
 
 	// blur X
@@ -293,7 +297,7 @@ void ShadowMapping::recordShadowMapRendering(
 		m_shadowBlurXPipe,
 		dispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_shadowBlurXDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(nullptr, 0));
+		vkcv::PushConstants(0));
 	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapIntermediate.getHandle());
 
 	// blur Y
@@ -303,7 +307,7 @@ void ShadowMapping::recordShadowMapRendering(
 		m_shadowBlurYPipe,
 		dispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_shadowBlurYDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(nullptr, 0));
+		vkcv::PushConstants(0));
 	m_shadowMap.recordMipChainGeneration(cmdStream);
 	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle());
 }
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index c117b4b9e6b896fbf51aae83343f30281061be9f..bbf161ddeb0899a1ce61279b4c476fb19cb906d7 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -232,34 +232,36 @@ void Voxelization::voxelizeMeshes(
 
 	const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -m_voxelInfo.offset);
 	const glm::mat4 voxelizationViewProjection = voxelizationProjection * voxelizationView;
-
-	std::vector<std::array<glm::mat4, 2>> voxelizationMatrices;
+	
+	vkcv::PushConstants voxelizationPushConstants (2 * sizeof(glm::mat4));
+	
 	for (const auto& m : modelMatrices) {
-		voxelizationMatrices.push_back({ voxelizationViewProjection * m, m });
+		voxelizationPushConstants.appendDrawcall(std::array<glm::mat4, 2>{ voxelizationViewProjection * m, m });
 	}
 
-	const vkcv::PushConstantData voxelizationPushConstantData((void*)voxelizationMatrices.data(), 2 * sizeof(glm::mat4));
-
 	// reset voxels
 	const uint32_t resetVoxelGroupSize = 64;
 	uint32_t resetVoxelDispatchCount[3];
 	resetVoxelDispatchCount[0] = glm::ceil(voxelCount / float(resetVoxelGroupSize));
 	resetVoxelDispatchCount[1] = 1;
 	resetVoxelDispatchCount[2] = 1;
+	
+	vkcv::PushConstants voxelCountPushConstants (sizeof(voxelCount));
+	voxelCountPushConstants.appendDrawcall(voxelCount);
 
 	m_corePtr->recordComputeDispatchToCmdStream(
 		cmdStream,
 		m_voxelResetPipe,
 		resetVoxelDispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(&voxelCount, sizeof(voxelCount)));
+		voxelCountPushConstants);
 	m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle());
 
 	// voxelization
 	std::vector<vkcv::DrawcallInfo> drawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
+	for (size_t i = 0; i < meshes.size(); i++) {
 		drawcalls.push_back(vkcv::DrawcallInfo(
-			meshes[i], 
+			meshes[i],
 			{ 
 				vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).vulkanHandle),
 				vkcv::DescriptorSetUsage(1, m_corePtr->getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) 
@@ -271,7 +273,7 @@ void Voxelization::voxelizeMeshes(
 		cmdStream,
 		m_voxelizationPass,
 		m_voxelizationPipe,
-		voxelizationPushConstantData,
+		voxelizationPushConstants,
 		drawcalls,
 		{ m_dummyRenderTarget.getHandle() });
 
@@ -287,7 +289,7 @@ void Voxelization::voxelizeMeshes(
 		m_bufferToImagePipe,
 		bufferToImageDispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(nullptr, 0));
+		vkcv::PushConstants(0));
 
 	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImageIntermediate.getHandle());
 
@@ -303,7 +305,7 @@ void Voxelization::voxelizeMeshes(
 		m_secondaryBouncePipe,
 		bufferToImageDispatchCount,
 		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(nullptr, 0));
+		vkcv::PushConstants(0));
 	m_voxelImage.recordMipChainGeneration(cmdStream);
 
 	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
@@ -319,7 +321,8 @@ void Voxelization::renderVoxelVisualisation(
 	const std::vector<vkcv::ImageHandle>&   renderTargets,
 	uint32_t                                mipLevel) {
 
-	const vkcv::PushConstantData voxelVisualisationPushConstantData((void*)&viewProjectin, sizeof(glm::mat4));
+	vkcv::PushConstants voxelVisualisationPushConstants (sizeof(glm::mat4));
+	voxelVisualisationPushConstants.appendDrawcall(viewProjectin);
 
 	mipLevel = std::clamp(mipLevel, (uint32_t)0, m_voxelImage.getMipCount()-1);
 
@@ -342,7 +345,7 @@ void Voxelization::renderVoxelVisualisation(
 		cmdStream,
 		m_visualisationPass,
 		m_visualisationPipe,
-		voxelVisualisationPushConstantData,
+		voxelVisualisationPushConstants,
 		{ drawcall },
 		renderTargets);
 }
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index edc50c554b6c73bd2f06914eba6dd7adf9e43483..ca9951490e57b4b6afa3bbee986a55342a40582e 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -111,7 +111,7 @@ int main(int argc, const char** argv) {
 	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
 	std::vector<vkcv::asset::VertexAttribute> vAttributes;
 
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+	for (size_t i = 0; i < scene.vertexGroups.size(); i++) {
 
 		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
 		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
@@ -452,14 +452,14 @@ int main(int argc, const char** argv) {
 
 	// prepare meshes
 	std::vector<vkcv::Mesh> meshes;
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+	for (size_t i = 0; i < scene.vertexGroups.size(); i++) {
 		vkcv::Mesh mesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices);
 		meshes.push_back(mesh);
 	}
 
 	std::vector<vkcv::DrawcallInfo> drawcalls;
 	std::vector<vkcv::DrawcallInfo> prepassDrawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
+	for (size_t i = 0; i < meshes.size(); i++) {
 
 		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
 			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
@@ -618,29 +618,32 @@ int main(int argc, const char** argv) {
 
 		// depth prepass
 		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
+		
+		vkcv::PushConstants prepassPushConstants (sizeof(glm::mat4));
+		
 		std::vector<glm::mat4> prepassMatrices;
 		for (const auto& m : modelMatrices) {
-			prepassMatrices.push_back(viewProjectionCamera * m);
+			prepassPushConstants.appendDrawcall(viewProjectionCamera * m);
 		}
 
-		const vkcv::PushConstantData            prepassPushConstantData((void*)prepassMatrices.data(), sizeof(glm::mat4));
+		
 		const std::vector<vkcv::ImageHandle>    prepassRenderTargets = { depthBuffer };
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
 			prepassPass,
 			prepassPipeline,
-			prepassPushConstantData,
+			prepassPushConstants,
 			prepassDrawcalls,
 			prepassRenderTargets);
 
 		core.recordImageMemoryBarrier(cmdStream, depthBuffer);
-
+		
+		vkcv::PushConstants pushConstants (2 * sizeof(glm::mat4));
+		
 		// main pass
-		std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
 		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ viewProjectionCamera * m, m });
 		}
 
 		VolumetricSettings volumeSettings;
@@ -648,28 +651,30 @@ int main(int argc, const char** argv) {
 		volumeSettings.absorptionCoefficient    = absorptionColor * absorptionDensity;
 		volumeSettings.ambientLight             = volumetricAmbient;
 		volumetricSettingsBuffer.fill({ volumeSettings });
-
-		const vkcv::PushConstantData            pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		
 		const std::vector<vkcv::ImageHandle>    renderTargets = { colorBuffer, depthBuffer };
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
 			forwardPass,
 			forwardPipeline,
-			pushConstantData,
+			pushConstants,
 			drawcalls,
 			renderTargets);
 
 		if (renderVoxelVis) {
 			voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip);
 		}
+		
+		vkcv::PushConstants skySettingsPushConstants (sizeof(skySettings));
+		skySettingsPushConstants.appendDrawcall(skySettings);
 
 		// sky
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
 			skyPass,
 			skyPipe,
-			vkcv::PushConstantData((void*)&skySettings, sizeof(skySettings)),
+			skySettingsPushConstants,
 			{ vkcv::DrawcallInfo(vkcv::Mesh({}, nullptr, 3), {}) },
 			renderTargets);
 
@@ -692,7 +697,7 @@ int main(int argc, const char** argv) {
 					resolvePipeline,
 					fulsscreenDispatchCount,
 					{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(resolveDescriptorSet).vulkanHandle) },
-					vkcv::PushConstantData(nullptr, 0));
+					vkcv::PushConstants(0));
 
 				core.recordImageMemoryBarrier(cmdStream, resolvedColorBuffer);
 			}
@@ -710,12 +715,15 @@ int main(int argc, const char** argv) {
 		auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime);
 		float timeF         = static_cast<float>(timeSinceStart.count()) * 0.01;
 
+		vkcv::PushConstants timePushConstants (sizeof(timeF));
+		timePushConstants.appendDrawcall(timeF);
+		
 		core.recordComputeDispatchToCmdStream(
 			cmdStream, 
 			tonemappingPipeline, 
 			fulsscreenDispatchCount,
 			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData(&timeF, sizeof(timeF)));
+			timePushConstants);
 
 		// present and end
 		core.prepareSwapchainImageForPresent(cmdStream);
diff --git a/src/vkcv/CommandStreamManager.cpp b/src/vkcv/CommandStreamManager.cpp
index 5a5b359b912d9cef36e0b03379d7f0f6f0951381..52b73213dbc5837f6be4a2aa25c28615dccf5969 100644
--- a/src/vkcv/CommandStreamManager.cpp
+++ b/src/vkcv/CommandStreamManager.cpp
@@ -32,11 +32,10 @@ namespace vkcv {
 
 		// find unused stream
 		int unusedStreamIndex = -1;
-		for (int i = 0; i < m_commandStreams.size(); i++) {
+		for (size_t i = 0; i < m_commandStreams.size(); i++) {
 			if (m_commandStreams[i].cmdBuffer) {
 				// still in use
-			}
-			else {
+			} else {
 				unusedStreamIndex = i;
 				break;
 			}
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 352a1cf62eabe55ce1bbf2f53a6b5a4bd6e91753..a66c1e6220261679a85241fff42de08a57428d4c 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -71,7 +71,6 @@ namespace vkcv
 
         const auto& queueManager = context.getQueueManager();
         
-		const int						graphicQueueFamilyIndex	= queueManager.getGraphicsQueues()[0].familyIndex;
 		const std::unordered_set<int>	queueFamilySet			= generateQueueFamilyIndexSet(queueManager);
 		const auto						commandResources		= createCommandResources(context.getDevice(), queueFamilySet);
 		const auto						defaultSyncResources	= createSyncResources(context.getDevice());
@@ -163,9 +162,9 @@ namespace vkcv
 					nullptr,
 					&imageIndex, {}
 			);
-		} catch (vk::OutOfDateKHRError e) {
+		} catch (const vk::OutOfDateKHRError& e) {
 			result = vk::Result::eErrorOutOfDateKHR;
-		} catch (vk::DeviceLostError e) {
+		} catch (const vk::DeviceLostError& e) {
 			result = vk::Result::eErrorDeviceLost;
 		}
 		
@@ -232,7 +231,7 @@ namespace vkcv
 		const CommandStreamHandle       cmdStreamHandle,
 		const PassHandle                renderpassHandle, 
 		const PipelineHandle            pipelineHandle, 
-        const PushConstantData          &pushConstantData,
+        const PushConstants             &pushConstants,
         const std::vector<DrawcallInfo> &drawcalls,
 		const std::vector<ImageHandle>  &renderTargets) {
 
@@ -268,7 +267,7 @@ namespace vkcv
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
 		std::vector<vk::ImageView> attachmentsViews;
-		for (const ImageHandle handle : renderTargets) {
+		for (const ImageHandle& handle : renderTargets) {
 			vk::ImageView targetHandle;
 			const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
 
@@ -305,8 +304,6 @@ namespace vkcv
 
         vk::Rect2D dynamicScissor({0, 0}, {width, height});
 
-		auto &bufferManager = m_BufferManager;
-
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
@@ -338,14 +335,13 @@ namespace vkcv
             cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
             const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
-            if(pipeConfig.m_UseDynamicViewport)
-            {
+            if (pipeConfig.m_UseDynamicViewport) {
                 cmdBuffer.setViewport(0, 1, &dynamicViewport);
                 cmdBuffer.setScissor(0, 1, &dynamicScissor);
             }
 
-            for (int i = 0; i < drawcalls.size(); i++) {
-                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
+            for (size_t i = 0; i < drawcalls.size(); i++) {
+                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstants, i);
             }
 
             cmdBuffer.endRenderPass();
@@ -364,7 +360,7 @@ namespace vkcv
 		PipelineHandle computePipeline,
 		const uint32_t dispatchCount[3],
 		const std::vector<DescriptorSetUsage>& descriptorSetUsages,
-		const PushConstantData& pushConstantData) {
+		const PushConstants& pushConstants) {
 
 		auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) {
 
@@ -379,13 +375,13 @@ namespace vkcv
 					{ usage.vulkanHandle },
 					{});
 			}
-			if (pushConstantData.sizePerDrawcall > 0) {
+			if (pushConstants.getSizePerDrawcall() > 0) {
 				cmdBuffer.pushConstants(
 					pipelineLayout,
 					vk::ShaderStageFlagBits::eCompute,
 					0,
-					pushConstantData.sizePerDrawcall,
-					pushConstantData.data);
+					pushConstants.getSizePerDrawcall(),
+					pushConstants.getData());
 			}
 			cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]);
 		};
@@ -417,9 +413,9 @@ namespace vkcv
 		
 		try {
 			result = queueManager.getPresentQueue().handle.presentKHR(presentInfo);
-		} catch (vk::OutOfDateKHRError e) {
+		} catch (const vk::OutOfDateKHRError& e) {
 			result = vk::Result::eErrorOutOfDateKHR;
-		} catch (vk::DeviceLostError e) {
+		} catch (const vk::DeviceLostError& e) {
 			result = vk::Result::eErrorDeviceLost;
 		}
 		
@@ -463,8 +459,6 @@ namespace vkcv
 	}
 
 	CommandStreamHandle Core::createCommandStream(QueueType queueType) {
-
-		const vk::Device&       device  = m_Context.getDevice();
 		const vkcv::Queue       queue   = getQueueForSubmit(queueType, m_Context.getQueueManager());
 		const vk::CommandPool   cmdPool = chooseCmdPool(queue, m_CommandResources);
 
@@ -522,12 +516,12 @@ namespace vkcv
 			multisampling);
 	}
 
-	const uint32_t Core::getImageWidth(ImageHandle imageHandle)
+	uint32_t Core::getImageWidth(ImageHandle imageHandle)
 	{
 		return m_ImageManager->getImageWidth(imageHandle);
 	}
 
-	const uint32_t Core::getImageHeight(ImageHandle imageHandle)
+	uint32_t Core::getImageHeight(ImageHandle imageHandle)
 	{
 		return m_ImageManager->getImageHeight(imageHandle);
 	}
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
index 8e565a766cd407dc33c0291d3d07b01d6d3066e7..07ca97b5ade9b69eed724000d9c7b388818d6725 100644
--- a/src/vkcv/DescriptorManager.cpp
+++ b/src/vkcv/DescriptorManager.cpp
@@ -16,7 +16,8 @@ namespace vkcv
 													vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
 													vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000) };
 
-		m_PoolInfo = vk::DescriptorPoolCreateInfo({},
+		m_PoolInfo = vk::DescriptorPoolCreateInfo(
+				vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
 			1000,
 			static_cast<uint32_t>(m_PoolSizes.size()),
 			m_PoolSizes.data());
@@ -29,9 +30,13 @@ namespace vkcv
         for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) {
 			destroyDescriptorSetById(id);
         }
+        
 		m_DescriptorSets.clear();
+  
 		for (const auto &pool : m_Pools) {
-			m_Device.destroy(pool);
+			if (pool) {
+				m_Device.destroy(pool);
+			}
 		}
     }
 
@@ -40,12 +45,12 @@ namespace vkcv
         std::vector<vk::DescriptorSetLayoutBinding> setBindings = {};
 
         //create each set's binding
-        for (uint32_t i = 0; i < bindings.size(); i++) {
+        for (auto binding : bindings) {
             vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding(
-                bindings[i].bindingID,
-                convertDescriptorTypeFlag(bindings[i].descriptorType),
-                bindings[i].descriptorCount,
-                convertShaderStageFlag(bindings[i].shaderStage));
+                binding.bindingID,
+                convertDescriptorTypeFlag(binding.descriptorType),
+                binding.descriptorCount,
+                convertShaderStageFlag(binding.shaderStage));
             setBindings.push_back(descriptorSetLayoutBinding);
         }
 
@@ -53,8 +58,7 @@ namespace vkcv
 
         //create the descriptor set's layout from the bindings gathered above
         vk::DescriptorSetLayoutCreateInfo layoutInfo({}, setBindings);
-        if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess)
-        {
+        if (m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess) {
 			vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
             return DescriptorSetHandle();
         };
@@ -70,6 +74,7 @@ namespace vkcv
 				allocInfo.setDescriptorPool(m_Pools.back());
 				result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle);
 			}
+			
 			if (result != vk::Result::eSuccess) {
 				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
 						 vk::to_string(result).c_str());
@@ -78,6 +83,8 @@ namespace vkcv
 				return DescriptorSetHandle();
 			}
         };
+	
+		set.poolIndex = (m_Pools.size() - 1);
 
         const uint64_t id = m_DescriptorSets.size();
 
@@ -277,17 +284,22 @@ namespace vkcv
 			m_Device.destroyDescriptorSetLayout(set.layout);
 			set.layout = nullptr;
 		}
-		// FIXME: descriptor set itself not destroyed
+		
+		if (set.vulkanHandle) {
+			m_Device.freeDescriptorSets(m_Pools[set.poolIndex], 1, &(set.vulkanHandle));
+			set.vulkanHandle = nullptr;
+		}
 	}
 
 	vk::DescriptorPool DescriptorManager::allocateDescriptorPool() {
 		vk::DescriptorPool pool;
-		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess)
-		{
+		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess) {
 			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
 			pool = nullptr;
-		};
-		m_Pools.push_back(pool);
+		} else {
+			m_Pools.push_back(pool);
+		}
+		
 		return pool;
 	}
 
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
index e6ea18588c251b5e49f454618a5ac9962cc8a264..32ed00e98f7ef72f0c391f61924444c26844869b 100644
--- a/src/vkcv/DrawcallRecording.cpp
+++ b/src/vkcv/DrawcallRecording.cpp
@@ -6,7 +6,7 @@ namespace vkcv {
         const DrawcallInfo      &drawcall,
         vk::CommandBuffer       cmdBuffer,
         vk::PipelineLayout      pipelineLayout,
-        const PushConstantData  &pushConstantData,
+        const PushConstants     &pushConstants,
         const size_t            drawcallIndex) {
 
         for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) {
@@ -23,17 +23,13 @@ namespace vkcv {
                 nullptr);
         }
 
-        const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall;
-        // char* cast because void* does not support pointer arithmetic
-        const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data;
-
-        if (pushConstantData.data && pushConstantData.sizePerDrawcall > 0) {
+        if (pushConstants.getSizePerDrawcall() > 0) {
             cmdBuffer.pushConstants(
                 pipelineLayout,
                 vk::ShaderStageFlagBits::eAll,
                 0,
-                pushConstantData.sizePerDrawcall,
-                drawcallPushConstantData);
+				pushConstants.getSizePerDrawcall(),
+                pushConstants.getDrawcallData(drawcallIndex));
         }
 
         if (drawcall.mesh.indexBuffer) {
diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp
index 020489418c8e2db6ce2062d6fd20f06f90a05c37..65fc02dedeba39953c173103efe9b228f49e5d7f 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -1,5 +1,7 @@
 #include "vkcv/Handles.hpp"
 
+#include <iostream>
+
 namespace vkcv {
 	
 	Handle::Handle() :
@@ -11,7 +13,7 @@ namespace vkcv {
 	{}
 	
 	Handle::~Handle() {
-		if ((m_rc) && (--(*m_rc) == 0)) {
+		if ((m_rc) && (*m_rc > 0) && (--(*m_rc) == 0)) {
 			if (m_destroy) {
 				m_destroy(m_id);
 			}
@@ -82,9 +84,9 @@ namespace vkcv {
 	
 	std::ostream& operator << (std::ostream& out, const Handle& handle) {
 		if (handle) {
-			return out << "[Handle: " << handle.getId() << ":" << handle.getRC() << "]";
+			return out << "[" << typeid(handle).name() << ": " << handle.getId() << ":" << handle.getRC() << "]";
 		} else {
-			return out << "[Handle: none]";
+			return out << "[" << typeid(handle).name() << ": none]";
 		}
 	}
 	
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index f8d94b734599cbf1f55aad7b590ab4796501d951..15a2fc5240176742f50141407a3c72b531757ee9 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -56,7 +56,7 @@ namespace vkcv{
 		m_manager->switchImageLayoutImmediate(m_handle, newLayout);
 	}
 
-	vkcv::ImageHandle Image::getHandle() const {
+	const vkcv::ImageHandle& Image::getHandle() const {
 		return m_handle;
 	}
 
@@ -64,7 +64,7 @@ namespace vkcv{
 		return m_manager->getImageMipCount(m_handle);
 	}
 
-	void Image::fill(void *data, size_t size) {
+	void Image::fill(const void *data, size_t size) {
 		m_manager->fillImage(m_handle, data, size);
 	}
 
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index ae554e6babdd2b2f42c352515c02a34e45182fec..ba96cf8ff1be1988dbaf3f9cb01bdaa96c84ac0b 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -67,7 +67,8 @@ namespace vkcv {
 		for (uint64_t id = 0; id < m_images.size(); id++) {
 			destroyImageById(id);
 		}
-		for (const auto swapchainImage : m_swapchainImages) {
+		
+		for (const auto& swapchainImage : m_swapchainImages) {
 			for (const auto view : swapchainImage.m_viewPerMip) {
 				m_core->getContext().getDevice().destroy(view);
 			}
@@ -196,7 +197,7 @@ namespace vkcv {
 		}
 		
 		std::vector<vk::ImageView> views;
-		for (int mip = 0; mip < mipCount; mip++) {
+		for (uint32_t mip = 0; mip < mipCount; mip++) {
 			const vk::ImageViewCreateInfo imageViewCreateInfo(
 				{},
 				image,
@@ -369,7 +370,7 @@ namespace vkcv {
 		}
 	}
 	
-	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
+	void ImageManager::fillImage(const ImageHandle& handle, const void* data, size_t size)
 	{
 		const uint64_t id = handle.getId();
 		
@@ -504,9 +505,6 @@ namespace vkcv {
 	}
 
 	void ImageManager::generateImageMipChainImmediate(const ImageHandle& handle) {
-
-		const auto& device = m_core->getContext().getDevice();
-
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 
@@ -655,7 +653,6 @@ namespace vkcv {
 
 	uint32_t ImageManager::getImageMipCount(const ImageHandle& handle) const {
 		const uint64_t id = handle.getId();
-		const bool isSwapchainFormat = handle.isSwapchainImage();
 
 		if (handle.isSwapchainImage()) {
 			return 1;
@@ -685,7 +682,7 @@ namespace vkcv {
 
 		assert(images.size() == views.size());
 		m_swapchainImages.clear();
-		for (int i = 0; i < images.size(); i++) {
+		for (size_t i = 0; i < images.size(); i++) {
 			m_swapchainImages.push_back(Image(images[i], nullptr, { views[i] }, width, height, 1, format, 1));
 		}
 	}
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index 1d8ce207b645e30cee291816eac3c934ed40e92a..9edd747141305a8a795a2ec8ada04f46e96c9852 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -103,7 +103,7 @@ namespace vkcv {
 			const ImageHandle& handle,
 			vk::CommandBuffer cmdBuffer);
 
-		void fillImage(const ImageHandle& handle, void* data, size_t size);
+		void fillImage(const ImageHandle& handle, const void* data, size_t size);
 		void generateImageMipChainImmediate(const ImageHandle& handle);
 		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream, const ImageHandle& handle);
 		void recordMSAAResolve(vk::CommandBuffer cmdBuffer, ImageHandle src, ImageHandle dst);
diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp
index df6c74cccf6c4652adc6a4c78802f282ea6ae293..b4891c6be387b817b87f059f4155f5708d4f4710 100644
--- a/src/vkcv/QueueManager.cpp
+++ b/src/vkcv/QueueManager.cpp
@@ -51,7 +51,7 @@ namespace vkcv {
         }
         //resort flags with heighest priority before allocating the queues
         std::vector<vk::QueueFlagBits> newFlags;
-        for(int i = 0; i < prios.size(); i++) {
+        for(size_t i = 0; i < prios.size(); i++) {
             auto minElem = std::min_element(prios.begin(), prios.end());
             int index = minElem - prios.begin();
             newFlags.push_back(queueFlags[index]);
@@ -79,7 +79,7 @@ namespace vkcv {
             switch (qFlag) {
                 case vk::QueueFlagBits::eGraphics:
                     found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                    for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                         if (queueFamilyStatus[i][0] > 0) {
                             queuePairsGraphics.push_back(std::pair(i, initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]));
                             queueFamilyStatus[i][0]--;
@@ -89,7 +89,7 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                        for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                             if (initialQueueFamilyStatus[i][0] > 0) {
                                 queuePairsGraphics.push_back(std::pair(i, 0));
                                 found = true;
@@ -101,7 +101,7 @@ namespace vkcv {
                     break;
                 case vk::QueueFlagBits::eCompute:
                     found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                    for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                         if (queueFamilyStatus[i][1] > 0) {
                             queuePairsCompute.push_back(std::pair(i, initialQueueFamilyStatus[i][1] - queueFamilyStatus[i][1]));
                             queueFamilyStatus[i][0]--;
@@ -111,7 +111,7 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                        for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                             if (initialQueueFamilyStatus[i][1] > 0) {
                                 queuePairsCompute.push_back(std::pair(i, 0));
                                 found = true;
@@ -123,7 +123,7 @@ namespace vkcv {
                     break;
                 case vk::QueueFlagBits::eTransfer:
                     found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                    for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                         if (queueFamilyStatus[i][2] > 0) {
                             queuePairsTransfer.push_back(std::pair(i, initialQueueFamilyStatus[i][2] - queueFamilyStatus[i][2]));
                             queueFamilyStatus[i][0]--;
@@ -133,7 +133,7 @@ namespace vkcv {
                         }
                     }
                     if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
+                        for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
                             if (initialQueueFamilyStatus[i][2] > 0) {
                                 queuePairsTransfer.push_back(std::pair(i, 0));
                                 found = true;
@@ -149,7 +149,7 @@ namespace vkcv {
         }
 
         // create all requested queues
-        for (int i = 0; i < qFamilyProperties.size(); i++) {
+        for (size_t i = 0; i < qFamilyProperties.size(); i++) {
             uint32_t create = std::abs(initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]);
             if (create > 0) {
                 vk::DeviceQueueCreateInfo qCreateInfo(
diff --git a/src/vkcv/Swapchain.cpp b/src/vkcv/Swapchain.cpp
index 94e7301d66bfcc513434ef6d22520d1b95f98161..d0aa26db9c661ea40caf06349a72cc9188e791a9 100644
--- a/src/vkcv/Swapchain.cpp
+++ b/src/vkcv/Swapchain.cpp
@@ -100,18 +100,14 @@ namespace vkcv
      * @return available Format
      */
     vk::SurfaceFormatKHR chooseSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        uint32_t formatCount;
-        physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, nullptr);
-        std::vector<vk::SurfaceFormatKHR> availableFormats(formatCount);
-        if (physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, &availableFormats[0]) != vk::Result::eSuccess) {
-            throw std::runtime_error("Failed to get surface formats");
-        }
+        std::vector<vk::SurfaceFormatKHR> availableFormats = physicalDevice.getSurfaceFormatsKHR(surface);
 
         for (const auto& availableFormat : availableFormats) {
             if (availableFormat.format == vk::Format::eB8G8R8A8Unorm  && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
                 return availableFormat;
             }
         }
+        
         return availableFormats[0];
     }
 
@@ -122,12 +118,7 @@ namespace vkcv
      * @return available PresentationMode
      */
     vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        uint32_t modeCount;
-        physicalDevice.getSurfacePresentModesKHR( surface, &modeCount, nullptr );
-        std::vector<vk::PresentModeKHR> availablePresentModes(modeCount);
-        if (physicalDevice.getSurfacePresentModesKHR(surface, &modeCount, &availablePresentModes[0]) != vk::Result::eSuccess) {
-            throw std::runtime_error("Failed to get presentation modes");
-        }
+        std::vector<vk::PresentModeKHR> availablePresentModes = physicalDevice.getSurfacePresentModesKHR(surface);
 
         for (const auto& availablePresentMode : availablePresentModes) {
             if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
@@ -145,12 +136,11 @@ namespace vkcv
      * @return available ImageCount
      */
     uint32_t chooseImageCount(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        vk::SurfaceCapabilitiesKHR surfaceCapabilities;
-        if(physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities) != vk::Result::eSuccess){
-            throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface.");
-        }
-
-        uint32_t imageCount = surfaceCapabilities.minImageCount + 1;    // minImageCount should always be at least 2; set to 3 for triple buffering
+        vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface);
+	
+		// minImageCount should always be at least 2; set to 3 for triple buffering
+        uint32_t imageCount = surfaceCapabilities.minImageCount + 1;
+        
         // check if requested image count is supported
         if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) {
             imageCount = surfaceCapabilities.maxImageCount;
@@ -215,8 +205,9 @@ namespace vkcv
     }
     
     void Swapchain::updateSwapchain(const Context &context, const Window &window) {
-    	if (!m_RecreationRequired.exchange(false))
-    		return;
+    	if (!m_RecreationRequired.exchange(false)) {
+			return;
+		}
     	
 		vk::SwapchainKHR oldSwapchain = m_Swapchain;
 		vk::Extent2D extent2D = chooseExtent(context.getPhysicalDevice(), m_Surface.handle, window);
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index ea72582d67d5350e5fbf3f3c0fa2aae2ba407b0e..025cb388c6880cc8132b454c799d39e2b530ceb3 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -150,11 +150,15 @@ namespace vkcv {
     }
 
     void Window::onGamepadEvent(int gamepadIndex) {
-        int activeWindowIndex = std::find_if(s_Windows.begin(),
-                                             s_Windows.end(),
-                                             [](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);})
-                                - s_Windows.begin();
-        activeWindowIndex *= (activeWindowIndex < s_Windows.size());    // fixes index getting out of bounds (e.g. if there is no focused window)
+        size_t activeWindowIndex = std::find_if(
+        		s_Windows.begin(),
+        		s_Windows.end(),
+        		[](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);}
+		) - s_Windows.begin();
+	
+		// fixes index getting out of bounds (e.g. if there is no focused window)
+        activeWindowIndex *= (activeWindowIndex < s_Windows.size());
+        
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(s_Windows[activeWindowIndex]));
 
         if (window != nullptr) {