diff --git a/src/main.cpp b/src/main.cpp
index 1216823..42439f0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,8 +1,11 @@
 #include <vulkan/vulkan.hpp>
 #include <vulkan/vulkan_raii.hpp>
 #include <GLFW/glfw3.h>
+#define GLM_FORCE_RADIANS
 #include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
 
+#include <chrono>
 #include <iostream>
 #include <fstream>
 #include <vector>
@@ -60,12 +63,16 @@ private:
     vk::raii::Buffer m_vertexBuffer{ nullptr };
     vk::raii::DeviceMemory m_indexBufferMemory{ nullptr };
     vk::raii::Buffer m_indexBuffer{ nullptr };
+    std::vector<vk::raii::DeviceMemory> m_uniformBuffersMemory;
+    std::vector<vk::raii::Buffer> m_uniformBuffers;
+    std::vector<void*> m_uniformBuffersMapped;
     vk::raii::SwapchainKHR m_swapChain{ nullptr };
     std::vector<vk::Image> m_swapChainImages;
     vk::Format m_swapChainImageFormat;
     vk::Extent2D m_swapChainExtent;
     std::vector<vk::raii::ImageView> m_swapChainImageViews;
     vk::raii::RenderPass m_renderPass{ nullptr };
+    vk::raii::DescriptorSetLayout m_descriptorSetLayout{ nullptr };
     vk::raii::PipelineLayout m_pipelineLayout{ nullptr };
     vk::raii::Pipeline m_graphicsPipeline{ nullptr };
     std::vector<vk::raii::Framebuffer> m_swapChainFramebuffers;
@@ -99,11 +106,13 @@ private:
         createSwapChain();
         createImageViews();
         createRenderPass();
+        createDescriptorSetLayout();
         createGraphicsPipeline();
         createFramebuffers();
         createCommandPool();
         createVertexBuffer();
         createIndexBuffer();
+        createUniformBuffers();
         createCommandBuffers();
         createSyncObjects();
     }
@@ -118,6 +127,10 @@ private:
     }
 
     void cleanup() {
+        for(const auto& it : m_uniformBuffersMemory){
+            it.unmapMemory();
+        }
+
         glfwDestroyWindow( m_window );
         glfwTerminate();
     }
@@ -588,6 +601,7 @@ private:
         colorBlending.setAttachments( colorBlendAttachment );
 
         vk::PipelineLayoutCreateInfo pipelineLayoutInfo;
+        pipelineLayoutInfo.setSetLayouts(*m_descriptorSetLayout);
         m_pipelineLayout = m_device.createPipelineLayout( pipelineLayoutInfo );
 
         vk::GraphicsPipelineCreateInfo pipelineInfo;
@@ -727,6 +741,8 @@ private:
         // Only reset the fence if we are submitting work
         m_device.resetFences( *m_inFlightFences[m_currentFrame] );
 
+        updateUniformBuffer(m_currentFrame);
+
         m_commandBuffers[m_currentFrame].reset();
         recordCommandBuffer(m_commandBuffers[m_currentFrame], imageIndex);
 
@@ -956,6 +972,77 @@ private:
         return 0; // optional
     }
     /////////////////////////////////////////////////////////////////
+
+    /////////////////////////////////////////////////////////////////
+    /// descriptor layout and buffer
+    struct UniformBufferObject {
+        glm::mat4 model;
+        glm::mat4 view;
+        glm::mat4 proj;
+    };
+    void createDescriptorSetLayout() {
+        vk::DescriptorSetLayoutBinding uboLayoutBinding;
+        uboLayoutBinding.binding = 0;
+        uboLayoutBinding.descriptorType = vk::DescriptorType::eUniformBuffer;
+        uboLayoutBinding.descriptorCount = 1;
+        uboLayoutBinding.stageFlags = vk::ShaderStageFlagBits::eVertex;
+
+        vk::DescriptorSetLayoutCreateInfo layoutInfo;
+        layoutInfo.setBindings( uboLayoutBinding );
+
+        m_descriptorSetLayout = m_device.createDescriptorSetLayout( layoutInfo );
+    }
+    void createUniformBuffers() {
+        vk::DeviceSize bufferSize  = sizeof(UniformBufferObject);
+
+        m_uniformBuffers.reserve(MAX_FRAMES_IN_FLIGHT);
+        m_uniformBuffersMemory.reserve(MAX_FRAMES_IN_FLIGHT);
+        m_uniformBuffersMapped.reserve(MAX_FRAMES_IN_FLIGHT);
+
+        for(size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+            m_uniformBuffers.emplace_back( nullptr );
+            m_uniformBuffersMemory.emplace_back( nullptr );
+            m_uniformBuffersMapped.emplace_back( nullptr );
+            createBuffer(bufferSize, 
+                vk::BufferUsageFlagBits::eUniformBuffer, 
+                vk::MemoryPropertyFlagBits::eHostVisible | 
+                vk::MemoryPropertyFlagBits::eHostCoherent, 
+                m_uniformBuffers[i], 
+                m_uniformBuffersMemory[i]
+            );
+
+            m_uniformBuffersMapped[i] = m_uniformBuffersMemory[i].mapMemory(0, bufferSize);
+        }
+    }
+    void updateUniformBuffer(uint32_t currentImage) {
+        static auto startTime = std::chrono::high_resolution_clock::now();
+
+        auto currentTime = std::chrono::high_resolution_clock::now();
+        float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
+
+        UniformBufferObject ubo{};
+        ubo.model = glm::rotate(
+            glm::mat4(1.0f), 
+            time * glm::radians(90.0f), 
+            glm::vec3(0.0f, 0.0f, 1.0f)
+        );
+        ubo.view = glm::lookAt(
+            glm::vec3(2.0f, 2.0f, 2.0f), 
+            glm::vec3(0.0f, 0.0f, 0.0f), 
+            glm::vec3(0.0f, 0.0f, 1.0f)
+        );
+        ubo.proj = glm::perspective(
+            glm::radians(45.0f), 
+            static_cast<float>(m_swapChainExtent.width) / m_swapChainExtent.height, 
+            0.1f, 
+            10.0f
+        );
+
+        ubo.proj[1][1] *= -1;
+
+        memcpy(m_uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
+    }
+    /////////////////////////////////////////////////////////////////
 };
 
 int main() {
