Refactored main function to use a singleton patten

This commit is contained in:
Lillian Salehi 2024-10-08 23:26:45 -05:00
parent 29599e4b9a
commit db832a7dae
11 changed files with 220 additions and 128 deletions

View File

@ -18,7 +18,6 @@ namespace DeviceControl {
VkPhysicalDeviceProperties deviceProperties;
VkPhysicalDeviceFeatures deviceFeatures;
std::vector<VkImage> 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<VkImageView> devicelibrary::getSwapChainImageViews() {
return swapChainImageViews;
}
VkExtent2D devicelibrary::getSwapChainExtent() {
return swapChainExtent;
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "global.h"
#include <optional>
#include <vulkan/vulkan_core.h>
namespace DeviceControl {
class devicelibrary {
public:
@ -19,6 +20,7 @@ class devicelibrary {
VkFormat getImageFormat();
std::vector<VkImageView> getSwapChainImageViews();
VkExtent2D getSwapChainExtent();
std::vector<VkFramebuffer> getSwapChainFramebuffers();
};
}

119
src/entrypoint.cpp Normal file
View File

@ -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<EntryApp*>(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();
}

24
src/entrypoint.h Normal file
View File

@ -0,0 +1,24 @@
#include <cstdlib>
#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;
};

View File

@ -1,5 +1,6 @@
#include "global.h"
#include "devicelibrary.h"
#include <vulkan/vulkan_core.h>
namespace Global {
const std::vector<const char*> 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<VkCommandBuffer> 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.

View File

@ -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.

View File

@ -9,7 +9,6 @@ namespace Graphics {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
std::vector<VkFramebuffer> swapChainFramebuffers;
VkRenderPass renderPass;
@ -17,6 +16,8 @@ namespace Graphics {
VkPipeline graphicsPipeline;
DeviceControl::devicelibrary deviceLibs;
std::vector<VkFramebuffer> swapChainFramebuffers;
static std::vector<char> 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<VkFramebuffer> graphicspipeline::getSwapChainFramebuffers() {
return swapChainFramebuffers;
}
}

View File

@ -14,5 +14,6 @@ namespace Graphics {
void destroyCommandPool();
void createCommandBuffer();
void recordCommandBuffer(VkCommandBuffer cmndBuffer, uint32_t imageIndex);
std::vector<VkFramebuffer> getSwapChainFramebuffers();
};
}

View File

@ -1,12 +1,31 @@
#include "render.h"
#include "graphicspipeline.h"
#include "../devicelibrary.h"
#include "../entrypoint.h"
namespace RenderPresent {
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> 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);
}
}

View File

@ -7,5 +7,7 @@ class render {
public:
void drawFrame();
void createSyncObject();
void destroyFenceSemaphores();
void cleanupSwapChain();
};
}

View File

@ -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 <cstdint>
#include <cstring>
#include <cstdlib>
#include <iostream>
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;