diff --git a/src/main.cpp b/src/main.cpp
index 4ec3b5f..46c75ca 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -85,6 +85,7 @@ private:
     std::vector<vk::raii::Buffer> m_uniformBuffers;
     std::vector<void*> m_uniformBuffersMapped;
     vk::raii::DeviceMemory m_textureImageMemory{ nullptr };
+    uint32_t m_mipLevels;
     vk::raii::Image m_textureImage{ nullptr };
     vk::raii::ImageView m_textureImageView{ nullptr };
     vk::raii::Sampler m_textureSampler{ nullptr };
@@ -498,7 +499,12 @@ private:
         m_swapChainImageViews.reserve( m_swapChainImages.size() );
         for (size_t i = 0; i < m_swapChainImages.size(); ++i) {
             m_swapChainImageViews.emplace_back( 
-                createImageView(m_swapChainImages[i], m_swapChainImageFormat, vk::ImageAspectFlagBits::eColor) 
+                createImageView(
+                    m_swapChainImages[i], 
+                    m_swapChainImageFormat, 
+                    vk::ImageAspectFlagBits::eColor,
+                    1
+                ) 
             );
         }
     }
@@ -1196,6 +1202,7 @@ private:
     void createImage(
         uint32_t width,
         uint32_t height,
+        uint32_t mipLevels,
         vk::Format format,
         vk::ImageTiling tiling,
         vk::ImageUsageFlags usage,
@@ -1208,7 +1215,7 @@ private:
         imageInfo.extent.width = width;
         imageInfo.extent.height = height;
         imageInfo.extent.depth = 1;
-        imageInfo.mipLevels = 1;
+        imageInfo.mipLevels = mipLevels;
         imageInfo.arrayLayers = 1;
         imageInfo.format = format;
         imageInfo.tiling = tiling;
@@ -1232,7 +1239,8 @@ private:
         vk::raii::Image& image,
         vk::Format format,
         vk::ImageLayout oldLayout,
-        vk::ImageLayout newLayout
+        vk::ImageLayout newLayout,
+        uint32_t mipLevels
     ) {
         vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands();
 
@@ -1244,7 +1252,7 @@ private:
         barrier.image = image;
         // barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
         barrier.subresourceRange.baseMipLevel = 0;
-        barrier.subresourceRange.levelCount = 1;
+        barrier.subresourceRange.levelCount = mipLevels;
         barrier.subresourceRange.baseArrayLayer = 0;
         barrier.subresourceRange.layerCount = 1;
 
@@ -1335,6 +1343,7 @@ private:
             throw std::runtime_error("failed to load texture image!");
         }
         vk::DeviceSize imageSize = texWidth * texHeight * 4;
+        m_mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
 
         vk::raii::DeviceMemory stagingBufferMemory{ nullptr };
         vk::raii::Buffer stagingBuffer{ nullptr };
@@ -1357,8 +1366,10 @@ private:
         createImage( 
             texWidth, 
             texHeight,
+            m_mipLevels,
             vk::Format::eR8G8B8A8Srgb,
             vk::ImageTiling::eOptimal,
+            vk::ImageUsageFlagBits::eTransferSrc |
             vk::ImageUsageFlagBits::eTransferDst | 
             vk::ImageUsageFlagBits::eSampled,
             vk::MemoryPropertyFlagBits::eDeviceLocal,
@@ -1370,7 +1381,8 @@ private:
             m_textureImage,
             vk::Format::eR8G8B8A8Srgb,
             vk::ImageLayout::eUndefined,
-            vk::ImageLayout::eTransferDstOptimal
+            vk::ImageLayout::eTransferDstOptimal,
+            m_mipLevels
         );
 
         copyBufferToImage(
@@ -1380,33 +1392,52 @@ private:
             static_cast<uint32_t>(texHeight)
         );
 
-        transitionImageLayout(
+        generateMipmaps(
             m_textureImage,
             vk::Format::eR8G8B8A8Srgb,
-            vk::ImageLayout::eTransferDstOptimal,
-            vk::ImageLayout::eShaderReadOnlyOptimal
+            texWidth,
+            texHeight,
+            m_mipLevels
         );
+
+        // transitionImageLayout(
+        //     m_textureImage,
+        //     vk::Format::eR8G8B8A8Srgb,
+        //     vk::ImageLayout::eTransferDstOptimal,
+        //     vk::ImageLayout::eShaderReadOnlyOptimal,
+        //     m_mipLevels
+        // );
         
     }
     /////////////////////////////////////////////////////////////////
 
     /////////////////////////////////////////////////////////////////
     /// image view and sampler
-    vk::raii::ImageView createImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags) {
+    vk::raii::ImageView createImageView(
+        vk::Image image, 
+        vk::Format format, 
+        vk::ImageAspectFlags aspectFlags,
+        uint32_t mipLevels
+    ) {
         vk::ImageViewCreateInfo viewInfo;
         viewInfo.image = image;
         viewInfo.viewType = vk::ImageViewType::e2D;
         viewInfo.format = format;
         viewInfo.subresourceRange.aspectMask = aspectFlags;
         viewInfo.subresourceRange.baseMipLevel = 0;
-        viewInfo.subresourceRange.levelCount = 1;
+        viewInfo.subresourceRange.levelCount = mipLevels;
         viewInfo.subresourceRange.baseArrayLayer = 0;
         viewInfo.subresourceRange.layerCount = 1;
 
         return m_device.createImageView(viewInfo);
     }
     void createTextureImageView() {
-        m_textureImageView = createImageView(m_textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor);
+        m_textureImageView = createImageView(
+            m_textureImage, 
+            vk::Format::eR8G8B8A8Srgb, 
+            vk::ImageAspectFlagBits::eColor,
+            m_mipLevels
+        );
     }
     void createTextureSampler() {
         vk::SamplerCreateInfo samplerInfo;
@@ -1431,7 +1462,9 @@ private:
         samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
         samplerInfo.mipLodBias = 0.0f;
         samplerInfo.minLod = 0.0f;
-        samplerInfo.maxLod = 0.0f;
+        samplerInfo.maxLod = static_cast<float>(m_mipLevels);
+
+        // samplerInfo.minLod = static_cast<float>(m_mipLevels / 2);
 
         m_textureSampler = m_device.createSampler(samplerInfo);
     }
@@ -1477,6 +1510,7 @@ private:
         createImage(
             m_swapChainExtent.width,
             m_swapChainExtent.height,
+            1,
             depthFormat,
             vk::ImageTiling::eOptimal,
             vk::ImageUsageFlagBits::eDepthStencilAttachment,
@@ -1485,13 +1519,19 @@ private:
             m_depthImageMemory
         );
 
-        m_depthImageView = createImageView(m_depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth);
+        m_depthImageView = createImageView(
+            m_depthImage, 
+            depthFormat, 
+            vk::ImageAspectFlagBits::eDepth,
+            1
+        );
 
         // transitionImageLayout(
         //     m_depthImage,
         //     depthFormat,
         //     vk::ImageLayout::eUndefined,
-        //     vk::ImageLayout::eDepthStencilAttachmentOptimal
+        //     vk::ImageLayout::eDepthStencilAttachmentOptimal,
+        //     1
         // );
     }
     /////////////////////////////////////////////////////////////////
@@ -1594,6 +1634,114 @@ private:
         if (m_pitch < -89.0f) m_pitch = -89.0f;
     }
     /////////////////////////////////////////////////////////////////
+
+    /////////////////////////////////////////////////////////////////
+    /// mipmaps
+    void generateMipmaps(
+        vk::raii::Image& image, 
+        vk::Format imageFormat,
+        int32_t texWidth, 
+        int32_t texHeight, 
+        uint32_t mipLevels
+    ) {
+        // vk::FormatProperties
+        auto formatProperties = m_physicalDevice.getFormatProperties(imageFormat);
+
+        if(!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)){
+            throw std::runtime_error("texture image format does not support linear blitting!");
+        }
+
+
+        auto commandBuffer = beginSingleTimeCommands();
+
+        vk::ImageMemoryBarrier barrier;
+        barrier.image = image;
+        barrier.srcQueueFamilyIndex = vk::QueueFamilyIgnored;
+        barrier.dstQueueFamilyIndex = vk::QueueFamilyIgnored;
+        barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
+        barrier.subresourceRange.baseArrayLayer = 0;
+        barrier.subresourceRange.layerCount = 1;
+        barrier.subresourceRange.levelCount = 1;
+
+        int32_t mipWidth = texWidth;
+        int32_t mipHeight = texHeight;
+
+        for (uint32_t i = 1; i < mipLevels; ++i) {
+            barrier.subresourceRange.baseMipLevel = i - 1;
+            barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
+            barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
+            barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
+            barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
+            commandBuffer.pipelineBarrier(
+                vk::PipelineStageFlagBits::eTransfer,
+                vk::PipelineStageFlagBits::eTransfer,
+                {},
+                nullptr,
+                nullptr,
+                barrier
+            );
+            
+            vk::ImageBlit blit;
+            blit.srcOffsets[0] = vk::Offset3D{ 0, 0, 0 };
+            blit.srcOffsets[1] = vk::Offset3D{ mipWidth, mipHeight, 1 };
+            blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
+            blit.srcSubresource.mipLevel = i - 1;
+            blit.srcSubresource.baseArrayLayer = 0;
+            blit.srcSubresource.layerCount = 1;
+            blit.dstOffsets[0] = vk::Offset3D{ 0, 0, 0 };
+            blit.dstOffsets[1] = vk::Offset3D{ 
+                mipWidth > 1 ? mipWidth / 2 : 1, 
+                mipHeight > 1 ? mipHeight / 2 : 1, 
+                1 
+            };
+            blit.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
+            blit.dstSubresource.mipLevel = i;
+            blit.dstSubresource.baseArrayLayer = 0;
+            blit.dstSubresource.layerCount = 1;
+            commandBuffer.blitImage(
+                image, vk::ImageLayout::eTransferSrcOptimal,
+                image, vk::ImageLayout::eTransferDstOptimal,
+                blit,
+                vk::Filter::eLinear
+            );
+
+            barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
+            barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
+            barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
+            barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
+            commandBuffer.pipelineBarrier(
+                vk::PipelineStageFlagBits::eTransfer,
+                vk::PipelineStageFlagBits::eFragmentShader,
+                {},
+                nullptr,
+                nullptr,
+                barrier
+            );
+
+            if (mipWidth > 1) mipWidth /= 2;
+            if (mipHeight > 1) mipHeight /= 2;
+        }
+
+        barrier.subresourceRange.baseMipLevel = mipLevels - 1;
+        barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
+        barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
+        barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
+        barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
+        commandBuffer.pipelineBarrier(
+            vk::PipelineStageFlagBits::eTransfer,
+            vk::PipelineStageFlagBits::eFragmentShader,
+            {},
+            nullptr,
+            nullptr,
+            barrier
+        );
+
+        if (mipWidth > 1) mipWidth /= 2;
+        if (mipHeight > 1) mipHeight /= 2;
+
+        endSingleTimeCommands( std::move(commandBuffer) );
+    }
+    /////////////////////////////////////////////////////////////////
 };
 
 
