diff --git a/Makefile b/Makefile index c06b349..08e8e73 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CPPFLAGS=-g LDFLAGS=-lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi -DEBUGFLAGS=-DDEBUG +DEBUGFLAGS=-DDEBUG -fsanitize=address SRC=$(shell find . -name *.cpp) OBJ=$(SRC:%.cpp=%.o) diff --git a/src/DeviceLibrary.cpp b/src/DeviceLibrary.cpp index 8243cd2..cde17ee 100644 --- a/src/DeviceLibrary.cpp +++ b/src/DeviceLibrary.cpp @@ -1,127 +1,155 @@ #include "DeviceLibrary.h" -#include "debug/VulkanDebugLibs.h" -#include "global.h" #include #include #include +#include #include #include -using namespace AgnosiaEngine; -VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; -VkPhysicalDeviceProperties deviceProperties; -VkPhysicalDeviceFeatures deviceFeatures; -VulkanDebugLibs debug; -VkQueue graphicsQueue; +namespace DeviceControl { -#ifdef DEBUG - const bool enableValidationLayers = true; -#else - const bool enableValidationLayers = false; -#endif -struct QueueFamilyIndices { - std::optional graphicsFamily; + VkSurfaceKHR surface; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkPhysicalDeviceProperties deviceProperties; + VkPhysicalDeviceFeatures deviceFeatures; - bool isComplete() { - return graphicsFamily.has_value(); - } -}; - -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. - // Next, we check the flags of the queueFamily item, use a bitwise and to see if they match, i.e. support graphical operations, then return that to notify that we have at least one family that supports VK_QUEUE_GRAPHICS_BIT. - // Which means this device supports graphical operations! - QueueFamilyIndices indices; + VkQueue graphicsQueue; + VkQueue presentQueue; - uint32_t queueFamilyCount; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + 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. + std::optional graphicsFamily; + std::optional presentFamily; - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - int i = 0; - for(const auto& queueFamily : queueFamilies) { - if(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - indices.graphicsFamily = i; + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); } - if(indices.isComplete()) { - break; - } - i++; - } - return indices; -} + }; -bool isDeviceSuitable(VkPhysicalDevice device) { - // These two are simple, create a structure to hold the apiVersion, driverVersion, vendorID, deviceID and type, name, and a few other settings. - // Then populate it by passing in the device and the structure reference. - vkGetPhysicalDeviceProperties(device, &deviceProperties); - // Similarly, we can pass in the device and a deviceFeatures struct, this is quite special, it holds a struct of optional features the GPU can perform. - // Some, like a geometry shader, and stereoscopic rendering (multiViewport) we want, so we dont return true without them. - vkGetPhysicalDeviceFeatures(device, &deviceFeatures); - // We need to find a device that supports graphical operations, or else we cant do much with it! This function just runs over all the queueFamilies and sees if there - // is a queue family with the VK_QUEUE_GRAPHICS_BIT flipped! - QueueFamilyIndices indices = findQueueFamilies(device); + 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. + // Next, we check the flags of the queueFamily item, use a bitwise and to see if they match, i.e. support graphical operations, then return that to notify that we have at least one family that supports VK_QUEUE_GRAPHICS_BIT. + // Which means this device supports graphical operations! + // We also do the same thing for window presentation, just check to see if its supported. + QueueFamilyIndices indices; + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.multiViewport && deviceFeatures.geometryShader && indices.isComplete(); -} + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); -void DeviceLibrary::pickPhysicalDevice(VkInstance& instance) { - uint32_t deviceCount = 0; - vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + int i = 0; + for(const auto& queueFamily : queueFamilies) { + if(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } - if(deviceCount == 0) { - throw std::runtime_error("Failed to find GPU's with Vulkan Support!!"); + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if(presentSupport) { + indices.presentFamily = i; + } + + if(indices.isComplete()) { + break; + } + i++; + } + return indices; } - std::vector devices(deviceCount); // Direct Initialization is weird af, yo - vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); - for(const auto& device : devices) { - if(isDeviceSuitable(device)) { - std::cout << "Using device: " << deviceProperties.deviceName << std::endl; - //Once we have buttons or such, maybe ask the user or write a config file for which GPU to use? - physicalDevice = device; - break; + bool isDeviceSuitable(VkPhysicalDevice device) { + // These two are simple, create a structure to hold the apiVersion, driverVersion, vendorID, deviceID and type, name, and a few other settings. + // Then populate it by passing in the device and the structure reference. + vkGetPhysicalDeviceProperties(device, &deviceProperties); + // Similarly, we can pass in the device and a deviceFeatures struct, this is quite special, it holds a struct of optional features the GPU can perform. + // Some, like a geometry shader, and stereoscopic rendering (multiViewport) we want, so we dont return true without them. + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + // We need to find a device that supports graphical operations, or else we cant do much with it! This function just runs over all the queueFamilies and sees if there + // is a queue family with the VK_QUEUE_GRAPHICS_BIT flipped! + QueueFamilyIndices indices = findQueueFamilies(device); + + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.multiViewport && deviceFeatures.geometryShader && indices.isComplete(); + } + + void DeviceLibrary::pickPhysicalDevice(VkInstance& instance) { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if(deviceCount == 0) { + throw std::runtime_error("Failed to find GPU's with Vulkan Support!!"); + } + std::vector devices(deviceCount); // Direct Initialization is weird af, yo + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + for(const auto& device : devices) { + if(isDeviceSuitable(device)) { + std::cout << "Using device: " << deviceProperties.deviceName << std::endl; + //Once we have buttons or such, maybe ask the user or write a config file for which GPU to use? + physicalDevice = device; + break; + } + } + if(physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("Failed to find a suitable GPU!"); } } - - if(physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("Failed to find a suitable GPU!"); + void DeviceLibrary::destroySurface(VkInstance& instance) { + vkDestroySurfaceKHR(instance, surface, nullptr); + std::cout << "Destroyed surface safely\n" << std::endl; + } + void DeviceLibrary::createSurface(VkInstance& instance, GLFWwindow* window) { + if(glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface!!"); + } + std::cout << "GLFW Window Surface created successfully\n" << std::endl; + } + void DeviceLibrary::createLogicalDevice(VkDevice& device) { + // Describe how many queues we want for a single family (1) here, right now we are solely interested in graphics capabilites, + // but Compute Shaders, transfer ops, decode and encode operations can also queued with setup! We also assign each queue a priority. + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { + indices.graphicsFamily.value(), + indices.presentFamily.value() + }; + + float queuePriority = 1.0f; + for(uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateSingularInfo = {}; + queueCreateSingularInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateSingularInfo.queueFamilyIndex = queueFamily; + queueCreateSingularInfo.queueCount = 1; + queueCreateSingularInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateSingularInfo); + } + VkDeviceCreateInfo createDeviceInfo = {}; + VkPhysicalDeviceFeatures emptyFeatures = {}; + createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data(); + createDeviceInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createDeviceInfo.pEnabledFeatures = &emptyFeatures; + createDeviceInfo.enabledExtensionCount = 0; + + + if(Global::enableValidationLayers) { + createDeviceInfo.enabledLayerCount = static_cast(Global::validationLayers.size()); + createDeviceInfo.ppEnabledLayerNames = Global::validationLayers.data(); + } else { + createDeviceInfo.enabledLayerCount = 0; + } + if(vkCreateDevice(physicalDevice, &createDeviceInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("Failed to create logical device"); + } + std::cout << "Created Logical device successfully!\n" << std::endl; + + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } } - -void DeviceLibrary::createLogicalDevice(VkDevice& device) { - // Describe how many queues we want for a single family (1) here, right now we are solely interested in graphics capabilites, - // but Compute Shaders, transfer ops, decode and encode operations can also queued with setup! We also assign each queue a priority. - QueueFamilyIndices indices = findQueueFamilies(physicalDevice); - - VkDeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); - queueCreateInfo.queueCount = 1; - - float queuePriority = 1.0f; - queueCreateInfo.pQueuePriorities = &queuePriority; - - VkDeviceCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - createInfo.pQueueCreateInfos = &queueCreateInfo; - createInfo.queueCreateInfoCount = 1; - createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = 0; - - if(enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); - } else { - createInfo.enabledLayerCount = 0; - } - if(vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { - throw std::runtime_error("Failed to create logical device"); - } - vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); -} diff --git a/src/DeviceLibrary.h b/src/DeviceLibrary.h index 1992923..2a0f801 100644 --- a/src/DeviceLibrary.h +++ b/src/DeviceLibrary.h @@ -1,9 +1,14 @@ #pragma once -#include -namespace AgnosiaEngine { +#include "global.h" +namespace DeviceControl { class DeviceLibrary { public: + void pickPhysicalDevice(VkInstance& instance); - void createLogicalDevice(VkDevice& devicvee); + void createLogicalDevice(VkDevice& device); + void createSurface(VkInstance& instance, GLFWwindow* window); + void destroySurface(VkInstance& instance); }; } + + diff --git a/src/debug/VulkanDebugLibs.cpp b/src/debug/VulkanDebugLibs.cpp index 243512f..746d859 100644 --- a/src/debug/VulkanDebugLibs.cpp +++ b/src/debug/VulkanDebugLibs.cpp @@ -5,18 +5,11 @@ #include #include "../global.h" -#include "VulkanDebugLibs.h" -using namespace AgnosiaEngine; +using namespace Debug; #include #include -// This ifdef checks if the build flag is present, hence whether to hook the debugger in at all. -#ifdef DEBUG - const bool enableValidationLayers = true; -#else - const bool enableValidationLayers = false; -#endif // This is our messenger object! It handles passing along debug messages to the debug callback we will also set. VkDebugUtilsMessengerEXT debugMessenger; // This is the set of "layers" to hook into. Basically, layers are used to tell the messenger what data we want, its a filter. *validation* is the general blanket layer to cover incorrect usage. @@ -31,7 +24,7 @@ std::vector getRequiredExtensions() { std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - if(enableValidationLayers) { + if(Global::enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; @@ -51,6 +44,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( return VK_FALSE; } + + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { // There is absolutely nothing about this i like, those long ass flags for messageType and Severity are just fucking hex values. Khronos should never cook again ToT // On a serious note, this is just a struct to define the parameters of the debug messenger, nothing super special. @@ -71,9 +66,9 @@ void VulkanDebugLibs::vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInsta createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - if(enableValidationLayers) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); + if(Global::enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(Global::validationLayers.size()); + createInfo.ppEnabledLayerNames = Global::validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; @@ -90,7 +85,7 @@ void VulkanDebugLibs::vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInsta void VulkanDebugLibs::checkUnavailableValidationLayers() { // Check if we are trying to hook validation layers in without support. - if(enableValidationLayers && !checkValidationLayerSupport()) { + if(Global::enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("Validation layers request, but not available! Are your SDK path variables set?"); } } @@ -106,7 +101,7 @@ bool VulkanDebugLibs::checkValidationLayerSupport() { std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); - for(const char* layerName : validationLayers) { + for(const char* layerName : Global::validationLayers) { bool layerFound = false; for(const auto& layerProperties : availableLayers) { @@ -153,7 +148,7 @@ void VulkanDebugLibs::DestroyDebugUtilsMessengerEXT(VkInstance instance, void VulkanDebugLibs::setupDebugMessenger(VkInstance& vulkanInstance) { // This is a pretty simple function! we just pass in the values to build the debug messenger, populate the structure with the data we want, // and safely create it, covering for runtime errors as per usual, this is the first thing that will be called! - if(!enableValidationLayers) return; + if(!Global::enableValidationLayers) return; VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); diff --git a/src/debug/VulkanDebugLibs.h b/src/debug/VulkanDebugLibs.h index 195b5b0..9b854dc 100644 --- a/src/debug/VulkanDebugLibs.h +++ b/src/debug/VulkanDebugLibs.h @@ -2,8 +2,9 @@ #include #include -namespace AgnosiaEngine { +namespace Debug { class VulkanDebugLibs { + public: void vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInstance& instance); bool checkValidationLayerSupport(); diff --git a/src/global.cpp b/src/global.cpp index b830e75..d9fe9a2 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -1,5 +1,13 @@ #include "global.h" +namespace Global { -const std::vector validationLayers = { + const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" -}; + }; + + #ifdef DEBUG + const bool enableValidationLayers = true; + #else + const bool enableValidationLayers = false; + #endif +} diff --git a/src/global.h b/src/global.h index bee9eea..f6436d0 100644 --- a/src/global.h +++ b/src/global.h @@ -1,5 +1,13 @@ #pragma once +#include "debug/VulkanDebugLibs.h" #include #include +#include -extern const std::vector validationLayers; +#define GLFW_INCLUDE_VULKAN +#include + +namespace Global { + extern const std::vector validationLayers; + extern const bool enableValidationLayers; +} diff --git a/src/main.cpp b/src/main.cpp index 67025f4..d69a3a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,24 +1,14 @@ -#include -#include -#define GLFW_INCLUDE_VULKAN -#include - #include "debug/VulkanDebugLibs.h" #include "DeviceLibrary.h" -using namespace AgnosiaEngine; +#include "debug/VulkanDebugLibs.h" +#include "global.h" #include #include #include #include -#ifdef DEBUG - const bool enableValidationLayers = true; -#else - const bool enableValidationLayers = false; -#endif - const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; @@ -26,7 +16,6 @@ const uint32_t HEIGHT = 600; class TriangleTestApplication { public: - void run() { initWindow(); initVulkan(); @@ -35,11 +24,13 @@ public: } private: + DeviceControl::DeviceLibrary deviceLibs; + Debug::VulkanDebugLibs debugController; + GLFWwindow* window; VkInstance instance; - VulkanDebugLibs debug; - DeviceLibrary deviceLibs; VkDevice device; + // Initialize GLFW Window. First, Initialize GLFW lib, disable resizing for // now, and create window. void initWindow() { @@ -53,13 +44,14 @@ private: void initVulkan() { createInstance(); - debug.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) + 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(device); } void createInstance() { - debug.checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers. + debugController.checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers. // Set application info for the vulkan instance! VkApplicationInfo appInfo{}; @@ -69,13 +61,13 @@ private: 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_0; // Tell vulkan what the highest API version we will allow this program to run on + 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. - debug.vulkanDebugSetup(createInfo, instance); // Handoff to the debug library to wrap the validation libs in! (And set the window up!) + 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. @@ -86,14 +78,15 @@ private: void cleanup() { // Similar to the last handoff, destroy the debug util in a safe manner in the library! vkDestroyDevice(device, nullptr); - if(enableValidationLayers) { - debug.DestroyDebugUtilsMessengerEXT(instance, nullptr); + if(Global::enableValidationLayers) { + debugController.DestroyDebugUtilsMessengerEXT(instance, nullptr); } + + deviceLibs.destroySurface(instance); vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); } - }; int main() {