Index: vulkan-game.cpp
===================================================================
--- vulkan-game.cpp	(revision ebeb3aa06c69b63f0483c8192aaa3e976a9eb364)
+++ vulkan-game.cpp	(revision 47bff4c38afaacac0cea0f1ebd84ac7c3fd740d4)
@@ -28,4 +28,6 @@
 const int SCREEN_HEIGHT = 600;
 
+const int MAX_FRAMES_IN_FLIGHT = 2;
+
 #ifdef NDEBUG
    const bool enableValidationLayers = false;
@@ -116,6 +118,14 @@
       VkPipelineLayout pipelineLayout;
       VkPipeline graphicsPipeline;
+      VkCommandPool commandPool;
 
       vector<VkFramebuffer> swapChainFramebuffers;
+      vector<VkCommandBuffer> commandBuffers;
+
+      vector<VkSemaphore> imageAvailableSemaphores;
+      vector<VkSemaphore> renderFinishedSemaphores;
+      vector<VkFence> inFlightFences;
+
+      size_t currentFrame = 0;
 
       // both SDL and GLFW create window functions return NULL on failure
@@ -154,4 +164,7 @@
          createGraphicsPipeline();
          createFramebuffers();
+         createCommandPool();
+         createCommandBuffers();
+         createSyncObjects();
       }
 
@@ -585,4 +598,12 @@
          subpass.pColorAttachments = &colorAttachmentRef;
 
+         VkSubpassDependency  dependency = {};
+         dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+         dependency.dstSubpass = 0;
+         dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+         dependency.srcAccessMask = 0;
+         dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+         dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
          VkRenderPassCreateInfo renderPassInfo = {};
          renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
@@ -591,4 +612,6 @@
          renderPassInfo.subpassCount = 1;
          renderPassInfo.pSubpasses = &subpass;
+         renderPassInfo.dependencyCount = 1;
+         renderPassInfo.pDependencies = &dependency;
 
          if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
@@ -749,4 +772,83 @@
       }
 
+      void createCommandPool() {
+         QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
+
+         VkCommandPoolCreateInfo poolInfo = {};
+         poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+         poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
+         poolInfo.flags = 0;
+
+         if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
+            throw runtime_error("failed to create command pool!");
+         }
+      }
+
+      void createCommandBuffers() {
+         commandBuffers.resize(swapChainFramebuffers.size());
+
+         VkCommandBufferAllocateInfo allocInfo = {};
+         allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+         allocInfo.commandPool = commandPool;
+         allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+         allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
+
+         if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
+            throw runtime_error("failed to create command buffers!");
+         }
+
+         for (size_t i = 0; i < commandBuffers.size(); i++) {
+            VkCommandBufferBeginInfo beginInfo = {};
+            beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+            beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+            beginInfo.pInheritanceInfo = nullptr;
+
+            if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
+               throw runtime_error("failed to begin recording command buffer!");
+            }
+
+            VkRenderPassBeginInfo renderPassInfo = {};
+            renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+            renderPassInfo.renderPass = renderPass;
+            renderPassInfo.framebuffer = swapChainFramebuffers[i];
+            renderPassInfo.renderArea.offset = { 0, 0 };
+            renderPassInfo.renderArea.extent = swapChainExtent;
+
+            VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
+            renderPassInfo.clearValueCount = 1;
+            renderPassInfo.pClearValues = &clearColor;
+
+            vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
+            vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+            vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
+            vkCmdEndRenderPass(commandBuffers[i]);
+
+            if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
+               throw runtime_error("failed to record command buffer!");
+            }
+         }
+      }
+
+      void createSyncObjects() {
+         imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+         renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+         inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
+
+         VkSemaphoreCreateInfo semaphoreInfo = {};
+         semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+         VkFenceCreateInfo fenceInfo = {};
+         fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+         fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+         for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+            if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
+               vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
+               vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
+               throw runtime_error("failed to create synchronization objects for a frame!");
+            }
+         }
+      }
+
       void mainLoop() {
          // TODO: Create some generic event-handling functions in game-gui-*
@@ -765,15 +867,70 @@
                   quit = true;
                }
-
-               /**/
-               SDL_FillRect(sdlSurface, nullptr, SDL_MapRGB(sdlSurface->format, 0xFF, 0xFF, 0xFF));
-
-               SDL_UpdateWindowSurface(window);
-               /**/
-            }
-         }
+            }
+
+            drawFrame();
+
+            //SDL_FillRect(sdlSurface, nullptr, SDL_MapRGB(sdlSurface->format, 0x00, 0x99, 0x99));
+            //SDL_UpdateWindowSurface(window);
+         }
+
+         vkDeviceWaitIdle(device);
+      }
+
+      void drawFrame() {
+         vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, numeric_limits<uint64_t>::max());
+         vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+         uint32_t imageIndex;
+
+         vkAcquireNextImageKHR(device, swapChain, numeric_limits<uint64_t>::max(), imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+
+         VkSubmitInfo submitInfo = {};
+         submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+
+         VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
+         VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+
+         submitInfo.waitSemaphoreCount = 1;
+         submitInfo.pWaitSemaphores = waitSemaphores;
+         submitInfo.pWaitDstStageMask = waitStages;
+         submitInfo.commandBufferCount = 1;
+         submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
+
+         VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
+
+         submitInfo.signalSemaphoreCount = 1;
+         submitInfo.pSignalSemaphores = signalSemaphores;
+
+         if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
+            throw runtime_error("failed to submit draw command buffer!");
+         }
+
+         VkPresentInfoKHR presentInfo = {};
+         presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+
+         presentInfo.waitSemaphoreCount = 1;
+         presentInfo.pWaitSemaphores = signalSemaphores;
+
+         VkSwapchainKHR swapChains[] = { swapChain };
+         presentInfo.swapchainCount = 1;
+         presentInfo.pSwapchains = swapChains;
+         presentInfo.pImageIndices = &imageIndex;
+         presentInfo.pResults = nullptr;
+
+         vkQueuePresentKHR(presentQueue, &presentInfo);
+
+         currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
       }
 
       void cleanup() {
+         for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+            vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
+            vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
+            vkDestroyFence(device, inFlightFences[i], nullptr);
+         }
+
+         vkDestroyCommandPool(device, commandPool, nullptr);
+
          for (auto framebuffer : swapChainFramebuffers) {
             vkDestroyFramebuffer(device, framebuffer, nullptr);
