diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 6a4281943778ee21f72ed27d615c5821758ab822..c7512346c9137b77c365e807b679b3950925f535 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -41,7 +41,6 @@ namespace vkcv
 		QueueType queueType;
 		std::vector<vk::Semaphore> waitSemaphores;
 		std::vector<vk::Semaphore> signalSemaphores;
-		vk::Fence fence;
 	};
 
     class Core final
@@ -215,7 +214,14 @@ namespace vkcv
          * @return Image-Object
          */
         [[nodiscard]]
-        Image createImage(vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1, bool supportStorage = false, bool supportColorAttachment = false);
+        Image createImage(
+			vk::Format  format,
+			uint32_t    width,
+			uint32_t    height,
+			uint32_t    depth = 1,
+			bool        createMipChain = false,
+			bool        supportStorage = false,
+			bool        supportColorAttachment = false);
 
         /** TODO:
          *   @param setDescriptions
@@ -261,7 +267,7 @@ namespace vkcv
 		 * @param record Record-command-function
 		 * @param finish Finish-command-function or nullptr
 		 */
-		void recordAndSubmitCommands(
+		void recordAndSubmitCommandsImmediate(
 			const SubmitInfo            &submitInfo, 
 			const RecordCommandFunction &record, 
 			const FinishCommandFunction &finish);
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index 1795b63e844a002564932f5d7ef839746e32fae5..98a0943bcb253142c836cdfd13623a13f18c2871 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -36,13 +36,22 @@ namespace vkcv {
 		void switchLayout(vk::ImageLayout newLayout);
 		
 		void fill(void* data, size_t size = SIZE_MAX);
+		void generateMipChainImmediate();
 	private:
 		ImageManager* const m_manager;
 		const ImageHandle   m_handle;
 
 		Image(ImageManager* manager, const ImageHandle& handle);
 		
-		static Image create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool supportStorage, bool supportColorAttachment);
+		static Image create(
+			ImageManager* manager,
+			vk::Format format,
+			uint32_t width,
+			uint32_t height,
+			uint32_t depth,
+			uint32_t mipCount,
+			bool supportStorage,
+			bool supportColorAttachment);
 		
 	};
 	
diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp
index 6a08cb4f02551cae2fd5d1e3ea4e6ff0bd2b2e04..a9a8bd01df379a0f4615c2c53aee09082d107b1c 100644
--- a/modules/gui/src/vkcv/gui/GUI.cpp
+++ b/modules/gui/src/vkcv/gui/GUI.cpp
@@ -141,7 +141,7 @@ namespace vkcv::gui {
 		
 		const SubmitInfo submitInfo { QueueType::Graphics, {}, {} };
 		
-		m_core.recordAndSubmitCommands(submitInfo, [](const vk::CommandBuffer& commandBuffer) {
+		m_core.recordAndSubmitCommandsImmediate(submitInfo, [](const vk::CommandBuffer& commandBuffer) {
 			ImGui_ImplVulkan_CreateFontsTexture(commandBuffer);
 		}, []() {
 			ImGui_ImplVulkan_DestroyFontUploadObjects();
@@ -214,7 +214,7 @@ namespace vkcv::gui {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		
-		m_core.recordAndSubmitCommands(submitInfo, [&](const vk::CommandBuffer& commandBuffer) {
+		m_core.recordAndSubmitCommandsImmediate(submitInfo, [&](const vk::CommandBuffer& commandBuffer) {
 			const vk::Rect2D renderArea (
 					vk::Offset2D(0, 0),
 					extent
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index d9650f3577cf5633ac915f435b59367a9f993d62..74e6de3ff6d9f80d764b774fea5dc55b4b53b2f8 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -123,6 +123,8 @@ int main(int argc, const char** argv) {
 	vkcv::asset::Texture &tex = mesh.textures[0];
 	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
 	texture.fill(tex.data.data());
+	texture.generateMipChainImmediate();
+	texture.switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
 
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
@@ -142,7 +144,7 @@ int main(int argc, const char** argv) {
 
 	core.writeDescriptorSet(descriptorSet, setWrites);
 
-	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight, 1, false).getHandle();
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index a67177447ec3b795c3c65476e6881b067a33df84..b04fab9a2b61430d24bd7171d9fd3637dd428699 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -65,8 +65,8 @@ 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_voxelImage(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, false, true)),
+	m_dummyRenderTarget(m_corePtr->createImage(voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, false, true)),
 	m_voxelInfoBuffer(m_corePtr->createBuffer<VoxelizationInfo>(vkcv::BufferType::UNIFORM, 1)),
 	m_voxelBuffer(m_corePtr->createBuffer<VoxelBufferContent>(vkcv::BufferType::STORAGE, voxelCount)){
 
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index 81d99f8af37722735742c697171219e012f6b339..1899a3b1e439e67a5ccee7bc21bf7366c1db8b2e 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -151,7 +151,7 @@ int main(int argc, const char** argv) {
 	);
 	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
 	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
+	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution);
 
 	// light info buffer
 	struct LightInfo {
@@ -194,8 +194,10 @@ int main(int argc, const char** argv) {
 
 		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
 
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
+		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h, 1, true));
 		sceneImages.back().fill(sceneTexture.data.data());
+		sceneImages.back().generateMipChainImmediate();
+        sceneImages.back().switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
 
 		vkcv::DescriptorWrites setWrites;
 		setWrites.sampledImageWrites = {
@@ -226,7 +228,7 @@ int main(int argc, const char** argv) {
 	}
 
 	vkcv::ImageHandle depthBuffer = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
-	vkcv::ImageHandle colorBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, true, true).getHandle();
+	vkcv::ImageHandle colorBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
@@ -324,7 +326,7 @@ int main(int argc, const char** argv) {
 
 		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
 			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
-			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, true, true).getHandle();
+			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
 
 			windowWidth = swapchainWidth;
 			windowHeight = swapchainHeight;
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index 4df411c193acffa42401e4f5932d97f531cac9c9..aec96411c5d9e07f200b24fbdcf9fa69e2af53d5 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -159,7 +159,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		core->recordAndSubmitCommands(
+		core->recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
 					const vk::BufferCopy region (
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 4b718db186432f4130f373a67c563c30c48af6a2..1492b1afa563543e6a9eef380295bcb71fef58b8 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -15,7 +15,7 @@
 #include "DescriptorManager.hpp"
 #include "ImageLayoutTransitions.hpp"
 #include "vkcv/CommandStreamManager.hpp"
-
+#include <cmath>
 #include "vkcv/Logger.hpp"
 
 namespace vkcv
@@ -375,7 +375,7 @@ namespace vkcv
 		}
 	}
 	
-	void Core::recordAndSubmitCommands(
+	void Core::recordAndSubmitCommandsImmediate(
 		const SubmitInfo &submitInfo, 
 		const RecordCommandFunction &record, 
 		const FinishCommandFunction &finish)
@@ -390,18 +390,12 @@ namespace vkcv
 		record(cmdBuffer);
 		cmdBuffer.end();
 		
-		vk::Fence waitFence;
-		
-		if (!submitInfo.fence) {
-			waitFence = createFence(device);
-		}
-		
+		vk::Fence waitFence = createFence(device);
+
 		submitCommandBufferToQueue(queue.handle, cmdBuffer, waitFence, submitInfo.waitSemaphores, submitInfo.signalSemaphores);
 		waitForFence(device, waitFence);
 		
-		if (!submitInfo.fence) {
-			device.destroyFence(waitFence);
-		}
+		device.destroyFence(waitFence);
 		
 		device.freeCommandBuffers(cmdPool, cmdBuffer);
 		
@@ -442,9 +436,30 @@ 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, bool supportStorage, bool supportColorAttachment)
+	Image Core::createImage(
+		vk::Format  format,
+		uint32_t    width,
+		uint32_t    height,
+		uint32_t    depth,
+		bool        createMipChain,
+		bool        supportStorage,
+		bool        supportColorAttachment)
 	{
-    	return Image::create(m_ImageManager.get(), format, width, height, depth, supportStorage, supportColorAttachment);
+
+		uint32_t mipCount = 1;
+		if (createMipChain) {
+			mipCount = 1 + (uint32_t)std::floor(std::log2(std::max(width, std::max(height, depth))));
+		}
+
+		return Image::create(
+			m_ImageManager.get(), 
+			format,
+			width,
+			height,
+			depth,
+			mipCount,
+			supportStorage,
+			supportColorAttachment);
 	}
 
     DescriptorSetHandle Core::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index 9f7fdadc7400fd3e63061f1e40cd494db63a7869..dca6358b84aa7bcd0f38e78d3fa7aa49fd11395d 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -19,9 +19,17 @@ namespace vkcv{
 		}
 	}
 
-	Image Image::create(ImageManager* manager, vk::Format format, uint32_t width, uint32_t height, uint32_t depth, bool supportStorage, bool supportColorAttachment)
+	Image Image::create(
+		ImageManager*   manager,
+		vk::Format      format,
+		uint32_t        width,
+		uint32_t        height,
+		uint32_t        depth,
+		uint32_t        mipCount,
+		bool            supportStorage,
+		bool            supportColorAttachment)
 	{
-		return Image(manager, manager->createImage(width, height, depth, format, supportStorage, supportColorAttachment));
+		return Image(manager, manager->createImage(width, height, depth, format, mipCount, supportStorage, supportColorAttachment));
 	}
 	
 	vk::Format Image::getFormat() const {
@@ -52,6 +60,10 @@ namespace vkcv{
 	void Image::fill(void *data, size_t size) {
 		m_manager->fillImage(m_handle, data, size);
 	}
+
+	void Image::generateMipChainImmediate() {
+		m_manager->generateImageMipChainImmediate(m_handle);
+	}
 	
 	Image::Image(ImageManager* manager, const ImageHandle& handle) :
 		m_manager(manager),
diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp
index cb0f90a79d188cd80a5744d8c6ad7718e542d473..8d31c64ccbcbf33e259714f8c581c920738190b4 100644
--- a/src/vkcv/ImageLayoutTransitions.cpp
+++ b/src/vkcv/ImageLayoutTransitions.cpp
@@ -15,7 +15,7 @@ namespace vkcv {
 		vk::ImageSubresourceRange imageSubresourceRange(
 			aspectFlags,
 			0,
-			image.m_levels,
+			image.m_viewPerMip.size(),
 			0,
 			image.m_layers
 		);
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index fff98dc5b56d0ad085c6ebc68acf7230223d9cd1..dd54da39cfe1d832e006e4f673e6aa8e1eb7ee18 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -13,25 +13,23 @@
 namespace vkcv {
 
 	ImageManager::Image::Image(
-		vk::Image           handle,
-		vk::DeviceMemory    memory,
-		vk::ImageView       view,
-		uint32_t            width,
-		uint32_t            height,
-		uint32_t            depth,
-		vk::Format          format,
-		uint32_t            layers,
-		uint32_t            levels)
+		vk::Image                   handle,
+		vk::DeviceMemory            memory,
+		std::vector<vk::ImageView>  views,
+		uint32_t                    width,
+		uint32_t                    height,
+		uint32_t                    depth,
+		vk::Format                  format,
+		uint32_t                    layers)
 		:
 		m_handle(handle),
 		m_memory(memory),
-		m_view(view),
+        m_viewPerMip(views),
 		m_width(width),
 		m_height(height),
 		m_depth(depth),
 		m_format(format),
-		m_layers(layers),
-		m_levels(levels)
+		m_layers(layers)
 	{}
 
 	/**
@@ -69,8 +67,11 @@ 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);
+		for (const auto swapchainImage : m_swapchainImages) {
+			for (const auto view : swapchainImage.m_viewPerMip) {
+				m_core->getContext().getDevice().destroy(view);
+			}
+		}
 	}
 	
 	bool isDepthImageFormat(vk::Format format) {
@@ -83,7 +84,14 @@ namespace vkcv {
 		}
 	}
 
-	ImageHandle ImageManager::createImage(uint32_t width, uint32_t height, uint32_t depth, vk::Format format, bool supportStorage, bool supportColorAttachment)
+	ImageHandle ImageManager::createImage(
+		uint32_t    width, 
+		uint32_t    height, 
+		uint32_t    depth, 
+		vk::Format  format, 
+		uint32_t    mipCount,
+		bool        supportStorage, 
+		bool        supportColorAttachment)
 	{
 		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
 		
@@ -91,7 +99,7 @@ namespace vkcv {
 		
 		vk::ImageCreateFlags createFlags;
 		vk::ImageUsageFlags imageUsageFlags = (
-				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst
+				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc
 		);
 		if (supportStorage) {
 			imageUsageFlags |= vk::ImageUsageFlagBits::eStorage;
@@ -135,11 +143,9 @@ namespace vkcv {
 			imageTiling = vk::ImageTiling::eLinear;
 		}
 		
-		const vk::ImageFormatProperties imageFormatProperties = physicalDevice.getImageFormatProperties(
-				format, imageType, imageTiling, imageUsageFlags
-		);
+		const vk::ImageFormatProperties imageFormatProperties = 
+			physicalDevice.getImageFormatProperties(format, imageType, imageTiling, imageUsageFlags);
 		
-		const uint32_t mipLevels = std::min<uint32_t>(1, imageFormatProperties.maxMipLevels);
 		const uint32_t arrayLayers = std::min<uint32_t>(1, imageFormatProperties.maxArrayLayers);
 		
 		const vk::ImageCreateInfo imageCreateInfo(
@@ -147,7 +153,7 @@ namespace vkcv {
 			imageType,
 			format,
 			vk::Extent3D(width, height, depth),
-			mipLevels,
+			mipCount,
 			arrayLayers,
 			vk::SampleCountFlagBits::e1,
 			imageTiling,
@@ -180,30 +186,33 @@ namespace vkcv {
 			aspectFlags = vk::ImageAspectFlagBits::eColor;
 		}
 		
-		const vk::ImageViewCreateInfo imageViewCreateInfo (
+		std::vector<vk::ImageView> views;
+		for (int mip = 0; mip < mipCount; mip++) {
+			const vk::ImageViewCreateInfo imageViewCreateInfo(
 				{},
 				image,
 				imageViewType,
 				format,
 				vk::ComponentMapping(
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity,
-						vk::ComponentSwizzle::eIdentity
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity,
+					vk::ComponentSwizzle::eIdentity
 				),
 				vk::ImageSubresourceRange(
-						aspectFlags,
-						0,
-						mipLevels,
-						0,
-						arrayLayers
+					aspectFlags,
+					mip,
+					mipCount - mip,
+					0,
+					arrayLayers
 				)
-		);
-		
-		vk::ImageView view = device.createImageView(imageViewCreateInfo);
+			);
+
+			views.push_back(device.createImageView(imageViewCreateInfo));
+		}
 		
 		const uint64_t id = m_images.size();
-		m_images.push_back(Image(image, memory, view, width, height, depth, format, arrayLayers, mipLevels));
+		m_images.push_back(Image(image, memory, views, width, height, depth, format, arrayLayers));
 		return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); });
 	}
 	
@@ -246,10 +255,10 @@ namespace vkcv {
 		return image.m_memory;
 	}
 	
-	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle) const {
+	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, const size_t mipLevel) const {
 		
 		if (handle.isSwapchainImage()) {
-			return m_swapchainImages[m_currentSwapchainInputImage].m_view;
+			return m_swapchainImages[m_currentSwapchainInputImage].m_viewPerMip[0];
 		}
 
 		const uint64_t id = handle.getId();
@@ -258,7 +267,14 @@ namespace vkcv {
 			return nullptr;
 		}
 		
-		return m_images[id].m_view;
+		const auto& image = m_images[id];
+
+		if (mipLevel >= m_images.size()) {
+			vkcv_log(LogLevel::ERROR, "Image does not have requested mipLevel");
+			return nullptr;
+		}
+
+		return image.m_viewPerMip[mipLevel];
 	}
 	
 	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
@@ -277,7 +293,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Graphics;
 		
-		m_core->recordAndSubmitCommands(
+		m_core->recordAndSubmitCommandsImmediate(
 			submitInfo,
 			[transitionBarrier](const vk::CommandBuffer& commandBuffer) {
 			// TODO: precise PipelineStageFlagBits, will require a lot of context
@@ -380,7 +396,7 @@ namespace vkcv {
 		SubmitInfo submitInfo;
 		submitInfo.queueType = QueueType::Transfer;
 		
-		m_core->recordAndSubmitCommands(
+		m_core->recordAndSubmitCommandsImmediate(
 				submitInfo,
 				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
 					vk::ImageAspectFlags aspectFlags;
@@ -421,7 +437,75 @@ namespace vkcv {
 				}
 		);
 	}
-	
+
+	void ImageManager::generateImageMipChainImmediate(const ImageHandle& handle) {
+
+		const auto& device = m_core->getContext().getDevice();
+
+		SubmitInfo submitInfo;
+		submitInfo.queueType = QueueType::Graphics;
+
+		if (handle.isSwapchainImage()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "You cannot generate a mip chain for the swapchain, what are you smoking?");
+			return;
+		}
+
+		const auto id = handle.getId();
+		if (id >= m_images.size()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "Invalid image handle");
+			return;
+		}
+		auto& image = m_images[id];
+		switchImageLayoutImmediate(handle, vk::ImageLayout::eGeneral);
+
+		const auto record = [&image, this, handle](const vk::CommandBuffer cmdBuffer) {
+
+			vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
+				vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor;
+
+			uint32_t srcWidth   = image.m_width;
+			uint32_t srcHeight  = image.m_height;
+			uint32_t srcDepth   = image.m_depth;
+
+			auto half = [](uint32_t in) {
+				return std::max<uint32_t>(in / 2, 1);
+			};
+
+			uint32_t dstWidth   = half(image.m_width);
+			uint32_t dstHeight  = half(image.m_height);
+			uint32_t dstDepth   = half(image.m_depth);
+
+			for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
+				uint32_t dstMip = srcMip + 1;
+				vk::ImageBlit region(
+					vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
+					vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
+
+				cmdBuffer.blitImage(
+					image.m_handle,
+					vk::ImageLayout::eGeneral,
+					image.m_handle,
+					vk::ImageLayout::eGeneral,
+					region,
+					vk::Filter::eLinear);
+
+				srcWidth    = dstWidth;
+				srcHeight   = dstHeight;
+				srcDepth    = dstDepth;
+
+				dstWidth    = half(dstWidth);
+				dstHeight   = half(dstHeight);
+				dstDepth    = half(dstDepth);
+				
+				recordImageMemoryBarrier(handle, cmdBuffer);
+			}
+		};
+
+		m_core->recordAndSubmitCommandsImmediate(submitInfo, record, nullptr);
+	}
+
 	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
 		const uint64_t id = handle.getId();
 		const bool isSwapchainImage = handle.isSwapchainImage();
@@ -475,9 +559,11 @@ namespace vkcv {
 
 		const vk::Device& device = m_core->getContext().getDevice();
 		
-		if (image.m_view) {
-			device.destroyImageView(image.m_view);
-			image.m_view = nullptr;
+		for (auto& view : image.m_viewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
 		}
 
 		if (image.m_memory) {
@@ -512,13 +598,16 @@ namespace vkcv {
 		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);
+		for (auto image : m_swapchainImages) {
+			for (const auto& view : image.m_viewPerMip) {
+				m_core->getContext().getDevice().destroyImageView(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));
+			m_swapchainImages.push_back(Image(images[i], nullptr, { views[i] }, width, height, 1, format, 1));
 		}
 	}
 
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index 7bb4cc7ef7951a8677c19f589bd7948dbcedb25d..69ab7943f34bcf3579a4384cbd59e78e5b7aacdf 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -18,29 +18,27 @@ namespace vkcv {
 	public:
 		struct Image
 		{
-			vk::Image           m_handle;
-			vk::DeviceMemory    m_memory;
-			vk::ImageView       m_view;
-			uint32_t            m_width = 0;
-			uint32_t            m_height = 0;
-			uint32_t            m_depth = 0;
-			vk::Format          m_format;
-			uint32_t            m_layers = 1;
-			uint32_t            m_levels = 1;
-			vk::ImageLayout     m_layout = vk::ImageLayout::eUndefined;
+			vk::Image                   m_handle;
+			vk::DeviceMemory            m_memory;
+			std::vector<vk::ImageView>  m_viewPerMip;
+			uint32_t                    m_width     = 0;
+			uint32_t                    m_height    = 0;
+			uint32_t                    m_depth     = 0;
+			vk::Format                  m_format;
+			uint32_t                    m_layers    = 1;
+			vk::ImageLayout             m_layout    = vk::ImageLayout::eUndefined;
 		private:
 			// struct is public so utility functions can access members, but only ImageManager can create Image
 			friend ImageManager;
 			Image(
-				vk::Image           handle,
-				vk::DeviceMemory    memory,
-				vk::ImageView       view,
-				uint32_t            width,
-				uint32_t            height,
-				uint32_t            depth,
-				vk::Format          format,
-				uint32_t            layers,
-				uint32_t            levels);
+				vk::Image                   handle,
+				vk::DeviceMemory            memory,
+				std::vector<vk::ImageView>  views,
+				uint32_t                    width,
+				uint32_t                    height,
+				uint32_t                    depth,
+				vk::Format                  format,
+				uint32_t                    layers);
 
 			Image();
 		};
@@ -71,7 +69,14 @@ 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, bool supportStorage, bool supportColorAttachment);
+		ImageHandle createImage(
+			uint32_t    width, 
+			uint32_t    height, 
+			uint32_t    depth, 
+			vk::Format  format, 
+			uint32_t    mipCount,
+			bool        supportStorage, 
+			bool        supportColorAttachment);
 		
 		ImageHandle createSwapchainImage();
 		
@@ -82,7 +87,7 @@ namespace vkcv {
 		vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle& handle) const;
 		
 		[[nodiscard]]
-		vk::ImageView getVulkanImageView(const ImageHandle& handle) const;
+		vk::ImageView getVulkanImageView(const ImageHandle& handle, const size_t mipLevel = 0) const;
 
 		void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout);
 		void recordImageLayoutTransition(
@@ -95,6 +100,7 @@ namespace vkcv {
 			vk::CommandBuffer cmdBuffer);
 
 		void fillImage(const ImageHandle& handle, void* data, size_t size);
+		void generateImageMipChainImmediate(const ImageHandle& handle);
 		
 		[[nodiscard]]
 		uint32_t getImageWidth(const ImageHandle& handle) const;
diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp
index eb44356f2cbee1caaf4cb0635c8c8937890b06f9..a6ebb95b5e237dcd06ed8041b3f16489f7339d6a 100644
--- a/src/vkcv/SamplerManager.cpp
+++ b/src/vkcv/SamplerManager.cpp
@@ -87,7 +87,7 @@ namespace vkcv {
 				false,
 				vk::CompareOp::eAlways,
 				0.0f,
-				1.0f,
+				16.0f,
 				vk::BorderColor::eIntOpaqueBlack,
 				false
 		);