From 1ffd31abe4aa74264d3a043e135e81c1a6880aaf Mon Sep 17 00:00:00 2001 From: macmacmac Date: Fri, 30 Aug 2024 21:30:51 -0400 Subject: [PATCH] Added presentation specific code --- CMakeLists.txt | 4 +- include/vulkanapp.hh | 98 ++++++++-- src/main.cc | 4 +- src/vulkanapp.cc | 451 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 530 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db54fe1..2e77085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,12 @@ SET(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE) set(EXE_NAME project) find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2) +find_package(Vulkan REQUIRED) set(PROGRAM_SOURCES ${CMAKE_SOURCE_DIR}/src/vulkanapp.cc) add_executable(${EXE_NAME} ${CMAKE_SOURCE_DIR}/src/main.cc ${PROGRAM_SOURCES}) target_include_directories(${EXE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) -target_link_libraries(${EXE_NAME} PRIVATE SDL2::SDL2) \ No newline at end of file +target_link_libraries(${EXE_NAME} PRIVATE SDL2::SDL2) +target_link_libraries(${EXE_NAME} PRIVATE Vulkan::Vulkan) \ No newline at end of file diff --git a/include/vulkanapp.hh b/include/vulkanapp.hh index 995e819..a38e13f 100644 --- a/include/vulkanapp.hh +++ b/include/vulkanapp.hh @@ -4,27 +4,99 @@ #include "SDL_events.h" #include +#include +#include #include +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance, const VkDebugUtilsMessengerCreateInfoEXT*, const VkAllocationCallbacks*, VkDebugUtilsMessengerEXT*); +void DestroyDebugUtilsMessengerEXT(VkInstance, VkDebugUtilsMessengerEXT, const VkAllocationCallbacks*); + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool complete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + class VulkanApp { public: - VulkanApp(const uint32_t& _w, const uint32_t& _h) : + VulkanApp(const uint32_t& _w, const uint32_t& _h) : mWidth(_w), mHeight(_h), mWin(nullptr), mActive(false) {} - - void run() { - init(); - loop(); - cleanup(); - } - - SDL_Window *mWin; - SDL_Event mEvent; - const uint32_t mWidth; - const uint32_t mHeight; - bool mActive; void init(); void loop(); void cleanup(); +private: + // SDL2 + SDL_Window *mWin; + SDL_Event mEvent; + + // Not tied to library + const uint32_t mWidth; + const uint32_t mHeight; + bool mActive; + + // Vulkan + VkInstance mInstance; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mLogicalDevice; + VkSurfaceKHR mSurface; + VkSwapchainKHR mSwapChain; + std::vector mSwapChainImages; + std::vector mSwapChainImageViews; + VkFormat mSwapChainImageFormat; + VkExtent2D mSwapChainExtent; + + VkQueue mGraphicsQueue; + VkQueue mPresentQueue; + + VkDebugUtilsMessengerEXT mDebugMessenger; + + void createInstance(); + void selectPhysicalDevice(); + void createLogicalDevice(); + void createSwapChain(); + void createImageViews(); + void createGraphicsPipeline(); + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice); + bool isDeviceSuitable(VkPhysicalDevice); + bool checkDeviceExtensionSupport(VkPhysicalDevice); + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice); + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector&); + VkPresentModeKHR chooseSwapPresentMode(const std::vector&); + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR&); + // Validation layer stuff + void setupDebugMessenger(); + bool checkValidationLayerSupport(); + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + fprintf(stderr, "Validation Layer: %s \n", pCallbackData->pMessage); + + return VK_FALSE; + } }; \ No newline at end of file diff --git a/src/main.cc b/src/main.cc index 4cff868..cdfd962 100644 --- a/src/main.cc +++ b/src/main.cc @@ -6,7 +6,9 @@ int main() { VulkanApp app(640, 480); try { - app.run(); + app.init(); + app.loop(); + app.cleanup(); } catch(const std::exception& except) { fprintf(stderr, "Error! %s\n", except.what()); diff --git a/src/vulkanapp.cc b/src/vulkanapp.cc index 231cd4f..4690507 100644 --- a/src/vulkanapp.cc +++ b/src/vulkanapp.cc @@ -5,10 +5,149 @@ #include #include +#include +#include +#include + +// Validator stuff +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + PFN_vkCreateDebugUtilsMessengerEXT func = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT") + ); + + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { + PFN_vkDestroyDebugUtilsMessengerEXT func = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT") + ); + + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +QueueFamilyIndices VulkanApp::findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + // Get queue families and check them + for(int i=0;i& availableFormats) { + for (const VkSurfaceFormatKHR& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} + +VkPresentModeKHR VulkanApp::chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const VkPresentModeKHR& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D VulkanApp::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + SDL_Vulkan_GetDrawableSize(mWin, &width, &height); + + VkExtent2D actualExtent = { + static_cast(width), + static_cast(height) + }; + + actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actualExtent; + } +} void VulkanApp::init() { - SDL_Init(SDL_INIT_EVERYTHING); - SDL_Vulkan_LoadLibrary(nullptr); + // Initialize SDL2 + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + //SDL_Vulkan_LoadLibrary(nullptr); // Create the window mWin = SDL_CreateWindow("Vulkan+SDL2 Application", @@ -18,20 +157,294 @@ void VulkanApp::init() { mHeight, SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN ); - + if(mWin == nullptr) { - std::string err = "Could not create window "; - err += SDL_GetError(); - err += "\n"; + std::string err = "Could not create window " + std::string(SDL_GetError()) + "\n"; throw std::runtime_error(err); } + + // Vulkan stuff from here + createInstance(); + setupDebugMessenger(); + + // Surface (must be done before physical device) + if(SDL_Vulkan_CreateSurface(mWin, mInstance, &mSurface) != SDL_TRUE) { + throw std::runtime_error("Could not create Vulkan surface!"); + } + + selectPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createGraphicsPipeline(); +} + +void VulkanApp::createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("Validation layers requested, but not available!"); + } + + // Get # of extensions then get extensions + uint32_t extensionCount = 0; + SDL_Vulkan_GetInstanceExtensions(mWin, &extensionCount, nullptr); + std::vector extensionNames(extensionCount); + SDL_Vulkan_GetInstanceExtensions(mWin, &extensionCount, extensionNames.data()); + + if(enableValidationLayers) + extensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // App info + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Simple Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "RAPT2"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + // Extensions + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + createInfo.enabledExtensionCount = static_cast(extensionNames.size()); + createInfo.ppEnabledExtensionNames = extensionNames.data(); + + // Debug handling + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if(enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + debugCreateInfo = {}; + debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + debugCreateInfo.pfnUserCallback = debugCallback; + + createInfo.pNext = reinterpret_cast(&debugCreateInfo); + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if(vkCreateInstance(&createInfo, nullptr, &mInstance) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan instance!"); + } +} + +void VulkanApp::selectPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr); + + if(deviceCount == 0) { + throw std::runtime_error("Could not find a Vulkan compatible GPU!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data()); + + for(const VkPhysicalDevice& device : devices) { + QueueFamilyIndices indices = findQueueFamilies(device); + if(indices.complete()) { + mPhysicalDevice = device; + break; + } + } + + if(mPhysicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("Could not find any suitable GPU!"); + } +} + +void VulkanApp::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { + indices.graphicsFamily.value(), + indices.presentFamily.value() + }; + + float queuePriority = 1.0f; + for(uint32_t queueFamily : uniqueQueueFamilies) { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mLogicalDevice) != VK_SUCCESS) { + throw std::runtime_error("Could not create logical device!"); + } + + vkGetDeviceQueue(mLogicalDevice, indices.graphicsFamily.value(), 0, &mGraphicsQueue); + vkGetDeviceQueue(mLogicalDevice, indices.presentFamily.value(), 0, &mPresentQueue); +} + +void VulkanApp::createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(mPhysicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = mSurface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(mLogicalDevice, &createInfo, nullptr, &mSwapChain) != VK_SUCCESS) { + throw std::runtime_error("Could not create swap chain!"); + } + + vkGetSwapchainImagesKHR(mLogicalDevice, mSwapChain, &imageCount, nullptr); + mSwapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(mLogicalDevice, mSwapChain, &imageCount, mSwapChainImages.data()); + + mSwapChainImageFormat = surfaceFormat.format; + mSwapChainExtent = extent; +} + +void VulkanApp::createImageViews() { + mSwapChainImageViews.resize(mSwapChainImages.size()); + + for (size_t i = 0; i < mSwapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = mSwapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = mSwapChainImageFormat; + + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (vkCreateImageView(mLogicalDevice, &createInfo, nullptr, &mSwapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("Could not create image views!"); + } + } +} + +void VulkanApp::createGraphicsPipeline() { + +} + +bool VulkanApp::checkDeviceExtensionSupport(VkPhysicalDevice device) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const VkExtensionProperties& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); +} + +bool VulkanApp::checkValidationLayerSupport() { + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const VkLayerProperties& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} + +void VulkanApp::setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + createInfo.pfnUserCallback = debugCallback; + + if (CreateDebugUtilsMessengerEXT(mInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } } void VulkanApp::loop() { if(mWin == nullptr) { - std::string err = "Could not find window "; - err += SDL_GetError(); - err += "\n"; + std::string err = "Could not find window " + std::string(SDL_GetError()) + "\n"; throw std::runtime_error(err); } @@ -46,12 +459,12 @@ void VulkanApp::loop() { mActive = false; break; } - + //SDL_SetRenderDrawColor(rend, 255, 0, 0, 255); //SDL_RenderClear(rend); //SDL_RenderPresent(rend); - + SDL_ShowWindow(mWin); } @@ -59,11 +472,25 @@ void VulkanApp::loop() { } void VulkanApp::cleanup() { + for (VkImageView imageView : mSwapChainImageViews) { + vkDestroyImageView(mLogicalDevice, imageView, nullptr); + } + + vkDestroySwapchainKHR(mLogicalDevice, mSwapChain, nullptr); + vkDestroyDevice(mLogicalDevice, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr); + } + vkDestroySurfaceKHR(mInstance, mSurface, nullptr); + vkDestroyInstance(mInstance, nullptr); + + // SDL Cleanup if(mWin != nullptr) { SDL_DestroyWindow(mWin); mWin = nullptr; } - SDL_Vulkan_UnloadLibrary(); + //SDL_Vulkan_UnloadLibrary(); SDL_Quit(); } \ No newline at end of file