diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp
index a390a2ff3be7f84b0c12f065398f9e40a42017e0..9eb80d70862a79a01593e6fe4c3aabe98d253ac8 100644
--- a/include/vkcv/BufferManager.hpp
+++ b/include/vkcv/BufferManager.hpp
@@ -132,6 +132,9 @@ namespace vkcv
 		 */
 		void unmapBuffer(const BufferHandle& handle);
 		
+		void recordBufferMemoryBarrier(
+			const BufferHandle& handle,
+			vk::CommandBuffer cmdBuffer);
 	};
 	
 }
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index bf9514d5ba4c9d5dbf8d41be2a489dae826886a8..dab18892b892aff9564a6d86b9252789ea3c2b03 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -63,9 +63,6 @@ namespace vkcv
         Context m_Context;
 
         Swapchain                       m_swapchain;
-        std::vector<vk::ImageView>      m_swapchainImageViews;
-        std::vector<vk::Image>          m_swapchainImages;
-		std::vector<vk::ImageLayout>    m_swapchainImageLayouts;
         Window&                   		m_window;
 
         std::unique_ptr<PassManager>            m_PassManager;
@@ -79,12 +76,10 @@ namespace vkcv
 		CommandResources    m_CommandResources;
 		SyncResources       m_SyncResources;
 		uint32_t            m_currentSwapchainImageIndex;
-	
-		event_handle<int,int> e_resizeHandle;
 
-        static std::vector<vk::ImageView> createImageViews( Context &context, Swapchain& swapChain);
+		event_handle<int,int> e_resizeHandle;
 
-		void recordSwapchainImageLayoutTransition(vk::CommandBuffer cmdBuffer, vk::ImageLayout newLayout);
+        static std::vector<vk::ImageView> createSwapchainImageViews( Context &context, Swapchain& swapChain);
 
     public:
         /**
@@ -220,7 +215,7 @@ namespace vkcv
          * @return Image-Object
          */
         [[nodiscard]]
-        Image createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1);
+        Image createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1, bool supportStorage = false, bool supportColorAttachment = false);
 
         /** TODO:
          *   @param setDescriptions
@@ -281,6 +276,9 @@ namespace vkcv
 		void submitCommandStream(const CommandStreamHandle handle);
 		void prepareSwapchainImageForPresent(const CommandStreamHandle handle);
 		void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image);
+		void recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer);
 		
 		const vk::ImageView& getSwapchainImageView() const;
 		
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index 840650a1d6288922eceff7ba10ee7e71bf88dc22..1795b63e844a002564932f5d7ef839746e32fae5 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -42,7 +42,7 @@ namespace vkcv {
 
 		Image(ImageManager* manager, const ImageHandle& handle);
 		
-		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth);
+		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool supportStorage, bool supportColorAttachment);
 		
 	};
 	
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index 93523128a67d7b9667c342fb7c10203f4b9a43dd..1e00c5209118469a7dc6ff1eb1e3c98a3c6903a7 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -13,6 +13,8 @@
 
 namespace vkcv {
 
+    enum class PrimitiveTopology{PointList, LineList, TriangleList };
+
     struct PipelineConfig {
         ShaderProgram                         m_ShaderProgram;
         uint32_t                              m_Width;
@@ -21,7 +23,8 @@ namespace vkcv {
         VertexLayout                          m_VertexLayout;
         std::vector<vk::DescriptorSetLayout>  m_DescriptorLayouts;
         bool                                  m_UseDynamicViewport;
-
+        bool                                  m_UseConservativeRasterization = false;
+        PrimitiveTopology                     m_PrimitiveTopology = PrimitiveTopology::TriangleList;
     };
 
 }
\ No newline at end of file
diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp
index 096a857a13f01840d8a3a7e2bf74ba571bd2c249..6a08cb4f02551cae2fd5d1e3ea4e6ff0bd2b2e04 100644
--- a/modules/gui/src/vkcv/gui/GUI.cpp
+++ b/modules/gui/src/vkcv/gui/GUI.cpp
@@ -197,11 +197,13 @@ namespace vkcv::gui {
 		const Swapchain& swapchain = m_core.getSwapchain();
 		const auto extent = swapchain.getExtent();
 		
+		const vk::ImageView swapchainImageView = m_core.getSwapchainImageView();
+
 		const vk::FramebufferCreateInfo framebufferCreateInfo (
 				vk::FramebufferCreateFlags(),
 				m_render_pass,
 				1,
-				&m_core.getSwapchainImageView(),
+				&swapchainImageView,
 				extent.width,
 				extent.height,
 				1
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
index 9e07dec255d283b4b8a8d4fcc769083498c10264..ec358188b8e871da6f4d62ffd397f32bfb795ee2 100644
--- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -3,6 +3,7 @@
 
 #include <fstream>
 #include <glslang/SPIRV/GlslangToSpv.h>
+#include <glslang/StandAlone/DirStackFileIncluder.h>
 
 #include <vkcv/Logger.hpp>
 
@@ -217,12 +218,26 @@ namespace vkcv::shader {
 		
 		TBuiltInResource resources = {};
 		initResources(resources);
+
+		const auto messages = (EShMessages)(
+			EShMsgSpvRules |
+			EShMsgVulkanRules
+			);
+
+		std::string preprocessedGLSL;
+
+		DirStackFileIncluder includer;
+		includer.pushExternalLocalDirectory(shaderPath.parent_path().string());
+
+		if (!shader.preprocess(&resources, 100, ENoProfile, false, false, messages, &preprocessedGLSL, includer)) {
+			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
+				shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
+			return;
+		}
 		
-		const auto messages = (EShMessages) (
-				EShMsgSpvRules |
-				EShMsgVulkanRules
-		);
-		
+		const char* preprocessedCString = preprocessedGLSL.c_str();
+		shader.setStrings(&preprocessedCString, 1);
+
 		if (!shader.parse(&resources, 100, false, messages)) {
 			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
 					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 474c9764bd29053c95ca2c4511d1287359350d02..99a3cb382ec02bab64e9fc714ceeb7e5cef39ed0 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -3,4 +3,4 @@
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
 add_subdirectory(first_scene)
-add_subdirectory(cmd_sync_test)
\ No newline at end of file
+add_subdirectory(voxelization)
\ No newline at end of file
diff --git a/projects/cmd_sync_test/.gitignore b/projects/cmd_sync_test/.gitignore
deleted file mode 100644
index 16f72da367245ad14a38ee756816f06f8cbbe3d2..0000000000000000000000000000000000000000
--- a/projects/cmd_sync_test/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-cmd_sync_test
\ No newline at end of file
diff --git a/projects/cmd_sync_test/CMakeLists.txt b/projects/cmd_sync_test/CMakeLists.txt
deleted file mode 100644
index da1d12949d9e8c918d78ab5cb0484106fae69b6a..0000000000000000000000000000000000000000
--- a/projects/cmd_sync_test/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-project(cmd_sync_test)
-
-# setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
-# adding source files to the project
-add_executable(cmd_sync_test src/main.cpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(cmd_sync_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(cmd_sync_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(cmd_sync_test PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
-
-# including headers of dependencies and the VkCV framework
-target_include_directories(cmd_sync_test SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
-
-# linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(cmd_sync_test vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
diff --git a/projects/cmd_sync_test/resources/shaders/compile.bat b/projects/cmd_sync_test/resources/shaders/compile.bat
deleted file mode 100644
index 516c2f2f78001e1a5d182356e7c3fe82d66a45ee..0000000000000000000000000000000000000000
--- a/projects/cmd_sync_test/resources/shaders/compile.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
-%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
-%VULKAN_SDK%\Bin32\glslc.exe shadow.vert -o shadow_vert.spv
-%VULKAN_SDK%\Bin32\glslc.exe shadow.frag -o shadow_frag.spv
-pause
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/frag.spv b/projects/cmd_sync_test/resources/shaders/frag.spv
deleted file mode 100644
index ff3110571871d65ce119dc6c5006e7e67aa53546..0000000000000000000000000000000000000000
Binary files a/projects/cmd_sync_test/resources/shaders/frag.spv and /dev/null differ
diff --git a/projects/cmd_sync_test/resources/shaders/shadow_frag.spv b/projects/cmd_sync_test/resources/shaders/shadow_frag.spv
deleted file mode 100644
index 6be3bd2518a3b1f234e39aea2503ba86cfb3314b..0000000000000000000000000000000000000000
Binary files a/projects/cmd_sync_test/resources/shaders/shadow_frag.spv and /dev/null differ
diff --git a/projects/cmd_sync_test/resources/shaders/shadow_vert.spv b/projects/cmd_sync_test/resources/shaders/shadow_vert.spv
deleted file mode 100644
index afaa0824ee9be2c22209d611943c6512587dce24..0000000000000000000000000000000000000000
Binary files a/projects/cmd_sync_test/resources/shaders/shadow_vert.spv and /dev/null differ
diff --git a/projects/cmd_sync_test/resources/shaders/vert.spv b/projects/cmd_sync_test/resources/shaders/vert.spv
deleted file mode 100644
index 5e514eef5983927316465679af5461f507497130..0000000000000000000000000000000000000000
Binary files a/projects/cmd_sync_test/resources/shaders/vert.spv and /dev/null differ
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 6377a6726d3e9d1830518206133dd7780593d576..d9650f3577cf5633ac915f435b59367a9f993d62 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -137,8 +137,9 @@ int main(int argc, const char** argv) {
 		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) };
 
 	vkcv::DescriptorWrites setWrites;
-	setWrites.sampledImageWrites    = { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
-	setWrites.samplerWrites         = { vkcv::SamplerDescriptorWrite(1, sampler) };
+	setWrites.sampledImageWrites	= { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
+	setWrites.samplerWrites			= { vkcv::SamplerDescriptorWrite(1, sampler) };
+
 	core.writeDescriptorSet(descriptorSet, setWrites);
 
 	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
diff --git a/projects/voxelization/.gitignore b/projects/voxelization/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f07a22d4e0641a9114e998212f38ca9cb83a9655
--- /dev/null
+++ b/projects/voxelization/.gitignore
@@ -0,0 +1 @@
+voxelization
\ No newline at end of file
diff --git a/projects/voxelization/CMakeLists.txt b/projects/voxelization/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..33cfaef6197079b72ab2f295a4503e80be724db4
--- /dev/null
+++ b/projects/voxelization/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.16)
+project(voxelization)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# this should fix the execution path to load local files from the project
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+
+# adding source files to the project
+add_executable(voxelization src/main.cpp)
+
+target_sources(voxelization PRIVATE
+    src/Voxelization.hpp
+    src/Voxelization.cpp)
+
+# this should fix the execution path to load local files from the project (for MSVC)
+if(MSVC)
+	set_target_properties(voxelization PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+	set_target_properties(voxelization PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+    
+    # in addition to setting the output directory, the working directory has to be set
+	# by default visual studio sets the working directory to the build directory, when using the debugger
+	set_target_properties(voxelization PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+endif()
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(voxelization SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(voxelization vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/voxelization/resources/Sponza/Sponza.bin b/projects/voxelization/resources/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..cfedd26ca5a67b6d0a47d44d13a75e14a141717a
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
+size 6347696
diff --git a/projects/voxelization/resources/Sponza/Sponza.gltf b/projects/voxelization/resources/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..172ea07e21c94465211c860cd805355704cef230
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
+size 713962
diff --git a/projects/voxelization/resources/Sponza/background.png b/projects/voxelization/resources/Sponza/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..b64def129da38f4e23d89e21b4af1039008a4327
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/background.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
+size 1417666
diff --git a/projects/voxelization/resources/Sponza/chain_texture.png b/projects/voxelization/resources/Sponza/chain_texture.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1e1768cff78e0614ad707eca8602a4c4edab5e5
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/chain_texture.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
+size 490895
diff --git a/projects/voxelization/resources/Sponza/lion.png b/projects/voxelization/resources/Sponza/lion.png
new file mode 100644
index 0000000000000000000000000000000000000000..c49c7f0ed31e762e19284d0d3624fbc47664e56b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/lion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
+size 2088728
diff --git a/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png b/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cde4c7a6511e9a5f03c63ad996437fcdba3ce2df
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
+size 1928829
diff --git a/projects/voxelization/resources/Sponza/sponza_arch_diff.png b/projects/voxelization/resources/Sponza/sponza_arch_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9bda2918d226039f9e2d03902d377b706fab6
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_arch_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
+size 1596151
diff --git a/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png b/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..59de631ffac4414cabf69b2dc794c46fc187d6cb
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
+size 1872074
diff --git a/projects/voxelization/resources/Sponza/sponza_column_a_diff.png b/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..01a82432d3f9939bbefe850bdb900f1ff9a3f6db
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
+size 1840992
diff --git a/projects/voxelization/resources/Sponza/sponza_column_b_diff.png b/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..10a660cce2a5a9b8997772c746058ce23e7d45d7
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
+size 2170433
diff --git a/projects/voxelization/resources/Sponza/sponza_column_c_diff.png b/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..bc46fd979044a938d3adca7601689e71504e48bf
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
+size 2066950
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..384c8c2c051160d530eb3ac8b05c9c60752a2d2b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
+size 9510905
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..af842e9f5fe18c1f609875e00899a6770fa4488b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
+size 9189263
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c9b6391a199407637fa71033d79fb58b8b4f0d7
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
+size 8785458
diff --git a/projects/voxelization/resources/Sponza/sponza_details_diff.png b/projects/voxelization/resources/Sponza/sponza_details_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..12656686362c3e0a297e060491f33bd7351551f9
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_details_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
+size 1522643
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..879d16ef84722a4fc13e83a771778de326e4bc54
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
+size 2229440
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..3311287a219d2148620b87fe428fea071688d051
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
+size 2267382
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..de110f369004388dae4cd5067c63428db3a07834
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
+size 2258220
diff --git a/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png b/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f6e0812a0df80346318baa3cb50a6888afc58f8
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
+size 1425673
diff --git a/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png b/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..788ed764f79ba724f04a2d603076a5b85013e188
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
+size 1996838
diff --git a/projects/voxelization/resources/Sponza/sponza_roof_diff.png b/projects/voxelization/resources/Sponza/sponza_roof_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..c5b84261fdd1cc776a94b3ce398c7806b895f9a3
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_roof_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
+size 2320533
diff --git a/projects/voxelization/resources/Sponza/sponza_thorn_diff.png b/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a9142674a7d4a6f94a48c5152cf0300743b597a
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
+size 635001
diff --git a/projects/voxelization/resources/Sponza/vase_dif.png b/projects/voxelization/resources/Sponza/vase_dif.png
new file mode 100644
index 0000000000000000000000000000000000000000..61236a81cb324af8797b05099cd264cefe189e56
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_dif.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
+size 1842101
diff --git a/projects/voxelization/resources/Sponza/vase_hanging.png b/projects/voxelization/resources/Sponza/vase_hanging.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a3cee71d8213225090c74f8c0dce33b9d44378
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_hanging.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
+size 1432804
diff --git a/projects/voxelization/resources/Sponza/vase_plant.png b/projects/voxelization/resources/Sponza/vase_plant.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ad95e702e229f1ebd803e5203a266d15f2c07b9
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_plant.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
+size 998089
diff --git a/projects/voxelization/resources/Sponza/vase_round.png b/projects/voxelization/resources/Sponza/vase_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..c17953abc000c44b8991e23c136c2b67348f3d1b
--- /dev/null
+++ b/projects/voxelization/resources/Sponza/vase_round.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
+size 1871399
diff --git a/projects/cmd_sync_test/resources/cube/boards2_vcyc_jpg.jpg b/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/boards2_vcyc_jpg.jpg
rename to projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
diff --git a/projects/cmd_sync_test/resources/cube/cube.bin b/projects/voxelization/resources/cube/cube.bin
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/cube.bin
rename to projects/voxelization/resources/cube/cube.bin
diff --git a/projects/cmd_sync_test/resources/cube/cube.blend b/projects/voxelization/resources/cube/cube.blend
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/cube.blend
rename to projects/voxelization/resources/cube/cube.blend
diff --git a/projects/cmd_sync_test/resources/cube/cube.blend1 b/projects/voxelization/resources/cube/cube.blend1
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/cube.blend1
rename to projects/voxelization/resources/cube/cube.blend1
diff --git a/projects/cmd_sync_test/resources/cube/cube.glb b/projects/voxelization/resources/cube/cube.glb
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/cube.glb
rename to projects/voxelization/resources/cube/cube.glb
diff --git a/projects/cmd_sync_test/resources/cube/cube.gltf b/projects/voxelization/resources/cube/cube.gltf
similarity index 100%
rename from projects/cmd_sync_test/resources/cube/cube.gltf
rename to projects/voxelization/resources/cube/cube.gltf
diff --git a/projects/voxelization/resources/shaders/gammaCorrection.comp b/projects/voxelization/resources/shaders/gammaCorrection.comp
new file mode 100644
index 0000000000000000000000000000000000000000..411a59c3e38b3414adbda260803c3f3322b16ff2
--- /dev/null
+++ b/projects/voxelization/resources/shaders/gammaCorrection.comp
@@ -0,0 +1,18 @@
+#version 440
+
+layout(set=0, binding=0, r11f_g11f_b10f)    uniform image2D inImage;
+layout(set=0, binding=1, rgba8)             uniform image2D outImage;
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
+        return;
+    }
+    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
+    vec3 linearColor = imageLoad(inImage, uv).rgb;
+    vec3 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/voxelization/resources/shaders/perMeshResources.inc b/projects/voxelization/resources/shaders/perMeshResources.inc
new file mode 100644
index 0000000000000000000000000000000000000000..95e4fb7c27009965659d14a9c72acfec950c37e3
--- /dev/null
+++ b/projects/voxelization/resources/shaders/perMeshResources.inc
@@ -0,0 +1,2 @@
+layout(set=1, binding=0) uniform texture2D  albedoTexture;
+layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shader.frag b/projects/voxelization/resources/shaders/shader.frag
similarity index 74%
rename from projects/cmd_sync_test/resources/shaders/shader.frag
rename to projects/voxelization/resources/shaders/shader.frag
index 95f1b3319e1ca5c7c34ff94e5e7198819c0233c1..edafc8c0077416cb57aedc4e7358126846507e18 100644
--- a/projects/cmd_sync_test/resources/shaders/shader.frag
+++ b/projects/voxelization/resources/shaders/shader.frag
@@ -1,5 +1,8 @@
 #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;
@@ -7,14 +10,12 @@ layout(location = 2) in vec3 passPos;
 
 layout(location = 0) out vec3 outColor;
 
-layout(set=0, binding=0) uniform texture2D  meshTexture;
-layout(set=0, binding=1) uniform sampler    textureSampler;
-layout(set=0, binding=2) uniform sunBuffer {
+layout(set=0, binding=0) uniform sunBuffer {
     vec3 L; float padding;
     mat4 lightMatrix;
 };
-layout(set=0, binding=3) uniform texture2D  shadowMap;
-layout(set=0, binding=4) uniform sampler    shadowMapSampler;
+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);
@@ -39,6 +40,6 @@ void main()	{
     vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
     sun *= shadowTest(passPos);
     vec3 ambient = vec3(0.1);
-    vec3 albedo = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
+    vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
 	outColor = albedo * (sun + ambient);
 }
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/shaders/shader.vert b/projects/voxelization/resources/shaders/shader.vert
similarity index 82%
rename from projects/cmd_sync_test/resources/shaders/shader.vert
rename to projects/voxelization/resources/shaders/shader.vert
index 0ab82c203806356d0f35dc52c0a6988b286d90d1..926f86af2860cb57c44d2d5ee78712b6ae155e5c 100644
--- a/projects/cmd_sync_test/resources/shaders/shader.vert
+++ b/projects/voxelization/resources/shaders/shader.vert
@@ -16,7 +16,7 @@ layout( push_constant ) uniform constants{
 
 void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
-	passNormal  = inNormal;
+	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/cmd_sync_test/resources/shaders/shadow.frag b/projects/voxelization/resources/shaders/shadow.frag
similarity index 100%
rename from projects/cmd_sync_test/resources/shaders/shadow.frag
rename to projects/voxelization/resources/shaders/shadow.frag
diff --git a/projects/cmd_sync_test/resources/shaders/shadow.vert b/projects/voxelization/resources/shaders/shadow.vert
similarity index 100%
rename from projects/cmd_sync_test/resources/shaders/shadow.vert
rename to projects/voxelization/resources/shaders/shadow.vert
diff --git a/projects/voxelization/resources/shaders/voxel.inc b/projects/voxelization/resources/shaders/voxel.inc
new file mode 100644
index 0000000000000000000000000000000000000000..d2b4400235817e3be1739dc46857ab42f260ebf7
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxel.inc
@@ -0,0 +1,25 @@
+struct VoxelInfo{
+    vec3 offset;
+    float extent;
+};
+
+uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
+    return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
+}
+
+uint packVoxelInfo(vec3 color){
+    uint opaqueBit   = 1 << 31;     
+    uint redBits     = uint(color.r * 255);
+    uint greenBits   = uint(color.g * 255) << 8;
+    uint blueBits    = uint(color.b * 255) << 16;
+    return opaqueBit | redBits | greenBits | blueBits;
+}
+
+vec4 unpackVoxelInfo(uint packed){
+    vec4 rgba;
+    rgba.r = (packed >> 0  & 0x000000FF) / 255.f;
+    rgba.g = (packed >> 8  & 0x000000FF) / 255.f;
+    rgba.b = (packed >> 16 & 0x000000FF) / 255.f;
+    rgba.a = packed >> 31; 
+    return rgba;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelBufferToImage.comp b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
new file mode 100644
index 0000000000000000000000000000000000000000..5e8298886cb2bacbc81f981e8e90310cdc876d5d
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelBufferToImage.comp
@@ -0,0 +1,24 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
+
+layout(set=0, binding=0, std430) buffer voxelBuffer{
+    uint packedVoxelData[];
+};
+
+layout(set=0, binding=1, rgba16f) uniform image3D voxelImage;
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+void main(){
+
+    ivec3 voxelImageSize = imageSize(voxelImage);
+    if(any(greaterThanEqual(gl_GlobalInvocationID, voxelImageSize))){
+        return;
+    }
+    ivec3 UV = ivec3(gl_GlobalInvocationID);
+    uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
+    
+    vec4 color = unpackVoxelInfo(packedVoxelData[flatIndex]);
+    imageStore(voxelImage, UV, vec4(color));
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelReset.comp b/projects/voxelization/resources/shaders/voxelReset.comp
new file mode 100644
index 0000000000000000000000000000000000000000..14b78d6584d703be68594e3cb03ebcd47c94b6e0
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelReset.comp
@@ -0,0 +1,19 @@
+#version 450
+
+layout(set=0, binding=0) buffer voxelizationBuffer{
+    uint isFilled[];
+};
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    uint voxelCount;
+};
+
+void main(){
+
+    if(gl_GlobalInvocationID.x> voxelCount){
+        return;
+    }  
+    isFilled[gl_GlobalInvocationID.x] = 0;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.frag b/projects/voxelization/resources/shaders/voxelVisualisation.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0b02beb7e848ab20cda4b012f77d1fa664b6ab53
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.frag
@@ -0,0 +1,9 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location=0) in vec3 passColorToFrag;
+layout(location=0) out vec3 outColor;
+
+void main()	{
+    outColor = passColorToFrag;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.geom b/projects/voxelization/resources/shaders/voxelVisualisation.geom
new file mode 100644
index 0000000000000000000000000000000000000000..e98076fcc83a69a903df454cb00267da84e3f223
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.geom
@@ -0,0 +1,104 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(points) in;
+layout (triangle_strip, max_vertices = 24) out;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+};
+
+layout(location = 0) in float passCubeHalf[1];
+layout(location = 1) in vec3 passColorToGeom[1];
+
+
+layout(location = 0) out vec3 passColorToFrag;
+
+void main()	{
+    float cubeHalf = passCubeHalf[0];
+    // right
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, 1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // left
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // back
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,   1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1,  1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // front
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,   1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1,  1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();                                                                  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, 1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // bot
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex(); 
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, 1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+    // top
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();   
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(1,  -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();  
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1,  1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();   
+    gl_Position = viewProjection * vec4(gl_in[0].gl_Position.xyz + cubeHalf * vec3(-1, -1, -1), 1);
+    passColorToFrag = passColorToGeom[0];
+    EmitVertex();
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.vert b/projects/voxelization/resources/shaders/voxelVisualisation.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8377143f4f4bbf351d3251df9724d37e1747a4dc
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelVisualisation.vert
@@ -0,0 +1,36 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "voxel.inc"
+
+layout(location = 0) out float passCubeHalf;
+layout(location = 1) out vec3 passColorToGeom;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+};
+
+layout(set=0, binding=0, rgba16f) uniform image3D  voxelImage;
+layout(set=0, binding=1) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+
+void main()	{
+    passCubeHalf        = voxelInfo.extent / float(imageSize(voxelImage).x) * 0.5f;
+    int voxelResolution = imageSize(voxelImage).x;
+    int slicePixelCount = voxelResolution * voxelResolution;
+    int z               = gl_VertexIndex / slicePixelCount;
+    int index2D         = gl_VertexIndex % slicePixelCount;
+    int y               = index2D / voxelResolution;
+    int x               = index2D % voxelResolution;
+    vec3 position       = (vec3(x, y, z) / voxelResolution - 0.5) * voxelInfo.extent + passCubeHalf + voxelInfo.offset;
+	gl_Position         = vec4(position, 1.0);
+    
+    vec4 voxelColor = imageLoad(voxelImage, ivec3(x,y,z));
+    if(voxelColor.a == 0){
+        gl_Position.x /= 0; // clip
+    }
+    passColorToGeom = voxelColor.rgb;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.frag b/projects/voxelization/resources/shaders/voxelization.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7ea161ce4f5a4d59bb3d50c78553df0dfb5ab4ec
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.frag
@@ -0,0 +1,40 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "voxel.inc"
+#include "perMeshResources.inc"
+
+layout(location = 0) in     vec3 passPos;
+layout(location = 1) out    vec2 passUV;
+
+layout(set=0, binding=0, std430) buffer voxelizationBuffer{
+    uint packedVoxelData[];
+};
+
+layout(set=0, binding=1) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+layout(set=0, binding=2, r8) uniform image3D voxelImage;
+
+vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
+    return (world - info.offset) / info.extent + 0.5f;
+}
+
+ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
+    return ivec3(voxelCoordinates * voxelImageResolution);
+}
+
+void main()	{
+    vec3 voxelCoordinates = worldToVoxelCoordinates(passPos, voxelInfo);
+    ivec3 voxelImageSize = imageSize(voxelImage);
+    ivec3 UV = voxelCoordinatesToUV(voxelCoordinates, voxelImageSize);
+    if(any(lessThan(UV, ivec3(0))) || any(greaterThanEqual(UV, voxelImageSize))){
+        return;
+    }
+    uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
+    
+    vec3 color = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+    atomicMax(packedVoxelData[flatIndex], packVoxelInfo(color));
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.geom b/projects/voxelization/resources/shaders/voxelization.geom
new file mode 100644
index 0000000000000000000000000000000000000000..19e31e2d2d032b5a9e5c273f6420c6449be9203e
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.geom
@@ -0,0 +1,35 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(triangles) in;
+layout (triangle_strip, max_vertices = 3) out;
+
+layout(location = 0) in vec3 passPosIn[3];
+layout(location = 1) in vec2 passUVIn[3];
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec2 passUV;
+
+void main()	{
+    // compute geometric normal, no normalization necessary
+    vec3 N = cross(passPosIn[0] - passPosIn[1], passPosIn[0] - passPosIn[2]);
+    N = abs(N); // only interested in the magnitude
+    
+    for(int i = 0; i < 3; i++){
+        // swizzle position, so biggest side is rasterized
+        if(N.z > N.x && N.z > N.y){
+            gl_Position = gl_in[i].gl_Position.xyzw;
+        }
+        else if(N.x > N.y){
+            gl_Position = gl_in[i].gl_Position.yzxw;
+        }
+        else{
+            gl_Position = gl_in[i].gl_Position.xzyw;
+        }
+        gl_Position.z = gl_Position.z * 0.5 + 0.5;  // xyz are kept in NDC range [-1, 1] so swizzling works, but vulkan needs final z in range [0, 1]
+        passPos = passPosIn[i];
+        passUV  = passUVIn[i];
+        EmitVertex();
+    }
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.vert b/projects/voxelization/resources/shaders/voxelization.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7a43c08b64d3df384d3a7e627d789db9be99f680
--- /dev/null
+++ b/projects/voxelization/resources/shaders/voxelization.vert
@@ -0,0 +1,20 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec2 passUV;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 model;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+    passPos     = (model * vec4(inPosition, 1)).xyz;
+    passUV      = inUV;
+}
\ No newline at end of file
diff --git a/projects/cmd_sync_test/resources/triangle/Triangle.bin b/projects/voxelization/resources/triangle/Triangle.bin
similarity index 100%
rename from projects/cmd_sync_test/resources/triangle/Triangle.bin
rename to projects/voxelization/resources/triangle/Triangle.bin
diff --git a/projects/cmd_sync_test/resources/triangle/Triangle.blend b/projects/voxelization/resources/triangle/Triangle.blend
similarity index 100%
rename from projects/cmd_sync_test/resources/triangle/Triangle.blend
rename to projects/voxelization/resources/triangle/Triangle.blend
diff --git a/projects/cmd_sync_test/resources/triangle/Triangle.glb b/projects/voxelization/resources/triangle/Triangle.glb
similarity index 100%
rename from projects/cmd_sync_test/resources/triangle/Triangle.glb
rename to projects/voxelization/resources/triangle/Triangle.glb
diff --git a/projects/cmd_sync_test/resources/triangle/Triangle.gltf b/projects/voxelization/resources/triangle/Triangle.gltf
similarity index 100%
rename from projects/cmd_sync_test/resources/triangle/Triangle.gltf
rename to projects/voxelization/resources/triangle/Triangle.gltf
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a67177447ec3b795c3c65476e6881b067a33df84
--- /dev/null
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -0,0 +1,285 @@
+#include "Voxelization.hpp"
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+vkcv::ShaderProgram loadVoxelizationShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelization.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelization.geom",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelization.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelVisualisationShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelVisualisation.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelVisualisation.geom",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelVisualisation.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelResetShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelReset.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadVoxelBufferToImageShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelBufferToImage.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+const uint32_t voxelResolution = 128;
+uint32_t voxelCount = voxelResolution * voxelResolution * voxelResolution;
+const vk::Format voxelizationDummyFormat = vk::Format::eR8Unorm;
+
+Voxelization::Voxelization(vkcv::Core* corePtr, const Dependencies& dependencies) 
+	:
+	m_corePtr(corePtr), 
+	m_voxelImage(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true)),
+	m_dummyRenderTarget(m_corePtr->createImage(voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, true)),
+	m_voxelInfoBuffer(m_corePtr->createBuffer<VoxelizationInfo>(vkcv::BufferType::UNIFORM, 1)),
+	m_voxelBuffer(m_corePtr->createBuffer<VoxelBufferContent>(vkcv::BufferType::STORAGE, voxelCount)){
+
+	const vkcv::ShaderProgram voxelizationShader = loadVoxelizationShader();
+
+	const vkcv::PassConfig voxelizationPassConfig({vkcv::AttachmentDescription(
+		vkcv::AttachmentOperation::DONT_CARE, 
+		vkcv::AttachmentOperation::DONT_CARE, 
+		voxelizationDummyFormat) });
+	m_voxelizationPass = m_corePtr->createPass(voxelizationPassConfig);
+
+	std::vector<vkcv::DescriptorBinding> voxelizationDescriptorBindings = 
+	{ voxelizationShader.getReflectedDescriptors()[0] };
+	m_voxelizationDescriptorSet = m_corePtr->createDescriptorSet(voxelizationDescriptorBindings);
+
+	vkcv::DescriptorSetHandle dummyPerMeshDescriptorSet =
+		m_corePtr->createDescriptorSet({ voxelizationShader.getReflectedDescriptors()[1] });
+
+	const vkcv::PipelineConfig voxelizationPipeConfig{
+		voxelizationShader,
+		voxelResolution,
+		voxelResolution,
+		m_voxelizationPass,
+		dependencies.vertexLayout,
+		{ 
+			m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).layout,
+			m_corePtr->getDescriptorSet(dummyPerMeshDescriptorSet).layout},
+		false,
+		true };
+	m_voxelizationPipe = m_corePtr->createGraphicsPipeline(voxelizationPipeConfig);
+
+	vkcv::DescriptorWrites voxelizationDescriptorWrites;
+	voxelizationDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	voxelizationDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
+	voxelizationDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, m_voxelImage.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_voxelizationDescriptorSet, voxelizationDescriptorWrites);
+
+	vkcv::ShaderProgram voxelVisualisationShader = loadVoxelVisualisationShader();
+
+	const std::vector<vkcv::DescriptorBinding> voxelVisualisationDescriptorBindings = 
+		{ voxelVisualisationShader.getReflectedDescriptors()[0] };
+	m_visualisationDescriptorSet = m_corePtr->createDescriptorSet(voxelVisualisationDescriptorBindings);
+
+	const vkcv::AttachmentDescription voxelVisualisationColorAttachments(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		dependencies.colorBufferFormat
+	);
+
+	const vkcv::AttachmentDescription voxelVisualisationDepthAttachments(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::LOAD,
+		dependencies.depthBufferFormat
+	);
+
+	vkcv::PassConfig voxelVisualisationPassDefinition(
+		{ voxelVisualisationColorAttachments, voxelVisualisationDepthAttachments });
+	m_visualisationPass = m_corePtr->createPass(voxelVisualisationPassDefinition);
+
+	const vkcv::PipelineConfig voxelVisualisationPipeConfig{
+		voxelVisualisationShader,
+		0,
+		0,
+		m_visualisationPass,
+		{},
+		{ m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).layout },
+		true,
+		false,
+		vkcv::PrimitiveTopology::PointList };	// points are extended to cubes in the geometry shader
+	m_visualisationPipe = m_corePtr->createGraphicsPipeline(voxelVisualisationPipeConfig);
+
+	std::vector<uint16_t> voxelIndexData;
+	for (int i = 0; i < voxelCount; i++) {
+		voxelIndexData.push_back(i);
+	}
+
+	vkcv::DescriptorWrites voxelVisualisationDescriptorWrite;
+	voxelVisualisationDescriptorWrite.storageImageWrites = 
+	{ vkcv::StorageImageDescriptorWrite(0, m_voxelImage.getHandle()) };
+	voxelVisualisationDescriptorWrite.uniformBufferWrites = 
+	{ vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_visualisationDescriptorSet, voxelVisualisationDescriptorWrite);
+
+	const vkcv::DescriptorSetUsage voxelizationDescriptorUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle);
+
+	vkcv::ShaderProgram resetVoxelShader = loadVoxelResetShader();
+
+	m_voxelResetDescriptorSet = m_corePtr->createDescriptorSet(resetVoxelShader.getReflectedDescriptors()[0]);
+	m_voxelResetPipe = m_corePtr->createComputePipeline(
+		resetVoxelShader,
+		{ m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).layout });
+
+	vkcv::DescriptorWrites resetVoxelWrites;
+	resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites);
+
+
+	vkcv::ShaderProgram bufferToImageShader = loadVoxelBufferToImageShader();
+
+	m_bufferToImageDescriptorSet = m_corePtr->createDescriptorSet(bufferToImageShader.getReflectedDescriptors()[0]);
+	m_bufferToImagePipe = m_corePtr->createComputePipeline(
+		bufferToImageShader,
+		{ m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).layout });
+
+	vkcv::DescriptorWrites bufferToImageDescriptorWrites;
+	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImage.getHandle()) };
+	m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites);
+}
+
+void Voxelization::voxelizeMeshes(
+	vkcv::CommandStreamHandle                       cmdStream, 
+	const glm::vec3&                                cameraPosition, 
+	const std::vector<vkcv::Mesh>&                  meshes,
+	const std::vector<glm::mat4>&                   modelMatrices,
+	const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets) {
+
+	VoxelizationInfo voxelizationInfo;
+	voxelizationInfo.extent = m_voxelExtent;
+
+	// move voxel offset with camera in voxel sized steps
+	const float voxelSize = m_voxelExtent / voxelResolution;
+	voxelizationInfo.offset = glm::floor(cameraPosition / voxelSize) * voxelSize;
+
+	m_voxelInfoBuffer.fill({ voxelizationInfo });
+
+	const float voxelizationHalfExtent = 0.5f * m_voxelExtent;
+	const glm::mat4 voxelizationProjection = glm::ortho(
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent,
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent,
+		-voxelizationHalfExtent,
+		voxelizationHalfExtent);
+
+	const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -voxelizationInfo.offset);
+	const glm::mat4 voxelizationViewProjection = voxelizationProjection * voxelizationView;
+
+	std::vector<std::array<glm::mat4, 2>> voxelizationMatrices;
+	for (const auto& m : modelMatrices) {
+		voxelizationMatrices.push_back({ voxelizationViewProjection * m, m });
+	}
+
+	const vkcv::PushConstantData voxelizationPushConstantData((void*)voxelizationMatrices.data(), 2 * sizeof(glm::mat4));
+
+	// reset voxels
+	const uint32_t resetVoxelGroupSize = 64;
+	uint32_t resetVoxelDispatchCount[3];
+	resetVoxelDispatchCount[0] = glm::ceil(voxelCount / float(resetVoxelGroupSize));
+	resetVoxelDispatchCount[1] = 1;
+	resetVoxelDispatchCount[2] = 1;
+
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_voxelResetPipe,
+		resetVoxelDispatchCount,
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) },
+		vkcv::PushConstantData(&voxelCount, sizeof(voxelCount)));
+	m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle());
+
+	// voxelization
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	for (int i = 0; i < meshes.size(); i++) {
+		drawcalls.push_back(vkcv::DrawcallInfo(
+			meshes[i], 
+			{ 
+				vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).vulkanHandle),
+				vkcv::DescriptorSetUsage(1, m_corePtr->getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) 
+			}));
+	}
+
+	m_corePtr->recordDrawcallsToCmdStream(
+		cmdStream,
+		m_voxelizationPass,
+		m_voxelizationPipe,
+		voxelizationPushConstantData,
+		drawcalls,
+		{ m_dummyRenderTarget.getHandle() });
+
+	// buffer to image
+	const uint32_t bufferToImageGroupSize[3] = { 4, 4, 4 };
+	uint32_t bufferToImageDispatchCount[3];
+	for (int i = 0; i < 3; i++) {
+		bufferToImageDispatchCount[i] = glm::ceil(voxelResolution / float(bufferToImageGroupSize[i]));
+	}
+
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_bufferToImagePipe,
+		bufferToImageDispatchCount,
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) },
+		vkcv::PushConstantData(nullptr, 0));
+
+	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
+}
+
+void Voxelization::renderVoxelVisualisation(
+	vkcv::CommandStreamHandle cmdStream, 
+	const glm::mat4& viewProjectin,
+	const std::vector<vkcv::ImageHandle>& renderTargets) {
+
+	const vkcv::PushConstantData voxelVisualisationPushConstantData((void*)&viewProjectin, sizeof(glm::mat4));
+
+	const auto drawcall = vkcv::DrawcallInfo(
+		vkcv::Mesh({}, nullptr, voxelCount),
+		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle) });
+
+	m_corePtr->recordDrawcallsToCmdStream(
+		cmdStream,
+		m_visualisationPass,
+		m_visualisationPipe,
+		voxelVisualisationPushConstantData,
+		{ drawcall },
+		renderTargets);
+}
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.hpp b/projects/voxelization/src/Voxelization.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f9b96998b39e24a3481d130efa68ebaa813b8256
--- /dev/null
+++ b/projects/voxelization/src/Voxelization.hpp
@@ -0,0 +1,59 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <glm/glm.hpp>
+
+class Voxelization{
+public:
+	struct Dependencies {
+		vkcv::VertexLayout  vertexLayout;
+		vk::Format          colorBufferFormat;
+		vk::Format          depthBufferFormat;
+	};
+	Voxelization(vkcv::Core* corePtr, const Dependencies& dependencies);
+
+	void voxelizeMeshes(
+		vkcv::CommandStreamHandle                       cmdStream, 
+		const glm::vec3&                                cameraPosition, 
+		const std::vector<vkcv::Mesh>&                  meshes,
+		const std::vector<glm::mat4>&                   modelMatrices,
+		const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets);
+
+	void renderVoxelVisualisation(
+		vkcv::CommandStreamHandle               cmdStream,
+		const glm::mat4&                        viewProjectin,
+		const std::vector<vkcv::ImageHandle>&   renderTargets);
+
+private:
+	vkcv::Core* m_corePtr;
+
+	struct VoxelBufferContent{
+		uint32_t isFilled;
+	};
+
+	vkcv::Image                         m_voxelImage;
+    vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
+
+	vkcv::Image                 m_dummyRenderTarget;
+	vkcv::PassHandle            m_voxelizationPass;
+	vkcv::PipelineHandle        m_voxelizationPipe;
+	vkcv::DescriptorSetHandle   m_voxelizationDescriptorSet;
+
+	vkcv::PipelineHandle        m_voxelResetPipe;
+	vkcv::DescriptorSetHandle   m_voxelResetDescriptorSet;
+
+	vkcv::PipelineHandle        m_bufferToImagePipe;
+	vkcv::DescriptorSetHandle   m_bufferToImageDescriptorSet;
+
+	vkcv::PassHandle            m_visualisationPass;
+	vkcv::PipelineHandle        m_visualisationPipe;
+
+	vkcv::DescriptorSetHandle   m_visualisationDescriptorSet;
+
+	struct VoxelizationInfo {
+		glm::vec3 offset;
+		float extent;
+	};
+	vkcv::Buffer<VoxelizationInfo> m_voxelInfoBuffer;
+
+	const float m_voxelExtent = 20.f;
+};
\ No newline at end of file
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..81d99f8af37722735742c697171219e012f6b339
--- /dev/null
+++ b/projects/voxelization/src/main.cpp
@@ -0,0 +1,441 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/Logger.hpp>
+#include "Voxelization.hpp"
+#include <glm/glm.hpp>
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "Voxelization";
+
+	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/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, true, true).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::ShaderProgram shadowShader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shadowShader.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+	const vkcv::PipelineConfig shadowPipeConfig{
+		shadowShader,
+		shadowMapResolution,
+		shadowMapResolution,
+		shadowPass,
+		vertexLayout,
+		{},
+		false
+	};
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	bool renderVoxelVis = false;
+	window.e_key.add([&renderVoxelVis](int key ,int scancode, int action, int mods) {
+		if (key == GLFW_KEY_V && action == GLFW_PRESS) {
+			renderVoxelVis = !renderVoxelVis;
+		}
+	});
+
+	// 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 });
+
+	// model matrices per mesh
+	std::vector<glm::mat4> modelMatrices;
+	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
+	for (const auto& mesh : scene.meshes) {
+		const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]);
+		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
+			modelMatrices[vertexGroupIndex] = m;
+		}
+	}
+
+	// prepare drawcalls
+	std::vector<vkcv::Mesh> meshes;
+	for (int i = 0; i < scene.vertexGroups.size(); i++) {
+		vkcv::Mesh mesh(
+			vertexBufferBindings[i], 
+			indexBuffers[i].getVulkanHandle(), 
+			scene.vertexGroups[i].numIndices);
+		meshes.push_back(mesh);
+	}
+
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
+	for (int i = 0; i < meshes.size(); i++) {
+
+		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
+			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
+			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
+	}
+
+	Voxelization::Dependencies voxelDependencies;
+	voxelDependencies.colorBufferFormat = colorBufferFormat;
+	voxelDependencies.depthBufferFormat = depthBufferFormat;
+	voxelDependencies.vertexLayout = vertexLayout;
+	Voxelization voxelization(&core, voxelDependencies);
+
+	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, true, true).getHandle();
+
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+
+		// update descriptor sets which use swapchain image
+		vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
+		gammaCorrectionDescriptorWrites.storageImageWrites = {
+			vkcv::StorageImageDescriptorWrite(0, colorBuffer),
+			vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
+		core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites);
+
+		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 = 20.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
+		for (const auto& m : modelMatrices) {
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix * m);
+		}
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
+
+		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		// shadow map
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
+		voxelization.voxelizeMeshes(
+			cmdStream, 
+			cameraManager.getActiveCamera().getPosition(), 
+			meshes, 
+			modelMatrices,
+			perMeshDescriptorSets);
+
+		// main pass
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+            forwardPass,
+            forwardPipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+
+		if (renderVoxelVis) {
+			voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets);
+		}
+
+		const uint32_t gammaCorrectionLocalGroupSize = 8;
+		const uint32_t gammaCorrectionDispatchCount[3] = {
+			static_cast<uint32_t>(glm::ceil(windowWidth / static_cast<float>(gammaCorrectionLocalGroupSize))),
+			static_cast<uint32_t>(glm::ceil(windowHeight / static_cast<float>(gammaCorrectionLocalGroupSize))),
+			1
+		};
+
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+		core.prepareImageForStorage(cmdStream, colorBuffer);
+
+		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/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index 6d494c4ec90726d46039007607464378624f1c75..4df411c193acffa42401e4f5932d97f531cac9c9 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -5,6 +5,7 @@
 
 #include "vkcv/BufferManager.hpp"
 #include "vkcv/Core.hpp"
+#include <vkcv/Logger.hpp>
 
 namespace vkcv {
 	
@@ -335,4 +336,33 @@ namespace vkcv {
 		}
 	}
 
+	void BufferManager ::recordBufferMemoryBarrier(const BufferHandle& handle, vk::CommandBuffer cmdBuffer) {
+
+		const uint64_t id = handle.getId();
+
+		if (id >= m_buffers.size()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "Invalid buffer handle");
+			return;
+		}
+
+		auto& buffer = m_buffers[id];
+		
+		vk::BufferMemoryBarrier memoryBarrier(
+			vk::AccessFlagBits::eMemoryWrite, 
+			vk::AccessFlagBits::eMemoryRead,
+			0,
+			0,
+			buffer.m_handle,
+			0,
+			buffer.m_size);
+
+		cmdBuffer.pipelineBarrier(
+			vk::PipelineStageFlagBits::eTopOfPipe,
+			vk::PipelineStageFlagBits::eBottomOfPipe,
+			{},
+			nullptr,
+			memoryBarrier,
+			nullptr);
+	}
+
 }
diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp
index b53a1a2c2db1008e7c69c880ef1c5a608d879021..ac133d1affc81702ee1a19b3f66810e606bec58d 100644
--- a/src/vkcv/Context.cpp
+++ b/src/vkcv/Context.cpp
@@ -275,7 +275,13 @@ namespace vkcv
 		deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		deviceCreateInfo.ppEnabledLayerNames = validationLayers.data();
 #endif
-		
+
+		// FIXME: check if device feature is supported
+		vk::PhysicalDeviceFeatures deviceFeatures;
+		deviceFeatures.fragmentStoresAndAtomics = true;
+		deviceFeatures.geometryShader = true;
+		deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
+
 		// Ablauf
 		// qCreateInfos erstellen --> braucht das Device
 		// device erstellen
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 49707d4cffc18719d8fbb18a9e632e12ba679c2e..fd2c595b4305740e801e8d3d50af74521fc3418c 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -37,8 +37,7 @@ namespace vkcv
 
         Swapchain swapChain = Swapchain::create(window, context);
 
-        std::vector<vk::ImageView> imageViews;
-        imageViews = createImageViews( context, swapChain);
+		 std::vector<vk::ImageView> swapchainImageViews = createSwapchainImageViews( context, swapChain);
 
         const auto& queueManager = context.getQueueManager();
         
@@ -47,7 +46,7 @@ namespace vkcv
 		const auto						commandResources		= createCommandResources(context.getDevice(), queueFamilySet);
 		const auto						defaultSyncResources	= createSyncResources(context.getDevice());
 
-        return Core(std::move(context) , window, swapChain, imageViews, commandResources, defaultSyncResources);
+        return Core(std::move(context) , window, swapChain, swapchainImageViews, commandResources, defaultSyncResources);
     }
 
     const Context &Core::getContext() const
@@ -59,12 +58,11 @@ namespace vkcv
     	return m_swapchain;
     }
 
-    Core::Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> imageViews,
+    Core::Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> swapchainImageViews,
         const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
             m_Context(std::move(context)),
             m_window(window),
             m_swapchain(swapChain),
-            m_swapchainImageViews(imageViews),
             m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)},
             m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)},
             m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)),
@@ -85,17 +83,19 @@ namespace vkcv
 			m_swapchain.signalSwapchainRecreation();
 		});
 
-		m_swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
-		m_swapchainImageLayouts.resize(m_swapchainImages.size(), vk::ImageLayout::eUndefined);
+		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+		m_ImageManager->setSwapchainImages(
+			swapchainImages, 
+			swapchainImageViews, 
+			swapChain.getExtent().width,
+			swapChain.getExtent().height,
+			swapChain.getFormat());
 	}
 
 	Core::~Core() noexcept {
     	m_window.e_resize.remove(e_resizeHandle);
     	
 		m_Context.getDevice().waitIdle();
-		for (auto image : m_swapchainImageViews) {
-			m_Context.m_Device.destroyImageView(image);
-		}
 
 		destroyCommandResources(m_Context.getDevice(), m_CommandResources);
 		destroySyncResources(m_Context.getDevice(), m_SyncResources);
@@ -151,16 +151,12 @@ namespace vkcv
 	bool Core::beginFrame(uint32_t& width, uint32_t& height) {
 		if (m_swapchain.shouldUpdateSwapchain()) {
 			m_Context.getDevice().waitIdle();
-			
-			for (auto image : m_swapchainImageViews)
-				m_Context.m_Device.destroyImageView(image);
-			
+
 			m_swapchain.updateSwapchain(m_Context, m_window);
-			m_swapchainImageViews = createImageViews(m_Context, m_swapchain);
-			m_swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+			const auto swapchainViews = createSwapchainImageViews(m_Context, m_swapchain);
+			const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
 
-			m_swapchainImageLayouts.clear();
-			m_swapchainImageLayouts.resize(m_swapchainImages.size(), vk::ImageLayout::eUndefined);
+			m_ImageManager->setSwapchainImages(swapchainImages, swapchainViews, width, height, m_swapchain.getFormat());
 		}
 		
     	if (acquireSwapchainImage() != Result::SUCCESS) {
@@ -176,6 +172,8 @@ namespace vkcv
 		width = extent.width;
 		height = extent.height;
 		
+		m_ImageManager->setCurrentSwapchainImageIndex(m_currentSwapchainImageIndex);
+
 		return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max());
 	}
 
@@ -218,23 +216,16 @@ namespace vkcv
 		const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
-		const vk::ImageView swapchainImageView = m_swapchainImageViews[m_currentSwapchainImageIndex];
-
 		std::vector<vk::ImageView> attachmentsViews;
 		for (const ImageHandle handle : renderTargets) {
 			vk::ImageView targetHandle;
 			const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
-			if (handle.isSwapchainImage()) {
-				recordSwapchainImageLayoutTransition(cmdBuffer, vk::ImageLayout::eColorAttachmentOptimal);
-				targetHandle = m_swapchainImageViews[m_currentSwapchainImageIndex];
-			}
-			else {
-				targetHandle = m_ImageManager->getVulkanImageView(handle);
-				const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle));
-				const vk::ImageLayout targetLayout = 
-					isDepthFormat ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal;
-				m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer);
-			}
+
+			targetHandle = m_ImageManager->getVulkanImageView(handle);
+			const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle));
+			const vk::ImageLayout targetLayout = 
+				isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal;
+			m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer);
 			attachmentsViews.push_back(targetHandle);
 		}
 		
@@ -451,9 +442,9 @@ namespace vkcv
 		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
 	}
 
-	Image Core::createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+	Image Core::createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool supportStorage, bool supportColorAttachment)
 	{
-    	return Image::create(m_ImageManager.get(), format, width, height, depth);
+    	return Image::create(m_ImageManager.get(), format, width, height, depth, supportStorage, supportColorAttachment);
 	}
 
     DescriptorSetHandle Core::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
@@ -463,7 +454,7 @@ namespace vkcv
 
 	void Core::writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes) {
 		m_DescriptorManager->writeDescriptorSet(
-			handle,  
+			handle,
 			writes, 
 			*m_ImageManager, 
 			*m_BufferManager, 
@@ -474,7 +465,7 @@ namespace vkcv
 		return m_DescriptorManager->getDescriptorSet(handle);
 	}
 
-    std::vector<vk::ImageView> Core::createImageViews( Context &context, Swapchain& swapChain){
+    std::vector<vk::ImageView> Core::createSwapchainImageViews( Context &context, Swapchain& swapChain){
         std::vector<vk::ImageView> imageViews;
         std::vector<vk::Image> swapChainImages = context.getDevice().getSwapchainImagesKHR(swapChain.getSwapchain());
         imageViews.reserve( swapChainImages.size() );
@@ -502,20 +493,11 @@ namespace vkcv
         return imageViews;
     }
 
-	void Core::recordSwapchainImageLayoutTransition(vk::CommandBuffer cmdBuffer, vk::ImageLayout newLayout) {
-		auto& imageLayout = m_swapchainImageLayouts[m_currentSwapchainImageIndex];
-		const auto transitionBarrier = createSwapchainImageLayoutTransitionBarrier(
-			m_swapchainImages[m_currentSwapchainImageIndex],
-			imageLayout,
-			newLayout);
-		recordImageBarrier(cmdBuffer, transitionBarrier);
-		imageLayout = newLayout;
-	}
-
-	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle handle) {
-		m_CommandStreamManager->recordCommandsToStream(handle, [&](vk::CommandBuffer cmdBuffer) {
-			recordSwapchainImageLayoutTransition(cmdBuffer, vk::ImageLayout::ePresentSrcKHR);
-		});
+	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle cmdStream) {
+		auto swapchainHandle = ImageHandle::createSwapchainImageHandle();
+		recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(swapchainHandle, vk::ImageLayout::ePresentSrcKHR, cmdBuffer);
+		}, nullptr);
 	}
 
 	void Core::prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image) {
@@ -523,9 +505,27 @@ namespace vkcv
 			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer);
 		}, nullptr);
 	}
+
+	void Core::prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eGeneral, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image) {
+		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
+			m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer);
+		}, nullptr);
+	}
+
+	void Core::recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer) {
+		recordCommandsToStream(cmdStream, [buffer, this](const vk::CommandBuffer cmdBuffer) {
+			m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer);
+		}, nullptr);
+	}
 	
 	const vk::ImageView& Core::getSwapchainImageView() const {
-    	return m_swapchainImageViews[m_currentSwapchainImageIndex];
+    	return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle());
     }
 	
 }
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
index 85b6eeb5fa413223b7b7f10f77b868252912041b..df7b7bbcb3fe278622cd160593eb750db00ec7b1 100644
--- a/src/vkcv/DrawcallRecording.cpp
+++ b/src/vkcv/DrawcallRecording.cpp
@@ -23,8 +23,6 @@ namespace vkcv {
                 nullptr);
         }
 
-        cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
-
         const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall;
         // char* cast because void* does not support pointer arithmetic
         const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data;
@@ -36,6 +34,12 @@ namespace vkcv {
             pushConstantData.sizePerDrawcall,
             drawcallPushConstantData);
 
-        cmdBuffer.drawIndexed(drawcall.mesh.indexCount, 1, 0, 0, {});
+        if (drawcall.mesh.indexBuffer) {
+            cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
+            cmdBuffer.drawIndexed(drawcall.mesh.indexCount, 1, 0, 0, {});
+        }
+        else {
+            cmdBuffer.draw(drawcall.mesh.indexCount, 1, 0, 0, {});
+        }
     }
 }
\ No newline at end of file
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index f861daeb1cd7de9697e2f649de444666b8b0e63c..9f7fdadc7400fd3e63061f1e40cd494db63a7869 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -19,9 +19,9 @@ namespace vkcv{
 		}
 	}
 
-	Image Image::create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth)
+	Image Image::create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool supportStorage, bool supportColorAttachment)
 	{
-		return Image(manager, manager->createImage(width, height, depth, format));
+		return Image(manager, manager->createImage(width, height, depth, format, supportStorage, supportColorAttachment));
 	}
 	
 	vk::Format Image::getFormat() const {
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index 1e3d19d02d7e86546d142bb64440364407e81824..fff98dc5b56d0ad085c6ebc68acf7230223d9cd1 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -69,6 +69,8 @@ namespace vkcv {
 		for (uint64_t id = 0; id < m_images.size(); id++) {
 			destroyImageById(id);
 		}
+		for (const auto swapchainImage : m_swapchainImages)
+			m_core->getContext().getDevice().destroy(swapchainImage.m_view);
 	}
 	
 	bool isDepthImageFormat(vk::Format format) {
@@ -81,7 +83,7 @@ namespace vkcv {
 		}
 	}
 
-	ImageHandle ImageManager::createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format)
+	ImageHandle ImageManager::createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format, bool supportStorage, bool supportColorAttachment)
 	{
 		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
 		
@@ -91,6 +93,12 @@ namespace vkcv {
 		vk::ImageUsageFlags imageUsageFlags = (
 				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst
 		);
+		if (supportStorage) {
+			imageUsageFlags |= vk::ImageUsageFlagBits::eStorage;
+		}
+		if (supportColorAttachment) {
+			imageUsageFlags |= vk::ImageUsageFlagBits::eColorAttachment;
+		}
 		
 		const bool isDepthFormat = isDepthImageFormat(format);
 		
@@ -204,8 +212,12 @@ namespace vkcv {
 	}
 	
 	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
+
+		if (handle.isSwapchainImage()) {
+			m_swapchainImages[m_currentSwapchainInputImage].m_handle;
+		}
+
 		const uint64_t id = handle.getId();
-		
 		if (id >= m_images.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
@@ -217,8 +229,13 @@ namespace vkcv {
 	}
 	
 	vk::DeviceMemory ImageManager::getVulkanDeviceMemory(const ImageHandle &handle) const {
+
+		if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::ERROR, "Swapchain image has no memory");
+			return nullptr;
+		}
+
 		const uint64_t id = handle.getId();
-		
 		if (id >= m_images.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
@@ -230,27 +247,31 @@ namespace vkcv {
 	}
 	
 	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
 		
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages[m_currentSwapchainInputImage].m_view;
+		}
+
+		const uint64_t id = handle.getId();
 		if (id >= m_images.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return nullptr;
 		}
 		
-		auto& image = m_images[id];
-		
-		return image.m_view;
+		return m_images[id].m_view;
 	}
 	
 	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
-		const uint64_t id = handle.getId();
+		uint64_t id = handle.getId();
 		
-		if (id >= m_images.size()) {
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 		
-		auto& image = m_images[id];
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
 		
 		SubmitInfo submitInfo;
@@ -279,22 +300,57 @@ namespace vkcv {
 		vk::CommandBuffer cmdBuffer) {
 
 		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
 
-		if (id >= m_images.size()) {
+		if (id >= m_images.size() && !isSwapchainImage) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
 		}
 
-		auto& image = m_images[id];
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
 		recordImageBarrier(cmdBuffer, transitionBarrier);
 		image.m_layout = newLayout;
 	}
+
+	void ImageManager::recordImageMemoryBarrier(
+		const ImageHandle& handle,
+		vk::CommandBuffer cmdBuffer) {
+
+		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
+			std::cerr << "Error: ImageManager::recordImageMemoryBarrier invalid handle" << std::endl;
+			return;
+		}
+
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, image.m_layout);
+		recordImageBarrier(cmdBuffer, transitionBarrier);
+	}
+	
+	constexpr uint32_t getChannelsByFormat(vk::Format format) {
+		switch (format) {
+			case vk::Format::eR8Unorm:
+				return 1;
+			case vk::Format::eR8G8B8A8Srgb:
+				return 4;
+			default:
+				std::cerr << "Check format instead of guessing, please!" << std::endl;
+				return 4;
+		}
+	}
 	
 	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
 	{
 		const uint64_t id = handle.getId();
 		
+		if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
+			return;
+		}
+
 		if (id >= m_images.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return;
@@ -306,7 +362,7 @@ namespace vkcv {
 				handle,
 				vk::ImageLayout::eTransferDstOptimal);
 		
-		uint32_t channels = 4; // TODO: check image.m_format
+		uint32_t channels = getChannelsByFormat(image.m_format);
 		const size_t image_size = (
 				image.m_width * image.m_height * image.m_depth * channels
 		);
@@ -368,39 +424,42 @@ namespace vkcv {
 	
 	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
 		const uint64_t id = handle.getId();
-		
-		if (id >= m_images.size()) {
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
-		auto& image = m_images[id];
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		
 		return image.m_width;
 	}
 	
 	uint32_t ImageManager::getImageHeight(const ImageHandle &handle) const {
 		const uint64_t id = handle.getId();
+		const bool isSwapchainImage = handle.isSwapchainImage();
 		
-		if (id >= m_images.size()) {
+		if (id >= m_images.size() && !isSwapchainImage) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
-		auto& image = m_images[id];
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		
 		return image.m_height;
 	}
 	
 	uint32_t ImageManager::getImageDepth(const ImageHandle &handle) const {
 		const uint64_t id = handle.getId();
-		
-		if (id >= m_images.size()) {
+		const bool isSwapchainImage = handle.isSwapchainImage();
+
+		if (id >= m_images.size() && !isSwapchainImage) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return 0;
 		}
 		
-		auto& image = m_images[id];
+		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
 		
 		return image.m_depth;
 	}
@@ -435,13 +494,32 @@ namespace vkcv {
 	vk::Format ImageManager::getImageFormat(const ImageHandle& handle) const {
 
 		const uint64_t id = handle.getId();
+		const bool isSwapchainFormat = handle.isSwapchainImage();
 
-		if (id >= m_images.size()) {
+		if (id >= m_images.size() && !isSwapchainFormat) {
 			vkcv_log(LogLevel::ERROR, "Invalid handle");
 			return vk::Format::eUndefined;
 		}
 
-		return m_images[id].m_format;
+		return isSwapchainFormat ? m_swapchainImages[m_currentSwapchainInputImage].m_format : m_images[id].m_format;
+	}
+
+	void ImageManager::setCurrentSwapchainImageIndex(int index) {
+		m_currentSwapchainInputImage = index;
+	}
+
+	void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views, 
+		uint32_t width, uint32_t height, vk::Format format) {
+
+		// destroy old views
+		for (auto image : m_swapchainImages)
+			m_core->getContext().getDevice().destroyImageView(image.m_view);
+
+		assert(images.size() == views.size());
+		m_swapchainImages.clear();
+		for (int i = 0; i < images.size(); i++) {
+			m_swapchainImages.push_back(Image(images[i], nullptr, views[i], width, height, 1, format, 1, 1));
+		}
 	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index b9fccb25ec16bc1fd9569ab1a94627bd7ff06b18..7bb4cc7ef7951a8677c19f589bd7948dbcedb25d 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -41,6 +41,8 @@ namespace vkcv {
 				vk::Format          format,
 				uint32_t            layers,
 				uint32_t            levels);
+
+			Image();
 		};
 	private:
 		
@@ -48,6 +50,8 @@ namespace vkcv {
 		BufferManager& m_bufferManager;
 		
 		std::vector<Image> m_images;
+		std::vector<Image> m_swapchainImages;
+		int m_currentSwapchainInputImage;
 		
 		ImageManager(BufferManager& bufferManager) noexcept;
 		
@@ -67,7 +71,7 @@ namespace vkcv {
 		ImageManager& operator=(ImageManager&& other) = delete;
 		ImageManager& operator=(const ImageManager& other) = delete;
 		
-		ImageHandle createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format);
+		ImageHandle createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format, bool supportStorage, bool supportColorAttachment);
 		
 		ImageHandle createSwapchainImage();
 		
@@ -86,6 +90,10 @@ namespace vkcv {
 			vk::ImageLayout newLayout, 
 			vk::CommandBuffer cmdBuffer);
 
+		void recordImageMemoryBarrier(
+			const ImageHandle& handle,
+			vk::CommandBuffer cmdBuffer);
+
 		void fillImage(const ImageHandle& handle, void* data, size_t size);
 		
 		[[nodiscard]]
@@ -99,5 +107,10 @@ namespace vkcv {
 		
 		[[nodiscard]]
 		vk::Format getImageFormat(const ImageHandle& handle) const;
+
+		void setCurrentSwapchainImageIndex(int index);
+		void setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views,
+			uint32_t width, uint32_t height, vk::Format format);
+
 	};
 }
\ No newline at end of file
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
index 949e5b9f713f3cac72d67b8a22cae46fc12aef0d..df36442efc2992bf16b6e82245ef9753dad95e5d 100644
--- a/src/vkcv/PipelineManager.cpp
+++ b/src/vkcv/PipelineManager.cpp
@@ -42,6 +42,15 @@ namespace vkcv
 		}
 	}
 
+    vk::PrimitiveTopology primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) {
+        switch (topology) {
+        case(PrimitiveTopology::PointList):     return vk::PrimitiveTopology::ePointList;
+        case(PrimitiveTopology::LineList):      return vk::PrimitiveTopology::eLineList;
+        case(PrimitiveTopology::TriangleList):  return vk::PrimitiveTopology::eTriangleList;
+        default: std::cout << "Error: Unknown primitive topology type" << std::endl; return vk::PrimitiveTopology::eTriangleList;
+        }
+    }
+
     PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, PassManager& passManager)
     {
 		const vk::RenderPass &pass = passManager.getVkPass(config.m_PassHandle);
@@ -124,9 +133,9 @@ namespace vkcv
 
         // input assembly state
         vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
-                {},
-                vk::PrimitiveTopology::eTriangleList,
-                false
+            {},
+            primitiveTopologyToVulkanPrimitiveTopology(config.m_PrimitiveTopology),
+            false
         );
 
         // viewport state
@@ -148,6 +157,14 @@ namespace vkcv
                 0.f,
                 1.f
         );
+        vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization;
+        if (config.m_UseConservativeRasterization) {
+            conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT(
+                {}, 
+                vk::ConservativeRasterizationModeEXT::eOverestimate,
+                0.f);
+            pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization;
+        }
 
         // multisample state
         vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
@@ -238,6 +255,20 @@ namespace vkcv
 
         // graphics pipeline create
         std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
+
+		const char *geometryShaderName = "main";	// outside of if to make sure it stays in scope
+		vk::ShaderModule geometryModule;
+		if (config.m_ShaderProgram.existsShader(ShaderStage::GEOMETRY)) {
+			const vkcv::Shader geometryShader = config.m_ShaderProgram.getShader(ShaderStage::GEOMETRY);
+			const auto& geometryCode = geometryShader.shaderCode;
+			const vk::ShaderModuleCreateInfo geometryModuleInfo({}, geometryCode.size(), reinterpret_cast<const uint32_t*>(geometryCode.data()));
+			if (m_Device.createShaderModule(&geometryModuleInfo, nullptr, &geometryModule) != vk::Result::eSuccess) {
+				return PipelineHandle();
+			}
+			vk::PipelineShaderStageCreateInfo geometryStage({}, vk::ShaderStageFlagBits::eGeometry, geometryModule, geometryShaderName);
+			shaderStages.push_back(geometryStage);
+		}
+
         const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
                 {},
                 static_cast<uint32_t>(shaderStages.size()),
@@ -263,11 +294,18 @@ namespace vkcv
         {
             m_Device.destroy(vertexModule);
             m_Device.destroy(fragmentModule);
+            if (geometryModule) {
+                m_Device.destroy(geometryModule);
+            }
+            m_Device.destroy();
             return PipelineHandle();
         }
 
         m_Device.destroy(vertexModule);
         m_Device.destroy(fragmentModule);
+        if (geometryModule) {
+            m_Device.destroy(geometryModule);
+        }
         
         const uint64_t id = m_Pipelines.size();
         m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config });
diff --git a/src/vkcv/Swapchain.cpp b/src/vkcv/Swapchain.cpp
index 639e949bc442588ece4e13b92bd032ecbd513352..33714adac7cec7c1b5e0013387424c4f865454ab 100644
--- a/src/vkcv/Swapchain.cpp
+++ b/src/vkcv/Swapchain.cpp
@@ -186,7 +186,7 @@ namespace vkcv
                 chosenSurfaceFormat.colorSpace,   // imageColorSpace
                 chosenExtent,   // imageExtent
                 1,  // imageArrayLayers TODO: should we only allow non-stereoscopic applications? yes -> 1, no -> ? "must be greater than 0, less or equal to maxImageArrayLayers"
-                vk::ImageUsageFlagBits::eColorAttachment,  // imageUsage TODO: what attachments? only color? depth?
+                vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,  // imageUsage TODO: what attachments? only color? depth?
                 vk::SharingMode::eExclusive,    // imageSharingMode TODO: which sharing mode? "VK_SHARING_MODE_EXCLUSIV access exclusive to a single queue family, better performance", "VK_SHARING_MODE_CONCURRENT access from multiple queues"
                 0,  // queueFamilyIndexCount, the number of queue families having access to the image(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
                 nullptr,    // pQueueFamilyIndices, the pointer to an array of queue family indices having access to the images(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
@@ -227,7 +227,7 @@ namespace vkcv
 				m_ColorSpace,
 				extent2D,
 				1,
-				vk::ImageUsageFlagBits::eColorAttachment,
+				vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,
 				vk::SharingMode::eExclusive,
 				0,
 				nullptr,