diff --git a/src/devicelibrary.cpp b/src/devicelibrary.cpp index 4dea9c6..f6e1ee4 100644 --- a/src/devicelibrary.cpp +++ b/src/devicelibrary.cpp @@ -19,14 +19,12 @@ namespace DeviceControl { VkPhysicalDeviceFeatures deviceFeatures; - VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; std::vector swapChainImageViews; - VkQueue graphicsQueue; - VkQueue presentQueue; + struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; @@ -227,8 +225,8 @@ namespace DeviceControl { } if(Global::enableValidationLayers) std::cout << "Created Logical device successfully!\n" << std::endl; - vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, &graphicsQueue); - vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, &presentQueue); + vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, &Global::graphicsQueue); + vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, &Global::presentQueue); } void devicelibrary::createSwapChain(GLFWwindow* window) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(Global::physicalDevice); @@ -284,20 +282,20 @@ namespace DeviceControl { // require you to recreate it and reference the old one specified here, will revisit in a few days. createSwapChainInfo.oldSwapchain = VK_NULL_HANDLE; - if(vkCreateSwapchainKHR(Global::device, &createSwapChainInfo, nullptr, &swapChain) != VK_SUCCESS) { + if(vkCreateSwapchainKHR(Global::device, &createSwapChainInfo, nullptr, &Global::swapChain) != VK_SUCCESS) { throw std::runtime_error("Failed to create the swap chain!!"); } if(Global::enableValidationLayers) std::cout << "Swap Chain created successfully\n" << std::endl; - vkGetSwapchainImagesKHR(Global::device, swapChain, &imageCount, nullptr); + vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(Global::device, swapChain, &imageCount, swapChainImages.data()); + vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, swapChainImages.data()); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; } void devicelibrary::destroySwapChain() { - vkDestroySwapchainKHR(Global::device, swapChain, nullptr); + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); if(Global::enableValidationLayers) std::cout << "Destroyed Swap Chain safely\n" << std::endl; } void devicelibrary::createImageViews() { diff --git a/src/devicelibrary.h b/src/devicelibrary.h index ce2ce22..4ad8cfc 100644 --- a/src/devicelibrary.h +++ b/src/devicelibrary.h @@ -14,6 +14,7 @@ class devicelibrary { void destroyImageViews(); void createCommandPool(); void destroyCommandPool(); + // ---------- Getters & Setters ----------- // VkFormat getImageFormat(); std::vector getSwapChainImageViews(); diff --git a/src/global.cpp b/src/global.cpp index dceeacb..76bbf98 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -14,7 +14,11 @@ namespace Global { VkSurfaceKHR surface = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - + VkSwapchainKHR swapChain = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; + std::vector commandBuffers; + VkQueue graphicsQueue = VK_NULL_HANDLE; + VkQueue presentQueue = VK_NULL_HANDLE; Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { // First we feed in a integer we want to use to hold the number of queued items, that fills it, then we create that amount of default constructed *VkQueueFamilyProperties* structs. // These store the flags, the amount of queued items in the family, and timestamp data. Queue families are simply group collections of tasks we want to get done. diff --git a/src/global.h b/src/global.h index 079c018..2e84176 100644 --- a/src/global.h +++ b/src/global.h @@ -14,6 +14,11 @@ namespace Global { extern const std::vector validationLayers; extern const bool enableValidationLayers; extern VkDevice device; + extern VkCommandPool commandPool; + extern std::vector commandBuffers; + extern VkQueue graphicsQueue; + extern VkQueue presentQueue; + const int MAX_FRAMES_IN_FLIGHT = 2; struct QueueFamilyIndices { // We need to check that the Queue families support graphics operations and window presentation, sometimes they can support one or the other, // therefore, we take into account both for completion. @@ -24,7 +29,7 @@ namespace Global { return graphicsFamily.has_value() && presentFamily.has_value(); } }; - + extern VkSwapchainKHR swapChain; extern VkSurfaceKHR surface; extern VkPhysicalDevice physicalDevice; Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); diff --git a/src/graphics/graphicspipeline.cpp b/src/graphics/graphicspipeline.cpp index 3b17d97..0c8a5df 100644 --- a/src/graphics/graphicspipeline.cpp +++ b/src/graphics/graphicspipeline.cpp @@ -11,8 +11,6 @@ namespace Graphics { }; std::vector swapChainFramebuffers; - VkCommandPool commandPool; - VkCommandBuffer commandBuffer; VkRenderPass renderPass; VkPipelineLayout pipelineLayout; @@ -242,6 +240,7 @@ namespace Graphics { } void graphicspipeline::createCommandPool() { + Global::QueueFamilyIndices queueFamilyIndices = Global::findQueueFamilies(Global::physicalDevice); VkCommandPoolCreateInfo poolInfo{}; @@ -249,28 +248,30 @@ namespace Graphics { poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); - if(vkCreateCommandPool(Global::device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + if(vkCreateCommandPool(Global::device, &poolInfo, nullptr, &Global::commandPool) != VK_SUCCESS) { throw std::runtime_error("Failed to create command pool!"); } if(Global::enableValidationLayers) std::cout << "Command pool created successfully\n" << std::endl; } void graphicspipeline::destroyCommandPool() { - vkDestroyCommandPool(Global::device, commandPool, nullptr); + vkDestroyCommandPool(Global::device, Global::commandPool, nullptr); } void graphicspipeline::createCommandBuffer() { + Global::commandBuffers.resize(Global::MAX_FRAMES_IN_FLIGHT); + VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = commandPool; + allocInfo.commandPool = Global::commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; + allocInfo.commandBufferCount = (uint32_t) Global::commandBuffers.size(); - if(vkAllocateCommandBuffers(Global::device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + if(vkAllocateCommandBuffers(Global::device, &allocInfo, Global::commandBuffers.data()) != VK_SUCCESS) { throw std::runtime_error("Failed to allocate command buffers"); } if(Global::enableValidationLayers) std::cout << "Allocated command buffers successfully\n" << std::endl; } - void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + void graphicspipeline::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = 0; // Optional diff --git a/src/graphics/graphicspipeline.h b/src/graphics/graphicspipeline.h index bad959a..85979cd 100644 --- a/src/graphics/graphicspipeline.h +++ b/src/graphics/graphicspipeline.h @@ -13,5 +13,6 @@ namespace Graphics { void createCommandPool(); void destroyCommandPool(); void createCommandBuffer(); + void recordCommandBuffer(VkCommandBuffer cmndBuffer, uint32_t imageIndex); }; } diff --git a/src/graphics/render.cpp b/src/graphics/render.cpp new file mode 100644 index 0000000..b495add --- /dev/null +++ b/src/graphics/render.cpp @@ -0,0 +1,113 @@ +#include "render.h" +#include "graphicspipeline.h" +namespace RenderPresent { + + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + Graphics::graphicspipeline pipeline; + uint32_t currentFrame = 0; + // At a high level, rendering in Vulkan consists of 5 steps: + // Wait for the previous frame, acquire a image from the swap chain + // record a comman d buffer which draws the scene onto that image + // submit the recorded command buffer and present the image! + void render::drawFrame() { + + vkWaitForFences(Global::device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(Global::device, 1, &inFlightFences[currentFrame]); + + uint32_t imageIndex; + vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + vkResetCommandBuffer(Global::commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + pipeline.recordCommandBuffer(Global::commandBuffers[currentFrame], 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 = &Global::commandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(Global::graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::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[] = {Global::swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(Global::presentQueue, &presentInfo); + currentFrame = (currentFrame + 1) % Global::MAX_FRAMES_IN_FLIGHT; + } + #pragma info + // SEMAPHORES + // Synchronization of execution on the GPU in Vulkan is *explicit* The Order of ops is up to us to + // define the how we want things to run. + // Similarly, Semaphores are used to add order between queue ops. There are 2 kinds of Semaphores; binary, and timeline. + // We are using Binary semaphores, which can be signaled or unsignaled. + // Semaphores are initizalized unsignaled, the way we use them to order queue operations is by providing the same semaphore in one queue op and a wait in another. + // For example: + // VkCommandBuffer QueueOne, QueueTwo = ... + // VkSemaphore semaphore = ... + // enqueue QueueOne, Signal semaphore when done, start now. + // vkQueueSubmit(work: QueueOne, signal: semaphore, wait: none) + // enqueue QueueTwo, wait on semaphore to start + // vkQueueSubmit(work: QueueTwo, signal: None, wait: semaphore) + // FENCES + // Fences are basically semaphores for the CPU! Otherwise known as the host. If the host needs to know when the GPU has finished a task, we use a fence. + // VkCommandBuffer cmndBuf = ... + // VkFence fence = ... + // Start work immediately, signal fence when done. + // vkQueueSubmit(work: cmndBuf, fence: fence) + // vkWaitForFence(fence) + // doStuffOnceFenceDone() + #pragma endinfo + + void render::createSyncObject() { + imageAvailableSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT); + inFlightFences.resize(Global::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 < Global::MAX_FRAMES_IN_FLIGHT; i++) { + if(vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || + vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || + vkCreateFence(Global::device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { + throw std::runtime_error("Failed to create semaphores!"); + } + } + + + } + void destroyFenceSemaphore() { + for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(Global::device, imageAvailableSemaphores[i], nullptr); + vkDestroySemaphore(Global::device, renderFinishedSemaphores[i], nullptr); + vkDestroyFence(Global::device, inFlightFences[i], nullptr); + } + } +} diff --git a/src/graphics/render.h b/src/graphics/render.h new file mode 100644 index 0000000..3d756bb --- /dev/null +++ b/src/graphics/render.h @@ -0,0 +1,11 @@ +#pragma once +#include "../global.h" + + +namespace RenderPresent { +class render { + public: + void drawFrame(); + void createSyncObject(); + }; +} diff --git a/src/main.cpp b/src/main.cpp index 9a3f7c4..548b437 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "devicelibrary.h" // Device Library includes global, redundant to include with it here #include "debug/vulkandebuglibs.h" #include "graphics/graphicspipeline.h" +#include "graphics/render.h" #include #include @@ -25,6 +26,7 @@ private: DeviceControl::devicelibrary deviceLibs; Debug::vulkandebuglibs debugController; Graphics::graphicspipeline graphicsPipeline; + RenderPresent::render renderPresentation; GLFWwindow* window; VkInstance instance; @@ -52,6 +54,7 @@ private: graphicsPipeline.createFramebuffers(); graphicsPipeline.createCommandPool(); graphicsPipeline.createCommandBuffer(); + renderPresentation.createSyncObject(); } void createInstance() { @@ -77,7 +80,9 @@ private: void mainLoop() { // This loop just updates the GLFW window. while (!glfwWindowShouldClose(window)) { glfwPollEvents(); + renderPresentation.drawFrame(); } + vkDeviceWaitIdle(Global::device); } void cleanup() { // Similar to the last handoff, destroy the utils in a safe manner in the library!