From db832a7dae0f2a1030bdb1854ff9c2dcb434a091 Mon Sep 17 00:00:00 2001 From: Lillian Salehi Date: Tue, 8 Oct 2024 23:26:45 -0500 Subject: [PATCH] Refactored main function to use a singleton patten --- src/devicelibrary.cpp | 4 +- src/devicelibrary.h | 2 + src/entrypoint.cpp | 119 ++++++++++++++++++++++++++++++ src/entrypoint.h | 24 ++++++ src/global.cpp | 17 +++-- src/global.h | 5 ++ src/graphics/graphicspipeline.cpp | 12 ++- src/graphics/graphicspipeline.h | 1 + src/graphics/render.cpp | 51 ++++++++++++- src/graphics/render.h | 2 + src/main.cpp | 111 +--------------------------- 11 files changed, 220 insertions(+), 128 deletions(-) create mode 100644 src/entrypoint.cpp create mode 100644 src/entrypoint.h diff --git a/src/devicelibrary.cpp b/src/devicelibrary.cpp index f6e1ee4..ff2eaf4 100644 --- a/src/devicelibrary.cpp +++ b/src/devicelibrary.cpp @@ -18,7 +18,6 @@ namespace DeviceControl { VkPhysicalDeviceProperties deviceProperties; VkPhysicalDeviceFeatures deviceFeatures; - std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; @@ -298,6 +297,7 @@ namespace DeviceControl { vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); if(Global::enableValidationLayers) std::cout << "Destroyed Swap Chain safely\n" << std::endl; } + void devicelibrary::createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for(size_t i = 0; i < swapChainImages.size(); i++) { @@ -332,7 +332,6 @@ namespace DeviceControl { } if(Global::enableValidationLayers) std::cout << "Image destroyed safely\n" << std::endl; } - // --------------------------------------- Getters & Setters ------------------------------------------ // VkFormat devicelibrary::getImageFormat() { return swapChainImageFormat; @@ -340,6 +339,7 @@ namespace DeviceControl { std::vector devicelibrary::getSwapChainImageViews() { return swapChainImageViews; } + VkExtent2D devicelibrary::getSwapChainExtent() { return swapChainExtent; } diff --git a/src/devicelibrary.h b/src/devicelibrary.h index 4ad8cfc..c14c4b7 100644 --- a/src/devicelibrary.h +++ b/src/devicelibrary.h @@ -1,6 +1,7 @@ #pragma once #include "global.h" #include +#include namespace DeviceControl { class devicelibrary { public: @@ -19,6 +20,7 @@ class devicelibrary { VkFormat getImageFormat(); std::vector getSwapChainImageViews(); VkExtent2D getSwapChainExtent(); + std::vector getSwapChainFramebuffers(); }; } diff --git a/src/entrypoint.cpp b/src/entrypoint.cpp new file mode 100644 index 0000000..b53e4a6 --- /dev/null +++ b/src/entrypoint.cpp @@ -0,0 +1,119 @@ +#include "entrypoint.h" +DeviceControl::devicelibrary deviceLibs; +Debug::vulkandebuglibs debugController; +Graphics::graphicspipeline graphicsPipeline; +RenderPresent::render renderPresentation; +VkInstance vulkaninstance; + + +void EntryApp::setFramebufferResized(bool setter) { + framebufferResized = setter; +} +bool EntryApp::getFramebufferResized() const { + return framebufferResized; +} +static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->EntryApp::getInstance().setFramebufferResized(true); +} + // Initialize GLFW Window. First, Initialize GLFW lib, disable resizing for + // now, and create window. +void initWindow() { + glfwInit(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + // Settings for the window are set, create window reference. + Global::window = glfwCreateWindow(Global::WIDTH, Global::HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(Global::window, &EntryApp::getInstance()); + glfwSetFramebufferSizeCallback(Global::window, framebufferResizeCallback); +} + + + +void createInstance() { + debugController.checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers. + + // Set application info for the vulkan instance! + VkApplicationInfo appInfo{}; + + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a Application Info structure + appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use + appInfo.applicationVersion = VK_MAKE_VERSION(1,0,0); // Create a Major Minor Patch version number for the application! + appInfo.pEngineName = "Agnosia Engine"; // Give an internal name for the engine running + appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version + appInfo.apiVersion = VK_API_VERSION_1_1; // Tell vulkan what the highest API version we will allow this program to run on + + VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure + createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it. + + debugController.vulkanDebugSetup(createInfo, vulkaninstance); // Handoff to the debug library to wrap the validation libs in! (And set the window up!) +} + + +void initVulkan() { + createInstance(); + debugController.setupDebugMessenger(vulkaninstance); // The debug messenger is out holy grail, it gives us Vulkan related debug info when built with the -DNDEBUG flag (as per the makefile) + deviceLibs.createSurface(vulkaninstance, Global::window); + deviceLibs.pickPhysicalDevice(vulkaninstance); + deviceLibs.createLogicalDevice(); + deviceLibs.createSwapChain(Global::window); + deviceLibs.createImageViews(); + graphicsPipeline.createRenderPass(); + graphicsPipeline.createGraphicsPipeline(); + graphicsPipeline.createFramebuffers(); + graphicsPipeline.createCommandPool(); + graphicsPipeline.createCommandBuffer(); + renderPresentation.createSyncObject(); +} + +void mainLoop() { // This loop just updates the GLFW window. + while (!glfwWindowShouldClose(Global::window)) { + glfwPollEvents(); + renderPresentation.drawFrame(); + } + vkDeviceWaitIdle(Global::device); +} + +void cleanup() { // Similar to the last handoff, destroy the utils in a safe manner in the library! + renderPresentation.cleanupSwapChain(); + graphicsPipeline.destroyGraphicsPipeline(); + graphicsPipeline.destroyRenderPass(); + renderPresentation.destroyFenceSemaphores(); + graphicsPipeline.destroyCommandPool(); + + deviceLibs.destroyImageViews(); + deviceLibs.destroySwapChain(); + + vkDestroyDevice(Global::device, nullptr); + if(Global::enableValidationLayers) { + debugController.DestroyDebugUtilsMessengerEXT(vulkaninstance, nullptr); + } + + deviceLibs.destroySurface(vulkaninstance); + vkDestroyInstance(vulkaninstance, nullptr); + glfwDestroyWindow(Global::window); + glfwTerminate(); +} + +EntryApp& EntryApp::getInstance() { + static EntryApp instance; + return instance; +} +EntryApp::EntryApp() : initialized(false), framebufferResized(false) {} + +void EntryApp::initialize() { + initialized = true; +} +bool EntryApp::isInitialized() const { + return initialized; +} + +void EntryApp::run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + diff --git a/src/entrypoint.h b/src/entrypoint.h new file mode 100644 index 0000000..615b12c --- /dev/null +++ b/src/entrypoint.h @@ -0,0 +1,24 @@ +#include +#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" +class EntryApp { + public: + static EntryApp& getInstance(); + void initialize(); + bool isInitialized() const; + void run(); + void setFramebufferResized(bool frame); + bool getFramebufferResized() const; + private: + EntryApp(); + + EntryApp(const EntryApp&) = delete; + void operator=(const EntryApp&) = delete; + + bool framebufferResized; + bool initialized; + + +}; diff --git a/src/global.cpp b/src/global.cpp index 76bbf98..7e72065 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1,5 +1,6 @@ #include "global.h" #include "devicelibrary.h" +#include namespace Global { const std::vector validationLayers = { @@ -11,14 +12,16 @@ namespace Global { const bool enableValidationLayers = false; #endif - 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; + VkSurfaceKHR surface; + VkDevice device; + VkPhysicalDevice physicalDevice; + VkSwapchainKHR swapChain; + VkCommandPool commandPool; std::vector commandBuffers; - VkQueue graphicsQueue = VK_NULL_HANDLE; - VkQueue presentQueue = VK_NULL_HANDLE; + VkQueue graphicsQueue; + VkQueue presentQueue; + GLFWwindow* window; + 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 2e84176..83a9392 100644 --- a/src/global.h +++ b/src/global.h @@ -19,6 +19,11 @@ namespace Global { extern VkQueue graphicsQueue; extern VkQueue presentQueue; const int MAX_FRAMES_IN_FLIGHT = 2; + extern GLFWwindow* window; + + const uint32_t WIDTH = 800; + const uint32_t HEIGHT = 600; + 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. diff --git a/src/graphics/graphicspipeline.cpp b/src/graphics/graphicspipeline.cpp index 0c8a5df..9801d7a 100644 --- a/src/graphics/graphicspipeline.cpp +++ b/src/graphics/graphicspipeline.cpp @@ -9,7 +9,6 @@ namespace Graphics { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - std::vector swapChainFramebuffers; VkRenderPass renderPass; @@ -17,6 +16,8 @@ namespace Graphics { VkPipeline graphicsPipeline; DeviceControl::devicelibrary deviceLibs; + std::vector swapChainFramebuffers; + static std::vector readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) { @@ -233,12 +234,6 @@ namespace Graphics { } } } - void graphicspipeline::destroyFramebuffers() { - for (auto framebuffer : swapChainFramebuffers) { - vkDestroyFramebuffer(Global::device, framebuffer, nullptr); - } - } - void graphicspipeline::createCommandPool() { Global::QueueFamilyIndices queueFamilyIndices = Global::findQueueFamilies(Global::physicalDevice); @@ -319,4 +314,7 @@ namespace Graphics { throw std::runtime_error("failed to record command buffer!"); } } + std::vector graphicspipeline::getSwapChainFramebuffers() { + return swapChainFramebuffers; + } } diff --git a/src/graphics/graphicspipeline.h b/src/graphics/graphicspipeline.h index 85979cd..953b640 100644 --- a/src/graphics/graphicspipeline.h +++ b/src/graphics/graphicspipeline.h @@ -14,5 +14,6 @@ namespace Graphics { void destroyCommandPool(); void createCommandBuffer(); void recordCommandBuffer(VkCommandBuffer cmndBuffer, uint32_t imageIndex); + std::vector getSwapChainFramebuffers(); }; } diff --git a/src/graphics/render.cpp b/src/graphics/render.cpp index b495add..d845ba4 100644 --- a/src/graphics/render.cpp +++ b/src/graphics/render.cpp @@ -1,12 +1,31 @@ #include "render.h" #include "graphicspipeline.h" +#include "../devicelibrary.h" +#include "../entrypoint.h" namespace RenderPresent { std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; std::vector inFlightFences; Graphics::graphicspipeline pipeline; + DeviceControl::devicelibrary deviceLibs; uint32_t currentFrame = 0; + + void recreateSwapChain() { + vkDeviceWaitIdle(Global::device); + // Don't really wanna do this but I also don't want to create an extra class instance just to call the cleanup function. + for(auto framebuffer : pipeline.getSwapChainFramebuffers()) { + vkDestroyFramebuffer(Global::device, framebuffer, nullptr); + } + for(auto imageView : deviceLibs.getSwapChainImageViews()) { + vkDestroyImageView(Global::device, imageView, nullptr); + } + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); + + deviceLibs.createSwapChain(Global::window); + deviceLibs.createImageViews(); + pipeline.createFramebuffers(); + } // 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 @@ -17,7 +36,14 @@ namespace RenderPresent { vkResetFences(Global::device, 1, &inFlightFences[currentFrame]); uint32_t imageIndex; - vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + vkResetFences(Global::device, 1, &inFlightFences[currentFrame]); vkResetCommandBuffer(Global::commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); pipeline.recordCommandBuffer(Global::commandBuffers[currentFrame], imageIndex); @@ -54,7 +80,13 @@ namespace RenderPresent { presentInfo.pImageIndices = &imageIndex; - vkQueuePresentKHR(Global::presentQueue, &presentInfo); + result = vkQueuePresentKHR(Global::presentQueue, &presentInfo); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || EntryApp::getInstance().getFramebufferResized()) { + EntryApp::getInstance().setFramebufferResized(false); + recreateSwapChain(); + } else if (result != VK_SUCCESS) { + throw std::runtime_error("failed to present swap chain image!"); + } currentFrame = (currentFrame + 1) % Global::MAX_FRAMES_IN_FLIGHT; } #pragma info @@ -70,7 +102,8 @@ namespace RenderPresent { // 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) + // 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 = ... @@ -103,11 +136,21 @@ namespace RenderPresent { } - void destroyFenceSemaphore() { + void render::destroyFenceSemaphores() { 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); } } + void render::cleanupSwapChain() { + for(auto framebuffer : pipeline.getSwapChainFramebuffers()) { + vkDestroyFramebuffer(Global::device, framebuffer, nullptr); + } + for(auto imageView : deviceLibs.getSwapChainImageViews()) { + vkDestroyImageView(Global::device, imageView, nullptr); + } + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); + } + } diff --git a/src/graphics/render.h b/src/graphics/render.h index 3d756bb..fa5adfc 100644 --- a/src/graphics/render.h +++ b/src/graphics/render.h @@ -7,5 +7,7 @@ class render { public: void drawFrame(); void createSyncObject(); + void destroyFenceSemaphores(); + void cleanupSwapChain(); }; } diff --git a/src/main.cpp b/src/main.cpp index 548b437..c1d1aff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,114 +1,9 @@ -#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 -#include -#include - -const uint32_t WIDTH = 800; -const uint32_t HEIGHT = 600; - -// Define a base class structure to handle public and private methods -class TriangleTestApplication { - -public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } - -private: - DeviceControl::devicelibrary deviceLibs; - Debug::vulkandebuglibs debugController; - Graphics::graphicspipeline graphicsPipeline; - RenderPresent::render renderPresentation; - GLFWwindow* window; - VkInstance instance; - - // Initialize GLFW Window. First, Initialize GLFW lib, disable resizing for - // now, and create window. - void initWindow() { - glfwInit(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - - // Settings for the window are set, create window reference. - window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); - } - - void initVulkan() { - createInstance(); - debugController.setupDebugMessenger(instance); // The debug messenger is out holy grail, it gives us Vulkan related debug info when built with the -DNDEBUG flag (as per the makefile) - deviceLibs.createSurface(instance, window); - deviceLibs.pickPhysicalDevice(instance); - deviceLibs.createLogicalDevice(); - deviceLibs.createSwapChain(window); - deviceLibs.createImageViews(); - graphicsPipeline.createRenderPass(); - graphicsPipeline.createGraphicsPipeline(); - graphicsPipeline.createFramebuffers(); - graphicsPipeline.createCommandPool(); - graphicsPipeline.createCommandBuffer(); - renderPresentation.createSyncObject(); - } - - void createInstance() { - debugController.checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers. - - // Set application info for the vulkan instance! - VkApplicationInfo appInfo{}; - - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a Application Info structure - appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use - appInfo.applicationVersion = VK_MAKE_VERSION(1,0,0); // Create a Major Minor Patch version number for the application! - appInfo.pEngineName = "Agnosia Engine"; // Give an internal name for the engine running - appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version - appInfo.apiVersion = VK_API_VERSION_1_1; // Tell vulkan what the highest API version we will allow this program to run on - - VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure - createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it. - - debugController.vulkanDebugSetup(createInfo, instance); // Handoff to the debug library to wrap the validation libs in! (And set the window up!) - } - - 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! - graphicsPipeline.destroyCommandPool(); - graphicsPipeline.destroyFramebuffers(); - graphicsPipeline.destroyGraphicsPipeline(); - graphicsPipeline.destroyRenderPass(); - deviceLibs.destroyImageViews(); - deviceLibs.destroySwapChain(); - vkDestroyDevice(Global::device, nullptr); - if(Global::enableValidationLayers) { - debugController.DestroyDebugUtilsMessengerEXT(instance, nullptr); - } - - deviceLibs.destroySurface(instance); - vkDestroyInstance(instance, nullptr); - glfwDestroyWindow(window); - glfwTerminate(); - } -}; - +#include "entrypoint.h" int main() { - TriangleTestApplication app; + EntryApp::getInstance().initialize(); try { - app.run(); + EntryApp::getInstance().run(); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE;