diff --git a/.gitignore b/.gitignore
index d2bf98a016f588760241f9dc7f90f6197c458404..7ee4ff1903e902c4715c6e2b0c3e784ed5755aaf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-<<<<<<< HEAD
-=======
-
->>>>>>> develop
 # IDE specific files
 .project
 .cproject
@@ -19,3 +15,6 @@ cmake-build-release/
 *.exe
 *.ilk
 *.pdb
+
+# GUI configuration files
+imgui.ini
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 318f3e931e557421a5e9275735174cdee7947453..33b70018e368ecc3ad019ea33e57485814eb233a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -14,7 +14,7 @@ build_ubuntu_gcc:
       - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
-    - ubuntu-gcc
+    - ubuntu-gcc-cached
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
   timeout: 10m
@@ -37,11 +37,11 @@ build_win10_msvc:
       - $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i
   stage: build
   tags: 
-    - win10-msvc
+    - win10-msvc-cached
   variables:
     GIT_SUBMODULE_STRATEGY: recursive
   timeout: 10m
-  retry: 1
+  retry: 0
   script:
     - cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\'
     - .\Launch-VsDevShell.ps1
diff --git a/.gitmodules b/.gitmodules
index 62938a4b1ff2c6787b619cc2c18ef91cb0f0f679..e0aaf2d17c340f98ae875f7e0f1238bfe04f7e5d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -19,3 +19,6 @@
 [submodule "modules/shader_compiler/lib/glslang"]
 	path = modules/shader_compiler/lib/glslang
 	url = https://github.com/KhronosGroup/glslang.git
+[submodule "modules/gui/lib/imgui"]
+	path = modules/gui/lib/imgui
+	url = https://github.com/ocornut/imgui.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a7f978f81313ecc3a657dd670d2a16db3cd4e8d..bff486150f082c2e96e543436d977cf3112403ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,15 +41,15 @@ if (vkcv_build_debug)
 	endif()
 endif()
 
+# configure everything to use the required dependencies
+include(${vkcv_config}/Libraries.cmake)
+
 # add modules as targets
 add_subdirectory(modules)
 
 # add source files for compilation
 include(${vkcv_config}/Sources.cmake)
 
-# configure everything to use the required dependencies
-include(${vkcv_config}/Libraries.cmake)
-
 message("-- Libraries: [ ${vkcv_libraries} ]")
 message("-- Flags: [ ${vkcv_flags} ]")
 
diff --git a/config/Sources.cmake b/config/Sources.cmake
index 62cec249367995db0217c71455cfcee982c65af3..4397e4978eb022d267571d185a1f122d053a5ea1 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -32,8 +32,8 @@ set(vkcv_sources
 		
 		${vkcv_include}/vkcv/Logger.hpp
 
-		${vkcv_include}/vkcv/SwapChain.hpp
-		${vkcv_source}/vkcv/SwapChain.cpp
+		${vkcv_include}/vkcv/Swapchain.hpp
+		${vkcv_source}/vkcv/Swapchain.cpp
 		
 		${vkcv_include}/vkcv/ShaderStage.hpp
 		
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 3d21e4bbfffef180c62a10b61add2ac015e52aa9..dab18892b892aff9564a6d86b9252789ea3c2b03 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -8,7 +8,7 @@
 #include <vulkan/vulkan.hpp>
 
 #include "vkcv/Context.hpp"
-#include "vkcv/SwapChain.hpp"
+#include "vkcv/Swapchain.hpp"
 #include "vkcv/Window.hpp"
 #include "vkcv/PassConfig.hpp"
 #include "vkcv/Handles.hpp"
@@ -41,6 +41,7 @@ namespace vkcv
 		QueueType queueType;
 		std::vector<vk::Semaphore> waitSemaphores;
 		std::vector<vk::Semaphore> signalSemaphores;
+		vk::Fence fence;
 	};
 
     class Core final
@@ -52,7 +53,7 @@ namespace vkcv
          *
          * @param context encapsulates various Vulkan objects
          */
-        Core(Context &&context, Window &window, const SwapChain& swapChain,  std::vector<vk::ImageView> imageViews,
+        Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> imageViews,
 			const CommandResources& commandResources, const SyncResources& syncResources) noexcept;
         // explicit destruction of default constructor
         Core() = delete;
@@ -61,8 +62,8 @@ namespace vkcv
 
         Context m_Context;
 
-        SwapChain                       m_swapchain;
-        const Window&                   m_window;
+        Swapchain                       m_swapchain;
+        Window&                   		m_window;
 
         std::unique_ptr<PassManager>            m_PassManager;
         std::unique_ptr<PipelineManager>        m_PipelineManager;
@@ -76,9 +77,9 @@ namespace vkcv
 		SyncResources       m_SyncResources;
 		uint32_t            m_currentSwapchainImageIndex;
 
-        std::function<void(int, int)> e_resizeHandle;
+		event_handle<int,int> e_resizeHandle;
 
-        static std::vector<vk::ImageView> createSwapchainImageViews( Context &context, SwapChain& swapChain);
+        static std::vector<vk::ImageView> createSwapchainImageViews( Context &context, Swapchain& swapChain);
 
     public:
         /**
@@ -118,6 +119,9 @@ namespace vkcv
 
         [[nodiscard]]
         const Context &getContext() const;
+        
+        [[nodiscard]]
+        const Swapchain& getSwapchain() const;
 
         /**
              * Creates a #Core with given @p applicationName and @p applicationVersion for your application.
@@ -248,8 +252,6 @@ namespace vkcv
 		*/
 		void endFrame();
 
-		vk::Format getSwapchainImageFormat();
-
 		/**
 		 * Submit a command buffer to any queue of selected type. The recording can be customized by a
 		 * custom record-command-function. If the command submission has finished, an optional finish-function
@@ -277,5 +279,8 @@ namespace vkcv
 		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/Event.hpp b/include/vkcv/Event.hpp
index 0836e836e84ff7dfc4931a7cedd65497bf9a89cf..e324917674cd2e1773ee23a9411ab28f6eb0d684 100644
--- a/include/vkcv/Event.hpp
+++ b/include/vkcv/Event.hpp
@@ -3,10 +3,18 @@
 #include <functional>
 
 namespace vkcv {
+	
+	template<typename... T>
+	struct event_handle {
+		uint32_t id;
+	};
 
     template<typename... T>
     struct event_function {
         typedef std::function<void(T...)> type;
+	
+		event_handle<T...> handle;
+        type callback;
     };
 
     /**
@@ -16,7 +24,8 @@ namespace vkcv {
     template<typename... T>
     struct event {
     private:
-        std::vector<typename event_function<T...>::type> m_handles;
+        std::vector< event_function<T...> > m_functions;
+        uint32_t m_id_counter;
 
     public:
 
@@ -25,28 +34,34 @@ namespace vkcv {
          * @param arguments of the given function
          */
         void operator()(T... arguments) {
-            for (auto &handle : this->m_handles) {
-                handle(arguments...);
+            for (auto &function : this->m_functions) {
+				function.callback(arguments...);
             }
         }
 
         /**
          * adds a function handle to the event to be called
-         * @param handle of the function
+         * @param callback of the function
+         * @return handle of the function
          */
-        typename event_function<T...>::type add(typename event_function<T...>::type handle) {
-            this->m_handles.push_back(handle);
-            return handle;
+		event_handle<T...> add(typename event_function<T...>::type callback) {
+			event_function<T...> function;
+			function.handle = { m_id_counter++ };
+			function.callback = callback;
+            this->m_functions.push_back(function);
+            return function.handle;
         }
 
         /**
          * removes a function handle of the event
          * @param handle of the function
          */
-        void remove(typename event_function<T...>::type handle) {
-            this->m_handles.erase(
-                    remove(this->m_handles.begin(), this->m_handles.end(), handle),
-                    this->m_handles.end()
+        void remove(event_handle<T...> handle) {
+            this->m_functions.erase(
+					std::remove_if(this->m_functions.begin(), this->m_functions.end(), [&handle](auto function){
+						return (handle.id == function.handle.id);
+					}),
+                    this->m_functions.end()
             );
         }
 
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index e1eb78ee02ea9142dcbaaa51ab06e3f3bbfa78e1..1795b63e844a002564932f5d7ef839746e32fae5 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -29,9 +29,6 @@ namespace vkcv {
 		
 		[[nodiscard]]
 		uint32_t getDepth() const;
-		
-		[[nodiscard]]
-		vk::ImageLayout getLayout() const;
 
 		[[nodiscard]]
 		vkcv::ImageHandle getHandle() const;
diff --git a/include/vkcv/SwapChain.hpp b/include/vkcv/Swapchain.hpp
similarity index 83%
rename from include/vkcv/SwapChain.hpp
rename to include/vkcv/Swapchain.hpp
index 089205d1633551b4ad9f11d0bdd5540b2bb61bbb..b75fc5a87156ea56061e41b4b0974928c83ffa28 100644
--- a/include/vkcv/SwapChain.hpp
+++ b/include/vkcv/Swapchain.hpp
@@ -7,8 +7,9 @@
 
 namespace vkcv
 {
-    class SwapChain final {
+    class Swapchain final {
     private:
+    	friend class Core;
 
         struct Surface
         {
@@ -21,10 +22,10 @@ namespace vkcv
         Surface m_Surface;
 
         vk::SwapchainKHR m_Swapchain;
-        vk::Format m_SwapchainFormat;
-        vk::ColorSpaceKHR m_SwapchainColorSpace;
-        vk::PresentModeKHR m_SwapchainPresentMode;
-		uint32_t m_SwapchainImageCount;
+        vk::Format m_Format;
+        vk::ColorSpaceKHR m_ColorSpace;
+        vk::PresentModeKHR m_PresentMode;
+		uint32_t m_ImageCount;
 	
 		vk::Extent2D m_Extent;
 	
@@ -39,16 +40,36 @@ namespace vkcv
          * @param format
          */
          // TODO:
-        SwapChain(const Surface &surface,
+        Swapchain(const Surface &surface,
                   vk::SwapchainKHR swapchain,
                   vk::Format format,
                   vk::ColorSpaceKHR colorSpace,
                   vk::PresentModeKHR presentMode,
                   uint32_t imageCount,
 				  vk::Extent2D extent) noexcept;
+	
+		/**
+		 * TODO
+		 *
+		 * @return
+		 */
+		bool shouldUpdateSwapchain() const;
+	
+		/**
+		 * TODO
+		 *
+		 * context
+		 * window
+		 */
+		void updateSwapchain(const Context &context, const Window &window);
+	
+		/**
+		 *
+		 */
+		void signalSwapchainRecreation();
 
     public:
-    	SwapChain(const SwapChain& other);
+    	Swapchain(const Swapchain& other);
 
         /**
          * @return The swapchain linked with the #SwapChain class
@@ -69,7 +90,7 @@ namespace vkcv
          * @return gets the chosen swapchain format
          */
         [[nodiscard]]
-        vk::Format getSwapchainFormat() const;
+        vk::Format getFormat() const;
 
         /**
          * creates a swap chain object out of the given window and the given context
@@ -77,37 +98,17 @@ namespace vkcv
          * @param context of the application
          * @return returns an object of swapChain
          */
-        static SwapChain create(const Window &window, const Context &context);
+        static Swapchain create(const Window &window, const Context &context);
 
         /**
          * Destructor of SwapChain
          */
-        virtual ~SwapChain();
+        virtual ~Swapchain();
 
 		/**
 		 * @return number of images in swapchain
 		*/
-		uint32_t getImageCount();
-		
-		/**
-		 * TODO
-		 *
-		 * @return
-		 */
-		bool shouldUpdateSwapchain() const;
-
-		/**
-		 * TODO
-		 *
-		 * context
-		 * window
-		 */
-		void updateSwapchain(const Context &context, const Window &window);
-		
-		/**
-		 *
-		 */
-        void signalSwapchainRecreation();
+		uint32_t getImageCount() const;
 	
         /**
          * TODO
diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp
index f71671c935a0a5e17bb517c726d75ffff2973532..51d6e2245a8b588334b38254c05276ee0eb10150 100644
--- a/include/vkcv/Window.hpp
+++ b/include/vkcv/Window.hpp
@@ -13,16 +13,19 @@ struct GLFWwindow;
 
 namespace vkcv {
 
-    class Window final {
-    private:
-        GLFWwindow *m_window;
-
-        /**
+    class Window {
+	protected:
+		GLFWwindow *m_window;
+	
+		/**
          *
          * @param GLFWwindow of the class
          */
-        explicit Window(GLFWwindow *window);
-
+		explicit Window(GLFWwindow *window);
+		
+		static GLFWwindow* createGLFWWindow(const char *windowTitle, int width, int height, bool resizable);
+		
+    private:
         /**
          * mouse callback for moving the mouse on the screen
          * @param[in] window The window that received the event.
@@ -58,6 +61,13 @@ namespace vkcv {
          * @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
          */
         static void onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods);
+	
+        /**
+         * char callback for any typed character
+         * @param[in] window The window that received the event
+         * @param[in] c The character that got typed
+         */
+		static void onCharEvent(GLFWwindow *callbackWindow, unsigned int c);
 
     public:
         /**
@@ -95,6 +105,7 @@ namespace vkcv {
         event< double, double > e_mouseScroll;
         event< int, int > e_resize;
         event< int, int, int, int > e_key;
+        event< unsigned int > e_char;
 
         /**
          * returns the current window
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index e8efea4981da3ffb338d508431ed4f92805ed5cd..5edb802b3adf16878c2dec4050d8444278739026 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -2,5 +2,6 @@
 # Add new modules here:
 add_subdirectory(asset_loader)
 add_subdirectory(camera)
+add_subdirectory(gui)
 add_subdirectory(shader_compiler)
 add_subdirectory(testing)
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index 0c5041795ece9cd84e740a04c0d64e4964d0f4c8..5755d6cdc20f0321197b7755e459725eb363fc90 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -25,11 +25,11 @@ namespace vkcv::camera {
      */
     class CameraManager{
     private:
-        std::function<void(int, int, int, int)> m_keyHandle;
-        std::function<void(double, double)> m_mouseMoveHandle;
-        std::function<void(double, double)> m_mouseScrollHandle;
-        std::function<void(int, int, int)> m_mouseButtonHandle;
-        std::function<void(int, int)> m_resizeHandle;
+		event_handle<int, int, int, int> m_keyHandle;
+		event_handle<double, double> m_mouseMoveHandle;
+		event_handle<double, double> m_mouseScrollHandle;
+		event_handle<int, int, int> m_mouseButtonHandle;
+		event_handle<int, int> m_resizeHandle;
 
         Window& m_window;
         std::vector<Camera> m_cameras;
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
index 561596c25a66334b56b3253b998c8c63428ef121..84a0d7ca3049846c4fbb234bab02b5f4d3c7ffd5 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -14,7 +14,13 @@ namespace vkcv::camera {
         m_lastY = static_cast<float>(window.getHeight()) / 2.0f;
     }
 
-    CameraManager::~CameraManager() {}
+    CameraManager::~CameraManager() {
+    	m_window.e_key.remove(m_keyHandle);
+		m_window.e_mouseMove.remove(m_mouseMoveHandle);
+		m_window.e_mouseScroll.remove(m_mouseScrollHandle);
+		m_window.e_mouseButton.remove(m_mouseButtonHandle);
+		m_window.e_resize.remove(m_resizeHandle);
+    }
 
     void CameraManager::bindCameraToEvents() {
         m_keyHandle = m_window.e_key.add( [&](int key, int scancode, int action, int mods) { this->keyCallback(key, scancode, action, mods); });
diff --git a/modules/gui/CMakeLists.txt b/modules/gui/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ce03f16e1f8d421f5b8e6c2fe913c0da04d34598
--- /dev/null
+++ b/modules/gui/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_gui)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_gui_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_gui_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_gui_sources
+		${vkcv_gui_include}/vkcv/gui/GUI.hpp
+		${vkcv_gui_source}/vkcv/gui/GUI.cpp
+		)
+
+# Setup some path variables to load libraries
+set(vkcv_gui_lib lib)
+set(vkcv_gui_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_gui_lib})
+
+# Check and load IMGUI
+include(config/ImGui.cmake)
+
+# adding source files to the module
+add_library(vkcv_gui STATIC ${vkcv_gui_sources} ${vkcv_imgui_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_gui ${vkcv_gui_libraries} vkcv ${vkcv_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_gui SYSTEM BEFORE PRIVATE ${vkcv_gui_includes} ${vkcv_include} ${vkcv_includes})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_gui BEFORE PUBLIC ${vkcv_gui_include} ${vkcv_imgui_includes})
diff --git a/modules/gui/config/ImGui.cmake b/modules/gui/config/ImGui.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..3f55ad05c34783ba0e82c41d2cbc4e5b204d60e7
--- /dev/null
+++ b/modules/gui/config/ImGui.cmake
@@ -0,0 +1,17 @@
+
+if (EXISTS "${vkcv_gui_lib_path}/imgui")
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_glfw.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_vulkan.cpp )
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_draw.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_demo.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_tables.cpp)
+	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/imgui_widgets.cpp)
+	
+	list(APPEND vkcv_imgui_includes ${vkcv_gui_lib}/imgui)
+	list(APPEND vkcv_imgui_includes ${vkcv_gui_lib}/imgui/backend)
+	
+	list(APPEND vkcv_gui_include ${vkcv_gui_lib})
+else()
+	message(WARNING "IMGUI is required..! Update the submodules!")
+endif ()
diff --git a/modules/gui/include/vkcv/gui/GUI.hpp b/modules/gui/include/vkcv/gui/GUI.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d1a9986c5f69bfd9d4392bd5ae50f0b1f8b60642
--- /dev/null
+++ b/modules/gui/include/vkcv/gui/GUI.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "imgui/imgui.h"
+#include "imgui/backends/imgui_impl_glfw.h"
+#include "imgui/backends/imgui_impl_vulkan.h"
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Window.hpp>
+
+namespace vkcv::gui {
+
+	class GUI final {
+	private:
+		Window& m_window;
+		Core& m_core;
+		
+		const Context& m_context;
+		
+		ImGuiContext* m_gui_context;
+		
+		vk::DescriptorPool m_descriptor_pool;
+		vk::RenderPass m_render_pass;
+		
+		event_handle<int,int,int> f_mouseButton;
+		event_handle<double,double> f_mouseScroll;
+		event_handle<int,int,int,int> f_key;
+		event_handle<unsigned int> f_char;
+		
+	public:
+		/**
+		 * Constructor of a new instance of ImGui management
+		 *
+		 * @param core Valid #Core instance of the framework
+		 * @param window Valid #Window instance of the framework
+		 */
+		GUI(Core& core, Window& window);
+		
+		GUI(const GUI& other) = delete;
+		GUI(GUI&& other) = delete;
+		
+		GUI& operator=(const GUI& other) = delete;
+		GUI& operator=(GUI&& other) = delete;
+		
+		/**
+		 * Destructor of a #GUI instance
+		 */
+		virtual ~GUI();
+		
+		/**
+		 * Sets up a new frame for ImGui to draw
+		 */
+		void beginGUI();
+		
+		/**
+		 * Ends a frame for ImGui, renders it and draws it onto
+		 * the currently active swapchain image of the core (ready to present).
+		 */
+		void endGUI();
+		
+	};
+
+}
diff --git a/modules/gui/lib/imgui b/modules/gui/lib/imgui
new file mode 160000
index 0000000000000000000000000000000000000000..d5828cd988db525f27128edeadb1a689cd2d7461
--- /dev/null
+++ b/modules/gui/lib/imgui
@@ -0,0 +1 @@
+Subproject commit d5828cd988db525f27128edeadb1a689cd2d7461
diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a08cb4f02551cae2fd5d1e3ea4e6ff0bd2b2e04
--- /dev/null
+++ b/modules/gui/src/vkcv/gui/GUI.cpp
@@ -0,0 +1,241 @@
+
+#include "vkcv/gui/GUI.hpp"
+
+#include <GLFW/glfw3.h>
+#include <vkcv/Logger.hpp>
+
+namespace vkcv::gui {
+	
+	static void checkVulkanResult(VkResult resultCode) {
+		if (resultCode == 0)
+			return;
+		
+		const auto result = vk::Result(resultCode);
+		
+		vkcv_log(LogLevel::ERROR, "ImGui has a problem with Vulkan! (%s)", vk::to_string(result).c_str());
+	}
+	
+	GUI::GUI(Core& core, Window& window) :
+	m_window(window),
+	m_core(core),
+	m_context(m_core.getContext()),
+	m_gui_context(nullptr) {
+		IMGUI_CHECKVERSION();
+		
+		m_gui_context = ImGui::CreateContext();
+		
+		ImGui_ImplGlfw_InitForVulkan(m_window.getWindow(), false);
+		
+		f_mouseButton = m_window.e_mouseButton.add([&](int button, int action, int mods) {
+			ImGui_ImplGlfw_MouseButtonCallback(m_window.getWindow(), button, action, mods);
+		});
+		
+		f_mouseScroll = m_window.e_mouseScroll.add([&](double xoffset, double yoffset) {
+			ImGui_ImplGlfw_ScrollCallback(m_window.getWindow(), xoffset, yoffset);
+		});
+		
+		f_key = m_window.e_key.add([&](int key, int scancode, int action, int mods) {
+			ImGui_ImplGlfw_KeyCallback(m_window.getWindow(), key, scancode, action, mods);
+		});
+		
+		f_char = m_window.e_char.add([&](unsigned int c) {
+			ImGui_ImplGlfw_CharCallback(m_window.getWindow(), c);
+		});
+		
+		vk::DescriptorPoolSize pool_sizes[] = {
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageImage, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformTexelBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageTexelBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000),
+				vk::DescriptorPoolSize(vk::DescriptorType::eInputAttachment, 1000)
+		};
+		
+		const vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo (
+				vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
+				static_cast<uint32_t>(1000 * IM_ARRAYSIZE(pool_sizes)),
+				static_cast<uint32_t>(IM_ARRAYSIZE(pool_sizes)),
+				pool_sizes
+		);
+		
+		m_descriptor_pool = m_context.getDevice().createDescriptorPool(descriptorPoolCreateInfo);
+		
+		const vk::PhysicalDevice& physicalDevice = m_context.getPhysicalDevice();
+		const Swapchain& swapchain = m_core.getSwapchain();
+		
+		const uint32_t graphicsQueueFamilyIndex = (
+				m_context.getQueueManager().getGraphicsQueues()[0].familyIndex
+		);
+		
+		ImGui_ImplVulkan_InitInfo init_info = {};
+		init_info.Instance = m_context.getInstance();
+		init_info.PhysicalDevice = m_context.getPhysicalDevice();
+		init_info.Device = m_context.getDevice();
+		init_info.QueueFamily = graphicsQueueFamilyIndex;
+		init_info.Queue = m_context.getQueueManager().getGraphicsQueues()[0].handle;
+		init_info.PipelineCache = nullptr;
+		init_info.DescriptorPool = m_descriptor_pool;
+		init_info.Allocator = nullptr;
+		init_info.MinImageCount = swapchain.getImageCount();
+		init_info.ImageCount = swapchain.getImageCount();
+		init_info.CheckVkResultFn = checkVulkanResult;
+		
+		const vk::AttachmentDescription attachment (
+				vk::AttachmentDescriptionFlags(),
+				swapchain.getFormat(),
+				vk::SampleCountFlagBits::e1,
+				vk::AttachmentLoadOp::eLoad,
+				vk::AttachmentStoreOp::eStore,
+				vk::AttachmentLoadOp::eDontCare,
+				vk::AttachmentStoreOp::eDontCare,
+				vk::ImageLayout::eUndefined,
+				vk::ImageLayout::ePresentSrcKHR
+		);
+		
+		const vk::AttachmentReference attachmentReference (
+				0,
+				vk::ImageLayout::eColorAttachmentOptimal
+		);
+		
+		const vk::SubpassDescription subpass (
+				vk::SubpassDescriptionFlags(),
+				vk::PipelineBindPoint::eGraphics,
+				0,
+				nullptr,
+				1,
+				&attachmentReference,
+				nullptr,
+				nullptr,
+				0,
+				nullptr
+		);
+		
+		const vk::SubpassDependency dependency (
+				VK_SUBPASS_EXTERNAL,
+				0,
+				vk::PipelineStageFlagBits::eColorAttachmentOutput,
+				vk::PipelineStageFlagBits::eColorAttachmentOutput,
+				vk::AccessFlags(),
+				vk::AccessFlagBits::eColorAttachmentWrite,
+				vk::DependencyFlags()
+		);
+		
+		const vk::RenderPassCreateInfo passCreateInfo (
+				vk::RenderPassCreateFlags(),
+				1,
+				&attachment,
+				1,
+				&subpass,
+				1,
+				&dependency
+		);
+		
+		m_render_pass = m_context.getDevice().createRenderPass(passCreateInfo);
+		
+		ImGui_ImplVulkan_Init(&init_info, m_render_pass);
+		
+		const SubmitInfo submitInfo { QueueType::Graphics, {}, {} };
+		
+		m_core.recordAndSubmitCommands(submitInfo, [](const vk::CommandBuffer& commandBuffer) {
+			ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
+		}, []() {
+			ImGui_ImplVulkan_DestroyFontUploadObjects();
+		});
+		
+		m_context.getDevice().waitIdle();
+	}
+	
+	GUI::~GUI() {
+		m_context.getDevice().waitIdle();
+		
+		ImGui_ImplVulkan_Shutdown();
+		
+		m_context.getDevice().destroyRenderPass(m_render_pass);
+		m_context.getDevice().destroyDescriptorPool(m_descriptor_pool);
+		
+		ImGui_ImplGlfw_Shutdown();
+		
+		m_window.e_mouseButton.remove(f_mouseButton);
+		m_window.e_mouseScroll.remove(f_mouseScroll);
+		m_window.e_key.remove(f_key);
+		m_window.e_char.remove(f_char);
+		
+		if (m_gui_context) {
+			ImGui::DestroyContext(m_gui_context);
+		}
+	}
+	
+	void GUI::beginGUI() {
+		const Swapchain& swapchain = m_core.getSwapchain();
+		const auto extent = swapchain.getExtent();
+		
+		if ((extent.width > 0) && (extent.height > 0)) {
+			ImGui_ImplVulkan_SetMinImageCount(swapchain.getImageCount());
+		}
+		
+		ImGui_ImplVulkan_NewFrame();
+		ImGui_ImplGlfw_NewFrame();
+		ImGui::NewFrame();
+	}
+	
+	void GUI::endGUI() {
+		ImGui::Render();
+		
+		ImDrawData* drawData = ImGui::GetDrawData();
+		
+		if ((!drawData) ||
+			(drawData->DisplaySize.x <= 0.0f) ||
+			(drawData->DisplaySize.y <= 0.0f)) {
+			return;
+		}
+		
+		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,
+				&swapchainImageView,
+				extent.width,
+				extent.height,
+				1
+		);
+		
+		const vk::Framebuffer framebuffer = m_context.getDevice().createFramebuffer(framebufferCreateInfo);
+		
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Graphics;
+		
+		m_core.recordAndSubmitCommands(submitInfo, [&](const vk::CommandBuffer& commandBuffer) {
+			const vk::Rect2D renderArea (
+					vk::Offset2D(0, 0),
+					extent
+			);
+			
+			const vk::RenderPassBeginInfo beginInfo (
+					m_render_pass,
+					framebuffer,
+					renderArea,
+					0,
+					nullptr
+			);
+			
+			commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline);
+			
+			ImGui_ImplVulkan_RenderDrawData(drawData, commandBuffer);
+			
+			commandBuffer.endRenderPass();
+		}, [&]() {
+			m_context.getDevice().destroyFramebuffer(framebuffer);
+		});
+	}
+	
+}
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7ec54582aac6b16a484b74183036539e91cfe731
--- /dev/null
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -0,0 +1,317 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+
+int main(int argc, const char** argv) {
+	const char* applicationName = "First Mesh";
+
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+	
+	vkcv::Window window = vkcv::Window::create(
+		applicationName,
+		windowWidth,
+		windowHeight,
+		true
+	);
+
+    vkcv::camera::CameraManager cameraManager(window);
+    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    
+    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
+    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camIndex).setYaw(180.0f);
+	
+	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
+
+	window.initEvents();
+
+	vkcv::Core core = vkcv::Core::create(
+		window,
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{},
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::asset::Scene mesh;
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+	int result = vkcv::asset::loadScene(path, mesh);
+
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	}
+	else {
+		std::cout << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	assert(mesh.vertexGroups.size() > 0);
+	auto vertexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::VERTEX,
+			mesh.vertexGroups[0].vertexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+
+	auto indexBuffer = core.createBuffer<uint8_t>(
+			vkcv::BufferType::INDEX,
+			mesh.vertexGroups[0].indexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+	
+	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
+	
+	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
+		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
+	});
+
+	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+		vkcv::VertexBufferBinding(attributes[0].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(attributes[1].offset, vertexBuffer.getVulkanHandle()),
+		vkcv::VertexBufferBinding(attributes[2].offset, vertexBuffer.getVulkanHandle()) };
+
+	const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
+
+	// an example attachment for passes that output to the window
+	const vkcv::AttachmentDescription present_color_attachment(
+		vkcv::AttachmentOperation::STORE,
+		vkcv::AttachmentOperation::CLEAR,
+		core.getSwapchain().getFormat()
+	);
+	
+	const vkcv::AttachmentDescription depth_attachment(
+			vkcv::AttachmentOperation::STORE,
+			vkcv::AttachmentOperation::CLEAR,
+			vk::Format::eD32Sfloat
+	);
+
+	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
+	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
+
+	if (!firstMeshPass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram firstMeshProgram{};
+    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
+    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.getVertexAttachments();
+    
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+    }
+    
+    const vkcv::VertexLayout firstMeshLayout (bindings);
+
+	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] };
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
+
+	const vkcv::PipelineConfig firstMeshPipelineConfig {
+        firstMeshProgram,
+		windowWidth,
+		windowHeight,
+        firstMeshPass,
+        firstMeshLayout,
+		{ core.getDescriptorSet(descriptorSet).layout },
+		true
+	};
+	
+	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
+	
+	if (!firstMeshPipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	//vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
+	//texture.fill(mesh.texture_hack.img);
+    vkcv::asset::Texture &tex = mesh.textures[0];
+    vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+    texture.fill(tex.data.data());
+
+	vkcv::SamplerHandle sampler = core.createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::REPEAT
+	);
+
+    vkcv::SamplerHandle shadowSampler = core.createSampler(
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerFilterType::NEAREST,
+        vkcv::SamplerMipmapMode::NEAREST,
+        vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+    );
+
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
+
+	const std::vector<glm::vec3> instancePositions = {
+		glm::vec3( 0.f, -2.f, 0.f),
+		glm::vec3( 3.f,  0.f, 0.f),
+		glm::vec3(-3.f,  0.f, 0.f),
+		glm::vec3( 0.f,  2.f, 0.f),
+		glm::vec3( 0.f, -5.f, 0.f)
+	};
+
+	std::vector<glm::mat4> modelMatrices;
+	std::vector<vkcv::DrawcallInfo> drawcalls;
+	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
+	for (const auto& position : instancePositions) {
+		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
+		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage }));
+		shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {}));
+	}
+
+	modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
+
+	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
+	std::vector<glm::mat4> mvpLight;
+
+	vkcv::ShaderProgram shadowShader;
+	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
+	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
+
+	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
+	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
+		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
+	};
+	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
+	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
+
+	const uint32_t shadowMapResolution = 1024;
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
+	const vkcv::PipelineConfig shadowPipeConfig {
+		shadowShader, 
+		shadowMapResolution, 
+		shadowMapResolution, 
+		shadowPass,
+        firstMeshLayout,
+		{},
+		false
+	};
+	
+	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+
+	struct LightInfo {
+		glm::vec3 direction;
+		float padding;
+		glm::mat4 lightMatrix;
+	};
+	LightInfo lightInfo;
+	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+
+	vkcv::DescriptorWrites setWrites;
+	setWrites.sampledImageWrites    = { 
+        vkcv::SampledImageDescriptorWrite(0, texture.getHandle()),
+        vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) };
+	setWrites.samplerWrites         = { 
+        vkcv::SamplerDescriptorWrite(1, sampler), 
+        vkcv::SamplerDescriptorWrite(4, shadowSampler) };
+    setWrites.uniformBufferWrites   = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) };
+	core.writeDescriptorSet(descriptorSet, setWrites);
+
+	auto start = std::chrono::system_clock::now();
+	const auto appStartTime = start;
+	while (window.isWindowOpen()) {
+		vkcv::Window::pollEvents();
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
+			continue;
+		}
+		
+		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
+			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
+			
+			windowWidth = swapchainWidth;
+			windowHeight = swapchainHeight;
+		}
+		
+		auto end = std::chrono::system_clock::now();
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		
+		start = end;
+		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
+		
+		const float sunTheta = 0.001f * static_cast<float>(duration.count());
+		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
+
+		const float shadowProjectionSize = 5.f;
+		glm::mat4 projectionLight = glm::ortho(
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize,
+			-shadowProjectionSize,
+			shadowProjectionSize);
+		
+		glm::mat4 vulkanCorrectionMatrix(1.f);
+		vulkanCorrectionMatrix[2][2] = 0.5;
+		vulkanCorrectionMatrix[3][2] = 0.5;
+		projectionLight = vulkanCorrectionMatrix * projectionLight;
+
+		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+
+		lightInfo.lightMatrix = projectionLight * viewLight;
+		lightBuffer.fill({ lightInfo });
+
+		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
+
+		mainPassMatrices.clear();
+		mvpLight.clear();
+		for (const auto& m : modelMatrices) {
+			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
+			mvpLight.push_back(lightInfo.lightMatrix* m);
+		}
+
+		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+
+		vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			shadowPass,
+			shadowPipe,
+			shadowPushConstantData,
+			shadowDrawcalls,
+			{ shadowMap.getHandle() });
+
+		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+            firstMeshPass,
+            firstMeshPipeline,
+			pushConstantData,
+			drawcalls,
+			renderTargets);
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		core.endFrame();
+	}
+	
+	return 0;
+}
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index ed67af878dcd594154361aaec11e85ab6ab3d79d..d9650f3577cf5633ac915f435b59367a9f993d62 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -63,7 +63,7 @@ int main(int argc, const char** argv) {
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchainImageFormat()
+		core.getSwapchain().getFormat()
 	);
 	
 	const vkcv::AttachmentDescription depth_attachment(
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
index 5a59edf7549dfd50877e78e3ca5071bba72098b3..00a862cfd77b522e9d83b51e703ea48ce45e5d5c 100644
--- a/projects/first_scene/src/main.cpp
+++ b/projects/first_scene/src/main.cpp
@@ -112,7 +112,7 @@ int main(int argc, const char** argv) {
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchainImageFormat()
+		core.getSwapchain().getFormat()
 	);
 
 	const vkcv::AttachmentDescription depth_attachment(
diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt
index 7e606b2348ea82486c2a57ee1062ef34150e46a0..ba8c83c06fc804082e6a0a14c3c0414899ef3057 100644
--- a/projects/first_triangle/CMakeLists.txt
+++ b/projects/first_triangle/CMakeLists.txt
@@ -22,7 +22,7 @@ if(MSVC)
 endif()
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera vkcv_shader_compiler)
+target_link_libraries(first_triangle vkcv vkcv_testing vkcv_camera vkcv_shader_compiler vkcv_gui)
diff --git a/projects/first_triangle/shaders/shader.frag b/projects/first_triangle/shaders/shader.frag
index d26446a73020111695aa2c86166205796dfa5e44..080678beb011afe4b03aed3bf7ae7148b77932dc 100644
--- a/projects/first_triangle/shaders/shader.frag
+++ b/projects/first_triangle/shaders/shader.frag
@@ -4,6 +4,6 @@
 layout(location = 0) in vec3 fragColor;
 layout(location = 0) out vec4 outColor;
 
-void main()	{
+void main() {
 	outColor = vec4(fragColor, 1.0);
 }
\ No newline at end of file
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 1c6f96041874a262b481727caf41e0b1142f5570..5a962b8983f6735530b38de5be679096fa997bd5 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -5,6 +5,7 @@
 #include <chrono>
 
 #include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/gui/GUI.hpp>
 
 int main(int argc, const char** argv) {
 	const char* applicationName = "First Triangle";
@@ -28,6 +29,8 @@ int main(int argc, const char** argv) {
 		{},
 		{ "VK_KHR_swapchain" }
 	);
+	
+	vkcv::gui::GUI gui (core, window);
 
 	const auto& context = core.getContext();
 	const vk::Instance& instance = context.getInstance();
@@ -81,7 +84,7 @@ int main(int argc, const char** argv) {
 	const vkcv::AttachmentDescription present_color_attachment(
 		vkcv::AttachmentOperation::STORE,
 		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchainImageFormat());
+		core.getSwapchain().getFormat());
 
 	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
 	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
@@ -215,6 +218,14 @@ int main(int argc, const char** argv) {
 
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		
+		ImGui::Begin("Hello world");
+		ImGui::Text("This is a test!");
+		ImGui::End();
+		
+		gui.endGUI();
 	    
 	    core.endFrame();
 	}
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index b2d1303053f02366912e25bf8bd8871ea9e0d096..554a6ee05c4327f169ceca87b639de8a95e0613b 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -7,6 +7,7 @@
 #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";
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 86d8b518bb5ddb4056d03e4ccecdffc6a710acb0..fd2c595b4305740e801e8d3d50af74521fc3418c 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -35,7 +35,7 @@ namespace vkcv
         		deviceExtensions
 		);
 
-        SwapChain swapChain = SwapChain::create(window, context);
+        Swapchain swapChain = Swapchain::create(window, context);
 
 		 std::vector<vk::ImageView> swapchainImageViews = createSwapchainImageViews( context, swapChain);
 
@@ -53,8 +53,12 @@ namespace vkcv
     {
         return m_Context;
     }
+    
+    const Swapchain& Core::getSwapchain() const {
+    	return m_swapchain;
+    }
 
-    Core::Core(Context &&context, Window &window, const SwapChain& swapChain,  std::vector<vk::ImageView> swapchainImageViews,
+    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),
@@ -74,8 +78,8 @@ namespace vkcv
 		m_CommandStreamManager->init(this);
 
 		m_ImageManager->m_core = this;
-
-		e_resizeHandle = window.e_resize.add( [&](int width, int height) {
+		
+		e_resizeHandle = m_window.e_resize.add( [&](int width, int height) {
 			m_swapchain.signalSwapchainRecreation();
 		});
 
@@ -85,10 +89,12 @@ namespace vkcv
 			swapchainImageViews, 
 			swapChain.getExtent().width,
 			swapChain.getExtent().height,
-			swapChain.getSwapchainFormat());
+			swapChain.getFormat());
 	}
 
 	Core::~Core() noexcept {
+    	m_window.e_resize.remove(e_resizeHandle);
+    	
 		m_Context.getDevice().waitIdle();
 
 		destroyCommandResources(m_Context.getDevice(), m_CommandResources);
@@ -150,7 +156,7 @@ namespace vkcv
 			const auto swapchainViews = createSwapchainImageViews(m_Context, m_swapchain);
 			const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
 
-			m_ImageManager->setSwapchainImages(swapchainImages, swapchainViews, width, height, m_swapchain.getSwapchainFormat());
+			m_ImageManager->setSwapchainImages(swapchainImages, swapchainViews, width, height, m_swapchain.getFormat());
 		}
 		
     	if (acquireSwapchainImage() != Result::SUCCESS) {
@@ -223,7 +229,6 @@ namespace vkcv
 			attachmentsViews.push_back(targetHandle);
 		}
 		
-		vk::Framebuffer framebuffer = nullptr;
         const vk::FramebufferCreateInfo createInfo(
             {},
             renderpass,
@@ -231,16 +236,21 @@ namespace vkcv
             attachmentsViews.data(),
             width,
             height,
-            1);
-        if(m_Context.m_Device.createFramebuffer(&createInfo, nullptr, &framebuffer) != vk::Result::eSuccess)
-        {
+            1
+		);
+		
+		vk::Framebuffer framebuffer = m_Context.m_Device.createFramebuffer(createInfo);
+        
+        if (!framebuffer) {
 			vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer");
             return;
         }
 
-        vk::Viewport dynamicViewport(0.0f, 0.0f,
-            static_cast<float>(width), static_cast<float>(height),
-            0.0f, 1.0f);
+        vk::Viewport dynamicViewport(
+        		0.0f, 0.0f,
+            	static_cast<float>(width), static_cast<float>(height),
+            0.0f, 1.0f
+		);
 
         vk::Rect2D dynamicScissor({0, 0}, {width, height});
 
@@ -340,15 +350,17 @@ namespace vkcv
 		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
 
 		const auto& queueManager = m_Context.getQueueManager();
-		std::array<vk::Semaphore, 2> waitSemaphores{ 
-			m_SyncResources.renderFinished, 
-			m_SyncResources.swapchainImageAcquired };
+		std::array<vk::Semaphore, 2> waitSemaphores{
+			m_SyncResources.renderFinished,
+			m_SyncResources.swapchainImageAcquired
+		};
 
 		const vk::SwapchainKHR& swapchain = m_swapchain.getSwapchain();
 		const vk::PresentInfoKHR presentInfo(
 			waitSemaphores,
 			swapchain,
-			m_currentSwapchainImageIndex);
+			m_currentSwapchainImageIndex
+		);
 		
 		vk::Result result;
 		
@@ -362,10 +374,6 @@ namespace vkcv
 			vkcv_log(LogLevel::ERROR, "Swapchain present failed (%s)", vk::to_string(result).c_str());
 		}
 	}
-
-	vk::Format Core::getSwapchainImageFormat() {
-		return m_swapchain.getSwapchainFormat();
-	}
 	
 	void Core::recordAndSubmitCommands(
 		const SubmitInfo &submitInfo, 
@@ -381,11 +389,19 @@ namespace vkcv
 		beginCommandBuffer(cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
 		record(cmdBuffer);
 		cmdBuffer.end();
-
-		const vk::Fence waitFence = createFence(device);
+		
+		vk::Fence waitFence;
+		
+		if (!submitInfo.fence) {
+			waitFence = createFence(device);
+		}
+		
 		submitCommandBufferToQueue(queue.handle, cmdBuffer, waitFence, submitInfo.waitSemaphores, submitInfo.signalSemaphores);
 		waitForFence(device, waitFence);
-		device.destroyFence(waitFence);
+		
+		if (!submitInfo.fence) {
+			device.destroyFence(waitFence);
+		}
 		
 		device.freeCommandBuffers(cmdPool, cmdBuffer);
 		
@@ -449,7 +465,7 @@ namespace vkcv
 		return m_DescriptorManager->getDescriptorSet(handle);
 	}
 
-    std::vector<vk::ImageView> Core::createSwapchainImageViews( 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() );
@@ -468,7 +484,7 @@ namespace vkcv
                     vk::ImageViewCreateFlags(),
                     image,
                     vk::ImageViewType::e2D,
-                    swapChain.getSwapchainFormat(),
+                    swapChain.getFormat(),
                     componentMapping,
                     subResourceRange);
 
@@ -507,4 +523,9 @@ namespace vkcv
 			m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer);
 		}, nullptr);
 	}
+	
+	const vk::ImageView& Core::getSwapchainImageView() const {
+    	return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle());
+    }
+	
 }
diff --git a/src/vkcv/SwapChain.cpp b/src/vkcv/Swapchain.cpp
similarity index 88%
rename from src/vkcv/SwapChain.cpp
rename to src/vkcv/Swapchain.cpp
index a6e1f2a141be71f7f296ad66a5a64341f370b379..33714adac7cec7c1b5e0013387424c4f865454ab 100644
--- a/src/vkcv/SwapChain.cpp
+++ b/src/vkcv/Swapchain.cpp
@@ -1,4 +1,4 @@
-#include <vkcv/SwapChain.hpp>
+#include <vkcv/Swapchain.hpp>
 #include <utility>
 
 #define GLFW_INCLUDE_VULKAN
@@ -27,44 +27,44 @@ namespace vkcv
         return vk::SurfaceKHR(surface);
     }
 
-    SwapChain::SwapChain(const Surface &surface,
+    Swapchain::Swapchain(const Surface &surface,
                          vk::SwapchainKHR swapchain,
                          vk::Format format,
                          vk::ColorSpaceKHR colorSpace,
                          vk::PresentModeKHR presentMode,
                          uint32_t imageCount,
 						 vk::Extent2D extent) noexcept :
-    m_Surface(surface),
-    m_Swapchain(swapchain),
-    m_SwapchainFormat(format),
-    m_SwapchainColorSpace(colorSpace),
-    m_SwapchainPresentMode(presentMode),
-    m_SwapchainImageCount(imageCount),
-	m_Extent(extent),
-    m_RecreationRequired(false)
+			m_Surface(surface),
+			m_Swapchain(swapchain),
+			m_Format(format),
+			m_ColorSpace(colorSpace),
+			m_PresentMode(presentMode),
+			m_ImageCount(imageCount),
+			m_Extent(extent),
+			m_RecreationRequired(false)
     {}
     
-    SwapChain::SwapChain(const SwapChain &other) :
+    Swapchain::Swapchain(const Swapchain &other) :
 			m_Surface(other.m_Surface),
 			m_Swapchain(other.m_Swapchain),
-			m_SwapchainFormat(other.m_SwapchainFormat),
-			m_SwapchainColorSpace(other.m_SwapchainColorSpace),
-			m_SwapchainPresentMode(other.m_SwapchainPresentMode),
-			m_SwapchainImageCount(other.m_SwapchainImageCount),
+			m_Format(other.m_Format),
+			m_ColorSpace(other.m_ColorSpace),
+			m_PresentMode(other.m_PresentMode),
+			m_ImageCount(other.m_ImageCount),
 			m_Extent(other.m_Extent),
 			m_RecreationRequired(other.m_RecreationRequired.load())
 	{}
 
-    const vk::SwapchainKHR& SwapChain::getSwapchain() const {
+    const vk::SwapchainKHR& Swapchain::getSwapchain() const {
         return m_Swapchain;
     }
 
-    vk::SurfaceKHR SwapChain::getSurface() const {
+    vk::SurfaceKHR Swapchain::getSurface() const {
         return m_Surface.handle;
     }
 
-    vk::Format SwapChain::getSwapchainFormat() const{
-        return m_SwapchainFormat;
+    vk::Format Swapchain::getFormat() const{
+        return m_Format;
     }
 
     /**
@@ -162,7 +162,7 @@ namespace vkcv
      * @param context that keeps instance, physicalDevice and a device.
      * @return swapchain
      */
-    SwapChain SwapChain::create(const Window &window, const Context &context) {
+    Swapchain Swapchain::create(const Window &window, const Context &context) {
         const vk::Instance& instance = context.getInstance();
         const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
         const vk::Device& device = context.getDevice();
@@ -199,7 +199,7 @@ namespace vkcv
 
         vk::SwapchainKHR swapchain = device.createSwapchainKHR(swapchainCreateInfo);
 
-        return SwapChain(surface,
+        return Swapchain(surface,
                          swapchain,
                          chosenSurfaceFormat.format,
                          chosenSurfaceFormat.colorSpace,
@@ -208,11 +208,11 @@ namespace vkcv
 						 chosenExtent);
     }
     
-    bool SwapChain::shouldUpdateSwapchain() const {
+    bool Swapchain::shouldUpdateSwapchain() const {
     	return m_RecreationRequired;
     }
     
-    void SwapChain::updateSwapchain(const Context &context, const Window &window) {
+    void Swapchain::updateSwapchain(const Context &context, const Window &window) {
     	if (!m_RecreationRequired.exchange(false))
     		return;
     	
@@ -222,9 +222,9 @@ namespace vkcv
 		vk::SwapchainCreateInfoKHR swapchainCreateInfo(
 				vk::SwapchainCreateFlagsKHR(),
 				m_Surface.handle,
-				m_SwapchainImageCount,
-				m_SwapchainFormat,
-				m_SwapchainColorSpace,
+				m_ImageCount,
+				m_Format,
+				m_ColorSpace,
 				extent2D,
 				1,
 				vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,
@@ -233,7 +233,7 @@ namespace vkcv
 				nullptr,
 				vk::SurfaceTransformFlagBitsKHR::eIdentity,
 				vk::CompositeAlphaFlagBitsKHR::eOpaque,
-				m_SwapchainPresentMode,
+				m_PresentMode,
 				true,
 				oldSwapchain
 		);
@@ -244,19 +244,19 @@ namespace vkcv
 		m_Extent = extent2D;
     }
 
-    void SwapChain::signalSwapchainRecreation() {
+    void Swapchain::signalSwapchainRecreation() {
 		m_RecreationRequired = true;
     }
     
-    const vk::Extent2D& SwapChain::getExtent() const {
+    const vk::Extent2D& Swapchain::getExtent() const {
     	return m_Extent;
     }
 
-    SwapChain::~SwapChain() {
+    Swapchain::~Swapchain() {
         // needs to be destroyed by creator
     }
 
-	uint32_t SwapChain::getImageCount() {
-		return m_SwapchainImageCount;
+	uint32_t Swapchain::getImageCount() const {
+		return m_ImageCount;
 	}
 }
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index c21271b78f7501721d5c0496d0344dd68e2e7e52..2436619300c24f035cba727481dfce8e1b397c9b 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -24,22 +24,25 @@ namespace vkcv {
             glfwTerminate();
         }
     }
+	
+    GLFWwindow* Window::createGLFWWindow(const char *windowTitle, int width, int height, bool resizable) {
+		if(s_WindowCount == 0) {
+			glfwInit();
+		}
+	
+		s_WindowCount++;
+	
+		width = std::max(width, 1);
+		height = std::max(height, 1);
+	
+		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+		glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
+		
+		return glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
+    }
 
     Window Window::create( const char *windowTitle, int width, int height, bool resizable) {
-        if(s_WindowCount == 0) {
-            glfwInit();
-        }
-        s_WindowCount++;
-
-        width = std::max(width, 1);
-        height = std::max(height, 1);
-
-        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
-        glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
-        GLFWwindow *window;
-        window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
-
-        return Window(window);
+        return Window(createGLFWWindow(windowTitle, width, height, resizable));
     }
 
     void Window::initEvents() {
@@ -55,6 +58,8 @@ namespace vkcv {
         glfwSetKeyCallback(m_window, Window::onKeyEvent);
 
         glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
+	
+		glfwSetCharCallback(m_window, Window::onCharEvent);
     }
 
     void Window::pollEvents() {
@@ -62,7 +67,6 @@ namespace vkcv {
     }
 
     void Window::onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -71,7 +75,6 @@ namespace vkcv {
     }
 
     void Window::onMouseMoveEvent(GLFWwindow *callbackWindow, double x, double y) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -88,7 +91,6 @@ namespace vkcv {
     }
 
     void Window::onResize(GLFWwindow *callbackWindow, int width, int height) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -97,13 +99,20 @@ namespace vkcv {
     }
 
     void Window::onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
             window->e_key(key, scancode, action, mods);
         }
     }
+    
+    void Window::onCharEvent(GLFWwindow *callbackWindow, unsigned int c) {
+		auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+	
+		if (window != nullptr) {
+			window->e_char(c);
+		}
+    }
 
     bool Window::isWindowOpen() const {
         return !glfwWindowShouldClose(m_window);