diff --git a/src/devicelibrary.cpp b/src/devicelibrary.cpp index c5ff58a..14d8887 100644 --- a/src/devicelibrary.cpp +++ b/src/devicelibrary.cpp @@ -3,330 +3,386 @@ namespace device_libs { - VkPhysicalDeviceProperties deviceProperties; +VkPhysicalDeviceProperties deviceProperties; - std::vector swapChainImages; - VkFormat swapChainImageFormat; - VkExtent2D swapChainExtent; +std::vector swapChainImages; +VkFormat swapChainImageFormat; +VkExtent2D swapChainExtent; - struct SwapChainSupportDetails { - VkSurfaceCapabilitiesKHR capabilities; - std::vector formats; - std::vector presentModes; - }; - const std::vector deviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; +SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { - /* Swap chains are weird ngl, it's another one of those Vulkan platform agnosticity. - The swapchain is basically a wrapper for GDI+, DXGI, X11, Wayland, etc. - It lets us use the swap chain rather than create a different framebuffer - handler for every targeted platform. Swap chains handle the ownership - of buffers before sending them to the presentation engine. (still no - fucking clue how it works though) */ - SwapChainSupportDetails details; + /* Swap chains are weird ngl, it's another one of those Vulkan platform + agnosticity. The swapchain is basically a wrapper for GDI+, DXGI, X11, + Wayland, etc. It lets us use the swap chain rather than create a different + framebuffer handler for every targeted platform. Swap chains handle the + ownership of buffers before sending them to the presentation engine. (still + no fucking clue how it works though) */ + SwapChainSupportDetails details; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, Global::surface, &details.capabilities); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, Global::surface, + &details.capabilities); - uint32_t formatCount; - vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, nullptr); + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, + nullptr); - if(formatCount != 0) { - details.formats.resize(formatCount); - vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, details.formats.data()); - } - - uint32_t presentModeCount; - vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface, &presentModeCount, details.presentModes.data()); - - if(presentModeCount != 0) { - details.presentModes.resize(presentModeCount); - vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface, &presentModeCount, details.presentModes.data()); - } - - return details; + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, + details.formats.data()); } - bool checkDeviceExtensionSupport(VkPhysicalDevice device) { - uint32_t extensionCount; - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR( + device, Global::surface, &presentModeCount, details.presentModes.data()); - std::vector availableExtensions(extensionCount); - vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); - - std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); - - for (const auto& extension : availableExtensions) { - requiredExtensions.erase(extension.extensionName); - } - - return requiredExtensions.empty(); + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface, + &presentModeCount, + details.presentModes.data()); } - 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. - VkPhysicalDeviceFeatures supportedFeatures; - vkGetPhysicalDeviceFeatures(device, &supportedFeatures); - // 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! - Global::QueueFamilyIndices indices = Global::findQueueFamilies(device); - bool extensionSupported = checkDeviceExtensionSupport(device); - bool swapChainAdequate = false; + return details; +} - if(extensionSupported) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); - swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); - } - - return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - && supportedFeatures.samplerAnisotropy - && indices.isComplete() - && extensionSupported - && swapChainAdequate; +bool 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 auto &extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); } -// -------------------------------------- Swap Chain Settings ----------------------------------------- // - VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { - // One of three settings we can set, Surface Format controls the color space and format. - - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - // sRGB & 32bit BGRA - return availableFormat; - } - } - return availableFormats[0]; + + return requiredExtensions.empty(); +} + +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. + VkPhysicalDeviceFeatures supportedFeatures; + vkGetPhysicalDeviceFeatures(device, &supportedFeatures); + // 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! + Global::QueueFamilyIndices indices = Global::findQueueFamilies(device); + bool extensionSupported = checkDeviceExtensionSupport(device); + bool swapChainAdequate = false; + + if (extensionSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && + !swapChainSupport.presentModes.empty(); } - VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { - // The second of the three settings, arguably the most important, the presentation mode! This dictates how images are displayed. - // MAILBOX is basically equivalent to triple buffering, it avoids screen tearing with fairly low latency, - // However, it is not always supported, so in the case that it isn't, currently we will default to FIFO, - // This is most similarly to standard V-Sync. - for(const auto& availablePresentMode : availablePresentModes) { - if(availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { - return availablePresentMode; - } - } - return VK_PRESENT_MODE_FIFO_KHR; - } - VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) { - // Swap Extent is just a fancy way of saying the resolution of the swap images to display. - // This is almost always going to equal the resolution of the window in pixels. - - // The max int32 value tells us that the window manager lets us change the windth and height to what we wish! - if (capabilities.currentExtent.width != std::numeric_limits::max()) { - return capabilities.currentExtent; - } else { - int width, height; - glfwGetFramebufferSize(window, &width, &height); + return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && + supportedFeatures.samplerAnisotropy && indices.isComplete() && + extensionSupported && swapChainAdequate; +} +// -------------------------------------- Swap Chain Settings +// ----------------------------------------- // +VkSurfaceFormatKHR chooseSwapSurfaceFormat( + const std::vector &availableFormats) { + // One of three settings we can set, Surface Format controls the color space + // and format. - VkExtent2D actualExtent = { - static_cast(width), - static_cast(height) - }; - // Clamp the image size to the minimum extent values specified by vulkan for our window manager. - 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; + for (const auto &availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + // sRGB & 32bit BGRA + return availableFormat; } } -// --------------------------------------- External Functions ----------------------------------------- // - void DeviceControl::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)) { - //Once we have buttons or such, maybe ask the user or write a config file for which GPU to use? - Global::physicalDevice = device; - break; - } - } - if(Global::physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("Failed to find a suitable GPU!"); + return availableFormats[0]; +} +VkPresentModeKHR chooseSwapPresentMode( + const std::vector &availablePresentModes) { + // The second of the three settings, arguably the most important, the + // presentation mode! This dictates how images are displayed. MAILBOX is + // basically equivalent to triple buffering, it avoids screen tearing with + // fairly low latency, However, it is not always supported, so in the case + // that it isn't, currently we will default to FIFO, This is most similarly to + // standard V-Sync. + for (const auto &availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; } } - void DeviceControl::destroySurface(VkInstance& instance) { - vkDestroySurfaceKHR(instance, Global::surface, nullptr); + + return VK_PRESENT_MODE_FIFO_KHR; +} +VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities, + GLFWwindow *window) { + // Swap Extent is just a fancy way of saying the resolution of the swap images + // to display. This is almost always going to equal the resolution of the + // window in pixels. + + // The max int32 value tells us that the window manager lets us change the + // windth and height to what we wish! + if (capabilities.currentExtent.width != + std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actualExtent = {static_cast(width), + static_cast(height)}; + // Clamp the image size to the minimum extent values specified by vulkan for + // our window manager. + 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 DeviceControl::createSurface(VkInstance& instance, GLFWwindow* window) { - if(glfwCreateWindowSurface(instance, window, nullptr, &Global::surface) != VK_SUCCESS) { - throw std::runtime_error("Failed to create window surface!!"); +} +// --------------------------------------- External Functions +// ----------------------------------------- // +void DeviceControl::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)) { + // Once we have buttons or such, maybe ask the user or write a config file + // for which GPU to use? + Global::physicalDevice = device; + break; } } - void DeviceControl::createLogicalDevice() { - // 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. - // We do this by looping over all the queueFamilies and sorting them by indices to fill the queue at the end! - Global::QueueFamilyIndices indices = Global::findQueueFamilies(Global::physicalDevice); + if (Global::physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("Failed to find a suitable GPU!"); + } +} +void DeviceControl::destroySurface(VkInstance &instance) { + vkDestroySurfaceKHR(instance, Global::surface, nullptr); +} +void DeviceControl::createSurface(VkInstance &instance, GLFWwindow *window) { + if (glfwCreateWindowSurface(instance, window, nullptr, &Global::surface) != + VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface!!"); + } +} +void DeviceControl::createLogicalDevice() { + // 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. We do this by looping over all the + // queueFamilies and sorting them by indices to fill the queue at the end! + Global::QueueFamilyIndices indices = + Global::findQueueFamilies(Global::physicalDevice); - std::vector queueCreateInfos; - std::set uniqueQueueFamilies = { - indices.graphicsFamily.value(), - indices.presentFamily.value() - }; + 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); - } - - VkPhysicalDeviceVulkan13Features features13 { + 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); + } + + VkPhysicalDeviceVulkan13Features features13{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, .pNext = nullptr, + .synchronization2 = true, .dynamicRendering = true, - }; - VkPhysicalDeviceFeatures featuresBase { + }; + VkPhysicalDeviceFeatures featuresBase{ .samplerAnisotropy = true, - }; + }; - VkPhysicalDeviceFeatures2 deviceFeatures { + VkPhysicalDeviceFeatures2 deviceFeatures{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &features13, .features = featuresBase, - }; + }; - VkDeviceCreateInfo createDeviceInfo = {}; - createDeviceInfo.pNext = &deviceFeatures; - createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data(); - createDeviceInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); - createDeviceInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); - createDeviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); + VkDeviceCreateInfo createDeviceInfo = {}; + createDeviceInfo.pNext = &deviceFeatures; + createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data(); + createDeviceInfo.queueCreateInfoCount = + static_cast(queueCreateInfos.size()); + createDeviceInfo.enabledExtensionCount = + static_cast(deviceExtensions.size()); + createDeviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); - if(vkCreateDevice(Global::physicalDevice, &createDeviceInfo, nullptr, &Global::device) != VK_SUCCESS) { - throw std::runtime_error("Failed to create logical device"); - } - vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, &Global::graphicsQueue); - vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, &Global::presentQueue); + if (vkCreateDevice(Global::physicalDevice, &createDeviceInfo, nullptr, + &Global::device) != VK_SUCCESS) { + throw std::runtime_error("Failed to create logical device"); } - void DeviceControl::createSwapChain(GLFWwindow* window) { - SwapChainSupportDetails swapChainSupport = querySwapChainSupport(Global::physicalDevice); - - VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); - VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); - VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities, window); - - // Number of images to hold in the swap chain, 1 over the minimum guarantees we won't have to wait on the driver to complete - // internal operations before acquiring another image. Absolutely a TODO to determine the best amount to queue. - uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; - // Make sure not to queue more than the max! 0 indicates that there is no maximum. - if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { - imageCount = swapChainSupport.capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR createSwapChainInfo{}; - createSwapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - createSwapChainInfo.surface = Global::surface; - createSwapChainInfo.minImageCount = imageCount; - createSwapChainInfo.imageFormat = surfaceFormat.format; - createSwapChainInfo.imageColorSpace = surfaceFormat.colorSpace; - createSwapChainInfo.imageExtent = extent; - // Image array layers is always 1 unless we are developing for VR (Spoiler: we are, we will use a build flag.) - // Image Usage specifies what operations you use the images for, COLOR_ATTACH means we render directly to them, - // if you wanted to render to separate images for things like post processing, you can use TRANSFER_DST and use a - // memory operation to transfer the image to a swap chain, this is also a TODO item eventually. - createSwapChainInfo.imageArrayLayers = 1; - createSwapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - - // This handles swap chain images across multiple queue families, ie, if the graphics queue family is different from the present queue - Global::QueueFamilyIndices indices = Global::findQueueFamilies(Global::physicalDevice); - uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; - // Usage across multiple queue families without explicit transfer of ownership if they are different queue families. - // Otherwise, no sharing without explicit handoffs, faster, but not easily supported with multiple families. - // Presentation and Graphics families are usually merged on most hardware. - if (indices.graphicsFamily != indices.presentFamily) { - createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; - createSwapChainInfo.queueFamilyIndexCount = 2; - createSwapChainInfo.pQueueFamilyIndices = queueFamilyIndices; - } else { - createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - } - // Transformation of image support. - createSwapChainInfo.preTransform = swapChainSupport.capabilities.currentTransform; - // Do NOT blend with other windows on the system. - createSwapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - createSwapChainInfo.presentMode = presentMode; - // This is interesting, clip pixels that are obscured for performance, but that means you wont be able to read them reliably.. - // I am curious if this would affect screen-space rendering techniques, may be something to note. - createSwapChainInfo.clipped = VK_TRUE; - // This is something that needs to be implemented later, operations like resizing the window invalidate the swap chain and - // 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, &Global::swapChain) != VK_SUCCESS) { - throw std::runtime_error("Failed to create the swap chain!!"); - } - - vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, nullptr); - swapChainImages.resize(imageCount); - vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, swapChainImages.data()); - - swapChainImageFormat = surfaceFormat.format; - swapChainExtent = extent; - } - void DeviceControl::destroySwapChain() { - vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); - } - VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipLevels) { - // This defines the parameters of a newly created image object! - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = flags; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - viewInfo.subresourceRange.levelCount = mipLevels; - - VkImageView imageView; - if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { - throw std::runtime_error("failed to create image view!"); - } - - return imageView; - } - void DeviceControl::createImageViews() { - Global::swapChainImageViews.resize(swapChainImages.size()); - - for (uint32_t i = 0; i < swapChainImages.size(); i++) { - Global::swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); - } - } - void DeviceControl::destroyImageViews() { - for (auto imageView : Global::swapChainImageViews) { - vkDestroyImageView(Global::device, imageView, nullptr); - } - } -// --------------------------------------- Getters & Setters ------------------------------------------ // - VkFormat* DeviceControl::getImageFormat() { - return &swapChainImageFormat; - } - VkExtent2D DeviceControl::getSwapChainExtent() { - return swapChainExtent; - } - std::vector DeviceControl::getSwapChainImages() { - return swapChainImages; - } - + vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, + &Global::graphicsQueue); + vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, + &Global::presentQueue); } +void DeviceControl::createSwapChain(GLFWwindow *window) { + SwapChainSupportDetails swapChainSupport = + querySwapChainSupport(Global::physicalDevice); + + VkSurfaceFormatKHR surfaceFormat = + chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = + chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities, window); + + // Number of images to hold in the swap chain, 1 over the minimum guarantees + // we won't have to wait on the driver to complete internal operations before + // acquiring another image. Absolutely a TODO to determine the best amount to + // queue. + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + // Make sure not to queue more than the max! 0 indicates that there is no + // maximum. + if (swapChainSupport.capabilities.maxImageCount > 0 && + imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createSwapChainInfo{}; + createSwapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createSwapChainInfo.surface = Global::surface; + createSwapChainInfo.minImageCount = imageCount; + createSwapChainInfo.imageFormat = surfaceFormat.format; + createSwapChainInfo.imageColorSpace = surfaceFormat.colorSpace; + createSwapChainInfo.imageExtent = extent; + // Image array layers is always 1 unless we are developing for VR (Spoiler: we + // are, we will use a build flag.) Image Usage specifies what operations you + // use the images for, COLOR_ATTACH means we render directly to them, if you + // wanted to render to separate images for things like post processing, you + // can use TRANSFER_DST and use a memory operation to transfer the image to a + // swap chain, this is also a TODO item eventually. + createSwapChainInfo.imageArrayLayers = 1; + createSwapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + // This handles swap chain images across multiple queue families, ie, if the + // graphics queue family is different from the present queue + Global::QueueFamilyIndices indices = + Global::findQueueFamilies(Global::physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), + indices.presentFamily.value()}; + // Usage across multiple queue families without explicit transfer of ownership + // if they are different queue families. Otherwise, no sharing without + // explicit handoffs, faster, but not easily supported with multiple families. + // Presentation and Graphics families are usually merged on most hardware. + if (indices.graphicsFamily != indices.presentFamily) { + createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createSwapChainInfo.queueFamilyIndexCount = 2; + createSwapChainInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + // Transformation of image support. + createSwapChainInfo.preTransform = + swapChainSupport.capabilities.currentTransform; + // Do NOT blend with other windows on the system. + createSwapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createSwapChainInfo.presentMode = presentMode; + // This is interesting, clip pixels that are obscured for performance, but + // that means you wont be able to read them reliably.. I am curious if this + // would affect screen-space rendering techniques, may be something to note. + createSwapChainInfo.clipped = VK_TRUE; + // This is something that needs to be implemented later, operations like + // resizing the window invalidate the swap chain and 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, + &Global::swapChain) != VK_SUCCESS) { + throw std::runtime_error("Failed to create the swap chain!!"); + } + + vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, + nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, + swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; +} +void DeviceControl::destroySwapChain() { + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); +} +VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, + VkImageAspectFlags flags, + uint32_t mipLevels) { + // This defines the parameters of a newly created image object! + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = flags; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.levelCount = mipLevels; + + VkImageView imageView; + if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) != + VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + + return imageView; +} +void DeviceControl::createImageViews() { + Global::swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + Global::swapChainImageViews[i] = createImageView( + swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1); + } +} +void DeviceControl::destroyImageViews() { + for (auto imageView : Global::swapChainImageViews) { + vkDestroyImageView(Global::device, imageView, nullptr); + } +} +// --------------------------------------- Getters & Setters +// ------------------------------------------ // +VkFormat *DeviceControl::getImageFormat() { return &swapChainImageFormat; } +VkExtent2D DeviceControl::getSwapChainExtent() { return swapChainExtent; } +std::vector DeviceControl::getSwapChainImages() { + return swapChainImages; +} + +} // namespace device_libs diff --git a/src/entrypoint.cpp b/src/entrypoint.cpp index 8efbad7..fe7d6ef 100644 --- a/src/entrypoint.cpp +++ b/src/entrypoint.cpp @@ -7,11 +7,10 @@ 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)); +bool EntryApp::getFramebufferResized() const { return framebufferResized; } +static void framebufferResizeCallback(GLFWwindow *window, int width, + int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->setFramebufferResized(true); } @@ -20,42 +19,58 @@ static void framebufferResizeCallback(GLFWwindow* window, int width, int height) void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - // Settings for the window are set, create window reference. - Global::window = glfwCreateWindow(Global::WIDTH, Global::HEIGHT, "Trimgles :o", nullptr, nullptr); + // Settings for the window are set, create window reference. + Global::window = glfwCreateWindow(Global::WIDTH, Global::HEIGHT, + "Trimgles :o", nullptr, nullptr); glfwSetWindowUserPointer(Global::window, &EntryApp::getInstance()); glfwSetFramebufferSizeCallback(Global::window, framebufferResizeCallback); } void createInstance() { - - // 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_3; // Tell vulkan what the highest API version we will allow this program to run on - - // This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed - // So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions! - // From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic! - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); - 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. + // 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_3; // Tell vulkan what the highest API version we will + // allow this program to run on + + // This gets a little weird, Vulkan is platform agnostic, so you need to + // figure out what extensions to interface with the current system are needed + // So, to figure out what extension codes and how many to use, feed the + // pointer into *glfwGetRequiredInstanceExtensions*, which will get the + // necessary extensions! From there, we can send that over to our createInfo + // Vulkan info struct to make it fully platform agnostic! + uint32_t glfwExtensionCount = 0; + const char **glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + std::vector extensions(glfwExtensions, + glfwExtensions + glfwExtensionCount); + + 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. createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); if (vkCreateInstance(&createInfo, nullptr, &vulkaninstance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } - } void initVulkan() { @@ -94,12 +109,12 @@ void mainLoop() { void cleanup() { render_present::Render::cleanupSwapChain(); graphics_pipeline::Graphics::destroyGraphicsPipeline(); - //graphics_pipeline::Graphics::destroyRenderPass(); buffers_libs::Buffers::destroyUniformBuffer(); buffers_libs::Buffers::destroyDescriptorPool(); texture_libs::Texture::destroyTextureSampler(); texture_libs::Texture::destroyTextureImage(); - vkDestroyDescriptorSetLayout(Global::device, Global::descriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(Global::device, Global::descriptorSetLayout, + nullptr); buffers_libs::Buffers::destroyBuffers(); render_present::Render::destroyFenceSemaphores(); graphics_pipeline::Graphics::destroyCommandPool(); @@ -112,18 +127,14 @@ void cleanup() { } // External Functions -EntryApp& EntryApp::getInstance() { +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::initialize() { initialized = true; } +bool EntryApp::isInitialized() const { return initialized; } void EntryApp::run() { initWindow(); @@ -131,4 +142,3 @@ void EntryApp::run() { mainLoop(); cleanup(); } - diff --git a/src/global.cpp b/src/global.cpp index d7551e4..a5cb654 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -2,60 +2,66 @@ namespace Global { - VkSurfaceKHR surface; - VkDevice device; - VkPhysicalDevice physicalDevice; - VkSwapchainKHR swapChain; - VkCommandPool commandPool; - std::vector commandBuffers; - VkQueue graphicsQueue; - VkQueue presentQueue; - GLFWwindow* window; - VkDescriptorSetLayout descriptorSetLayout; - std::vector descriptorSets; - uint32_t currentFrame = 0; - VkImageView textureImageView; - VkSampler textureSampler; - VkImageView depthImageView; - VkImage depthImage; - VkDeviceMemory depthImageMemory; +VkSurfaceKHR surface; +VkDevice device; +VkPhysicalDevice physicalDevice; +VkSwapchainKHR swapChain; +VkCommandPool commandPool; +std::vector commandBuffers; +VkQueue graphicsQueue; +VkQueue presentQueue; +GLFWwindow *window; +VkDescriptorSetLayout descriptorSetLayout; +std::vector descriptorSets; +uint32_t currentFrame = 0; +VkImageView textureImageView; +VkSampler textureSampler; +VkImageView depthImageView; +VkImage depthImage; +VkDeviceMemory depthImageMemory; - std::vector swapChainImageViews; - std::vector vertices; - // Index buffer definition, showing which points to reuse. - std::vector indices; +std::vector swapChainImageViews; +std::vector vertices; +// Index buffer definition, showing which points to reuse. +std::vector indices; - 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. - // 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. - Global::QueueFamilyIndices indices; - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); +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. 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. + Global::QueueFamilyIndices indices; + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + 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; - } - - VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Global::surface, &presentSupport); - if(presentSupport) { - indices.presentFamily = i; - } - - if(indices.isComplete()) { - break; - } - i++; + int i = 0; + for (const auto &queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; } - return indices; + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Global::surface, + &presentSupport); + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + i++; } + return indices; } +} // namespace Global diff --git a/src/graphics/graphicspipeline.cpp b/src/graphics/graphicspipeline.cpp index 64b57bb..a551b35 100644 --- a/src/graphics/graphicspipeline.cpp +++ b/src/graphics/graphicspipeline.cpp @@ -261,27 +261,35 @@ void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer, if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin recording command buffer!"); } - - const VkImageMemoryBarrier imageMemoryBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + const VkImageMemoryBarrier2 imageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + .srcAccessMask = 0, + .dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = device_libs::DeviceControl::getSwapChainImages()[imageIndex], - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = texture_libs::Texture::getMipLevels(), - .baseArrayLayer = 0, - .layerCount = 1, - }}; + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + const VkDependencyInfo dependencyInfo{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &imageMemoryBarrier, + }; + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); - vkCmdPipelineBarrier(commandBuffer, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, - nullptr, 1, &imageMemoryBarrier - - ); // ------------------- DYNAMIC RENDER INFO ---------------------- // const VkRenderingAttachmentInfo colorAttachmentInfo{ @@ -347,6 +355,36 @@ void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer, vkCmdEndRendering(commandBuffer); + const VkImageMemoryBarrier2 prePresentImageBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = device_libs::DeviceControl::getSwapChainImages()[imageIndex], + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + const VkDependencyInfo depInfo{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &prePresentImageBarrier, + }; + + vkCmdPipelineBarrier2(Global::commandBuffers[Global::currentFrame], &depInfo); + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } diff --git a/src/graphics/render.cpp b/src/graphics/render.cpp index 0eced75..d72d025 100644 --- a/src/graphics/render.cpp +++ b/src/graphics/render.cpp @@ -1,162 +1,174 @@ -#include "render.h" -#include "graphicspipeline.h" #include "../devicelibrary.h" #include "../entrypoint.h" +#include "graphicspipeline.h" +#include "render.h" #include "texture.h" +#include namespace render_present { - std::vector imageAvailableSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; +std::vector imageAvailableSemaphores; +std::vector renderFinishedSemaphores; +std::vector inFlightFences; - void recreateSwapChain() { - int width = 0, height = 0; +void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(Global::window, &width, &height); + while (width == 0 || height == 0) { glfwGetFramebufferSize(Global::window, &width, &height); - while (width == 0 || height == 0) { - glfwGetFramebufferSize(Global::window, &width, &height); - glfwWaitEvents(); - } - 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 imageView : Global::swapChainImageViews) { - vkDestroyImageView(Global::device, imageView, nullptr); - } - vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); - - device_libs::DeviceControl::createSwapChain(Global::window); - device_libs::DeviceControl::createImageViews(); - texture_libs::Texture::createDepthResources(); + glfwWaitEvents(); } - // 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() { + 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. - vkWaitForFences(Global::device, 1, &inFlightFences[Global::currentFrame], VK_TRUE, UINT64_MAX); - vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]); - - uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, imageAvailableSemaphores[Global::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!"); - } - - buffers_libs::Buffers::updateUniformBuffer(Global::currentFrame); - - vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]); - - vkResetCommandBuffer(Global::commandBuffers[Global::currentFrame], /*VkCommandBufferResetFlagBits*/ 0); - graphics_pipeline::Graphics::recordCommandBuffer(Global::commandBuffers[Global::currentFrame], imageIndex); - - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - - VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[Global::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[Global::currentFrame]; - - VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[Global::currentFrame]}; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = signalSemaphores; - - if (vkQueueSubmit(Global::graphicsQueue, 1, &submitInfo, inFlightFences[Global::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; - - 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!"); - } - Global::currentFrame = (Global::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 Render::destroyFenceSemaphores() { - for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) { - vkDestroySemaphore(Global::device, renderFinishedSemaphores[i], nullptr); - vkDestroySemaphore(Global::device, imageAvailableSemaphores[i], nullptr); - vkDestroyFence(Global::device, inFlightFences[i], nullptr); - } - } - void Render::cleanupSwapChain() { - vkDestroyImageView(Global::device, Global::depthImageView, nullptr); - vkDestroyImage(Global::device, Global::depthImage, nullptr); - vkFreeMemory(Global::device, Global::depthImageMemory, nullptr); - - for(auto imageView : Global::swapChainImageViews) { - vkDestroyImageView(Global::device, imageView, nullptr); - } - vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); + for (auto imageView : Global::swapChainImageViews) { + vkDestroyImageView(Global::device, imageView, nullptr); } + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); + device_libs::DeviceControl::createSwapChain(Global::window); + device_libs::DeviceControl::createImageViews(); + texture_libs::Texture::createDepthResources(); } +// 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[Global::currentFrame], + VK_TRUE, UINT64_MAX); + vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]); + + uint32_t imageIndex; + VkResult result = + vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, + imageAvailableSemaphores[Global::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!"); + } + + buffers_libs::Buffers::updateUniformBuffer(Global::currentFrame); + + vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]); + + vkResetCommandBuffer(Global::commandBuffers[Global::currentFrame], + /*VkCommandBufferResetFlagBits*/ 0); + graphics_pipeline::Graphics::recordCommandBuffer( + Global::commandBuffers[Global::currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = { + imageAvailableSemaphores[Global::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[Global::currentFrame]; + + VkSemaphore signalSemaphores[] = { + renderFinishedSemaphores[Global::currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(Global::graphicsQueue, 1, &submitInfo, + inFlightFences[Global::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; + + 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!"); + } + Global::currentFrame = + (Global::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 Render::destroyFenceSemaphores() { + for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) { + vkDestroySemaphore(Global::device, renderFinishedSemaphores[i], nullptr); + vkDestroySemaphore(Global::device, imageAvailableSemaphores[i], nullptr); + vkDestroyFence(Global::device, inFlightFences[i], nullptr); + } +} +void Render::cleanupSwapChain() { + vkDestroyImageView(Global::device, Global::depthImageView, nullptr); + vkDestroyImage(Global::device, Global::depthImage, nullptr); + vkFreeMemory(Global::device, Global::depthImageMemory, nullptr); + + for (auto imageView : Global::swapChainImageViews) { + vkDestroyImageView(Global::device, imageView, nullptr); + } + vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); +} + +} // namespace render_present