2024-10-07 05:09:46 -05:00
# include "devicelibrary.h"
2024-10-24 20:21:10 -05:00
# include <vulkan/vulkan_core.h>
2024-10-05 19:59:15 -05:00
2024-10-16 06:02:15 -05:00
namespace device_libs {
2024-10-05 16:53:36 -05:00
2024-10-06 04:35:53 -05:00
VkPhysicalDeviceProperties deviceProperties ;
2024-10-05 16:53:36 -05:00
2024-10-06 20:17:19 -05:00
std : : vector < VkImage > swapChainImages ;
VkFormat swapChainImageFormat ;
VkExtent2D swapChainExtent ;
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities ;
std : : vector < VkSurfaceFormatKHR > formats ;
std : : vector < VkPresentModeKHR > presentModes ;
} ;
const std : : vector < const char * > deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
} ;
SwapChainSupportDetails querySwapChainSupport ( VkPhysicalDevice device ) {
2024-10-24 20:21:10 -05:00
/* 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 ) */
2024-10-06 20:17:19 -05:00
SwapChainSupportDetails details ;
2024-10-08 01:57:32 -05:00
vkGetPhysicalDeviceSurfaceCapabilitiesKHR ( device , Global : : surface , & details . capabilities ) ;
2024-10-06 20:17:19 -05:00
uint32_t formatCount ;
2024-10-08 01:57:32 -05:00
vkGetPhysicalDeviceSurfaceFormatsKHR ( device , Global : : surface , & formatCount , nullptr ) ;
2024-10-06 20:17:19 -05:00
if ( formatCount ! = 0 ) {
details . formats . resize ( formatCount ) ;
2024-10-24 20:21:10 -05:00
vkGetPhysicalDeviceSurfaceFormatsKHR ( device , Global : : surface , & formatCount , details . formats . data ( ) ) ;
2024-10-06 20:17:19 -05:00
}
uint32_t presentModeCount ;
2024-10-08 01:57:32 -05:00
vkGetPhysicalDeviceSurfacePresentModesKHR ( device , Global : : surface , & presentModeCount , details . presentModes . data ( ) ) ;
2024-10-06 20:17:19 -05:00
if ( presentModeCount ! = 0 ) {
details . presentModes . resize ( presentModeCount ) ;
2024-10-08 01:57:32 -05:00
vkGetPhysicalDeviceSurfacePresentModesKHR ( device , Global : : surface , & presentModeCount , details . presentModes . data ( ) ) ;
2024-10-06 20:17:19 -05:00
}
return details ;
}
2024-10-08 01:57:32 -05:00
bool checkDeviceExtensionSupport ( VkPhysicalDevice device ) {
uint32_t extensionCount ;
vkEnumerateDeviceExtensionProperties ( device , nullptr , & extensionCount , nullptr ) ;
std : : vector < VkExtensionProperties > availableExtensions ( extensionCount ) ;
vkEnumerateDeviceExtensionProperties ( device , nullptr , & extensionCount , availableExtensions . data ( ) ) ;
std : : set < std : : string > requiredExtensions ( deviceExtensions . begin ( ) , deviceExtensions . end ( ) ) ;
for ( const auto & extension : availableExtensions ) {
requiredExtensions . erase ( extension . extensionName ) ;
}
return requiredExtensions . empty ( ) ;
}
2024-10-06 04:35:53 -05:00
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.
2024-10-13 04:56:45 -05:00
VkPhysicalDeviceFeatures supportedFeatures ;
vkGetPhysicalDeviceFeatures ( device , & supportedFeatures ) ;
2024-10-06 04:35:53 -05:00
// 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!
2024-10-08 01:57:32 -05:00
Global : : QueueFamilyIndices indices = Global : : findQueueFamilies ( device ) ;
2024-10-06 20:17:19 -05:00
bool extensionSupported = checkDeviceExtensionSupport ( device ) ;
bool swapChainAdequate = false ;
if ( extensionSupported ) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport ( device ) ;
swapChainAdequate = ! swapChainSupport . formats . empty ( ) & & ! swapChainSupport . presentModes . empty ( ) ;
}
2024-10-05 19:59:15 -05:00
2024-10-06 20:17:19 -05:00
return deviceProperties . deviceType = = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
2024-10-13 04:56:45 -05:00
& & supportedFeatures . samplerAnisotropy
2024-10-06 20:17:19 -05:00
& & indices . isComplete ( )
& & extensionSupported
& & swapChainAdequate ;
}
2024-10-08 01:57:32 -05:00
// -------------------------------------- Swap Chain Settings ----------------------------------------- //
2024-10-06 20:17:19 -05:00
VkSurfaceFormatKHR chooseSwapSurfaceFormat ( const std : : vector < VkSurfaceFormatKHR > & 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 ] ;
}
VkPresentModeKHR chooseSwapPresentMode ( const std : : vector < VkPresentModeKHR > & 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 ;
}
}
2024-10-07 05:09:46 -05:00
2024-10-06 20:17:19 -05:00
return VK_PRESENT_MODE_FIFO_KHR ;
2024-10-06 04:35:53 -05:00
}
2024-10-06 20:17:19 -05:00
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 < uint32_t > : : max ( ) ) {
return capabilities . currentExtent ;
} else {
2024-10-07 05:09:46 -05:00
int width , height ;
glfwGetFramebufferSize ( window , & width , & height ) ;
2024-10-05 16:53:36 -05:00
2024-10-07 05:09:46 -05:00
VkExtent2D actualExtent = {
static_cast < uint32_t > ( width ) ,
static_cast < uint32_t > ( height )
} ;
2024-10-06 20:17:19 -05:00
// Clamp the image size to the minimum extent values specified by vulkan for our window manager.
2024-10-07 05:09:46 -05:00
actualExtent . width = std : : clamp ( actualExtent . width , capabilities . minImageExtent . width , capabilities . maxImageExtent . width ) ;
actualExtent . height = std : : clamp ( actualExtent . height , capabilities . minImageExtent . height , capabilities . maxImageExtent . height ) ;
2024-10-06 20:17:19 -05:00
2024-10-07 05:09:46 -05:00
return actualExtent ;
2024-10-06 20:17:19 -05:00
}
}
2024-10-08 01:57:32 -05:00
// --------------------------------------- External Functions ----------------------------------------- //
2024-10-16 06:02:15 -05:00
void DeviceControl : : pickPhysicalDevice ( VkInstance & instance ) {
2024-10-06 04:35:53 -05:00
uint32_t deviceCount = 0 ;
vkEnumeratePhysicalDevices ( instance , & deviceCount , nullptr ) ;
2024-10-05 16:53:36 -05:00
2024-10-06 04:35:53 -05:00
if ( deviceCount = = 0 ) {
throw std : : runtime_error ( " Failed to find GPU's with Vulkan Support!! " ) ;
}
std : : vector < VkPhysicalDevice > 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?
2024-10-08 01:57:32 -05:00
Global : : physicalDevice = device ;
2024-10-06 04:35:53 -05:00
break ;
}
}
2024-10-08 01:57:32 -05:00
if ( Global : : physicalDevice = = VK_NULL_HANDLE ) {
2024-10-06 04:35:53 -05:00
throw std : : runtime_error ( " Failed to find a suitable GPU! " ) ;
2024-10-05 16:53:36 -05:00
}
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : destroySurface ( VkInstance & instance ) {
2024-10-08 01:57:32 -05:00
vkDestroySurfaceKHR ( instance , Global : : surface , nullptr ) ;
2024-10-05 16:53:36 -05:00
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : createSurface ( VkInstance & instance , GLFWwindow * window ) {
2024-10-08 01:57:32 -05:00
if ( glfwCreateWindowSurface ( instance , window , nullptr , & Global : : surface ) ! = VK_SUCCESS ) {
2024-10-06 04:35:53 -05:00
throw std : : runtime_error ( " Failed to create window surface!! " ) ;
}
2024-10-05 19:59:15 -05:00
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : createLogicalDevice ( ) {
2024-10-06 04:35:53 -05:00
// 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.
2024-10-06 04:52:10 -05:00
// We do this by looping over all the queueFamilies and sorting them by indices to fill the queue at the end!
2024-10-08 01:57:32 -05:00
Global : : QueueFamilyIndices indices = Global : : findQueueFamilies ( Global : : physicalDevice ) ;
2024-10-06 04:35:53 -05:00
std : : vector < VkDeviceQueueCreateInfo > queueCreateInfos ;
std : : set < uint32_t > 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 ) ;
}
2024-10-24 20:21:10 -05:00
VkPhysicalDeviceVulkan13Features features13 {
. sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES ,
. pNext = nullptr ,
. dynamicRendering = true ,
} ;
VkPhysicalDeviceFeatures featuresBase {
. samplerAnisotropy = true ,
} ;
VkPhysicalDeviceFeatures2 deviceFeatures {
. sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 ,
. pNext = & features13 ,
. features = featuresBase ,
} ;
2024-10-13 04:56:45 -05:00
2024-10-06 04:35:53 -05:00
VkDeviceCreateInfo createDeviceInfo = { } ;
2024-10-24 20:21:10 -05:00
createDeviceInfo . pNext = & deviceFeatures ;
2024-10-06 04:35:53 -05:00
createDeviceInfo . sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO ;
createDeviceInfo . pQueueCreateInfos = queueCreateInfos . data ( ) ;
createDeviceInfo . queueCreateInfoCount = static_cast < uint32_t > ( queueCreateInfos . size ( ) ) ;
2024-10-06 20:17:19 -05:00
createDeviceInfo . enabledExtensionCount = static_cast < uint32_t > ( deviceExtensions . size ( ) ) ;
createDeviceInfo . ppEnabledExtensionNames = deviceExtensions . data ( ) ;
2024-10-06 04:35:53 -05:00
2024-10-08 01:57:32 -05:00
if ( vkCreateDevice ( Global : : physicalDevice , & createDeviceInfo , nullptr , & Global : : device ) ! = VK_SUCCESS ) {
2024-10-06 04:35:53 -05:00
throw std : : runtime_error ( " Failed to create logical device " ) ;
}
2024-10-08 06:02:47 -05:00
vkGetDeviceQueue ( Global : : device , indices . graphicsFamily . value ( ) , 0 , & Global : : graphicsQueue ) ;
vkGetDeviceQueue ( Global : : device , indices . presentFamily . value ( ) , 0 , & Global : : presentQueue ) ;
2024-10-05 19:59:15 -05:00
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : createSwapChain ( GLFWwindow * window ) {
2024-10-08 01:57:32 -05:00
SwapChainSupportDetails swapChainSupport = querySwapChainSupport ( Global : : physicalDevice ) ;
2024-10-06 20:17:19 -05:00
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 ;
2024-10-08 01:57:32 -05:00
createSwapChainInfo . surface = Global : : surface ;
2024-10-06 20:17:19 -05:00
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
2024-10-08 01:57:32 -05:00
Global : : QueueFamilyIndices indices = Global : : findQueueFamilies ( Global : : physicalDevice ) ;
2024-10-06 20:17:19 -05:00
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 ;
2024-10-09 06:14:01 -05:00
// This is interesting, clip pixels that are obscured for performance, but that means you wont be able to read them reliably..
2024-10-06 20:17:19 -05:00
// 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.
2024-10-10 02:26:13 -05:00
//createSwapChainInfo.oldSwapchain = VK_NULL_HANDLE;
2024-10-06 20:17:19 -05:00
2024-10-08 06:02:47 -05:00
if ( vkCreateSwapchainKHR ( Global : : device , & createSwapChainInfo , nullptr , & Global : : swapChain ) ! = VK_SUCCESS ) {
2024-10-06 20:17:19 -05:00
throw std : : runtime_error ( " Failed to create the swap chain!! " ) ;
}
2024-10-08 06:02:47 -05:00
vkGetSwapchainImagesKHR ( Global : : device , Global : : swapChain , & imageCount , nullptr ) ;
2024-10-06 20:17:19 -05:00
swapChainImages . resize ( imageCount ) ;
2024-10-08 06:02:47 -05:00
vkGetSwapchainImagesKHR ( Global : : device , Global : : swapChain , & imageCount , swapChainImages . data ( ) ) ;
2024-10-06 20:17:19 -05:00
swapChainImageFormat = surfaceFormat . format ;
swapChainExtent = extent ;
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : destroySwapChain ( ) {
2024-10-08 06:02:47 -05:00
vkDestroySwapchainKHR ( Global : : device , Global : : swapChain , nullptr ) ;
2024-10-07 05:09:46 -05:00
}
2024-10-18 11:57:07 -05:00
VkImageView DeviceControl : : createImageView ( VkImage image , VkFormat format , VkImageAspectFlags flags , uint32_t mipLevels ) {
2024-10-13 20:32:20 -05:00
// This defines the parameters of a newly created image object!
2024-10-13 04:56:45 -05:00
VkImageViewCreateInfo viewInfo { } ;
viewInfo . sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO ;
viewInfo . image = image ;
viewInfo . viewType = VK_IMAGE_VIEW_TYPE_2D ;
viewInfo . format = format ;
2024-10-14 05:26:02 -05:00
viewInfo . subresourceRange . aspectMask = flags ;
2024-10-13 04:56:45 -05:00
viewInfo . subresourceRange . baseMipLevel = 0 ;
viewInfo . subresourceRange . levelCount = 1 ;
viewInfo . subresourceRange . baseArrayLayer = 0 ;
viewInfo . subresourceRange . layerCount = 1 ;
2024-10-18 11:57:07 -05:00
viewInfo . subresourceRange . levelCount = mipLevels ;
2024-10-13 04:56:45 -05:00
VkImageView imageView ;
if ( vkCreateImageView ( Global : : device , & viewInfo , nullptr , & imageView ) ! = VK_SUCCESS ) {
throw std : : runtime_error ( " failed to create image view! " ) ;
}
2024-10-08 23:26:45 -05:00
2024-10-13 04:56:45 -05:00
return imageView ;
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : createImageViews ( ) {
2024-10-24 20:21:10 -05:00
Global : : swapChainImageViews . resize ( swapChainImages . size ( ) ) ;
2024-10-13 04:56:45 -05:00
for ( uint32_t i = 0 ; i < swapChainImages . size ( ) ; i + + ) {
2024-10-24 20:21:10 -05:00
Global : : swapChainImageViews [ i ] = createImageView ( swapChainImages [ i ] , swapChainImageFormat , VK_IMAGE_ASPECT_COLOR_BIT , 1 ) ;
2024-10-07 05:09:46 -05:00
}
}
2024-10-16 06:02:15 -05:00
void DeviceControl : : destroyImageViews ( ) {
2024-10-24 20:21:10 -05:00
for ( auto imageView : Global : : swapChainImageViews ) {
2024-10-08 01:57:32 -05:00
vkDestroyImageView ( Global : : device , imageView , nullptr ) ;
2024-10-07 05:09:46 -05:00
}
2024-10-06 20:17:19 -05:00
}
2024-10-08 01:57:32 -05:00
// --------------------------------------- Getters & Setters ------------------------------------------ //
2024-10-24 20:21:10 -05:00
VkFormat * DeviceControl : : getImageFormat ( ) {
return & swapChainImageFormat ;
2024-10-08 01:57:32 -05:00
}
2024-10-16 06:02:15 -05:00
VkExtent2D DeviceControl : : getSwapChainExtent ( ) {
2024-10-08 01:57:32 -05:00
return swapChainExtent ;
}
2024-10-24 20:21:10 -05:00
std : : vector < VkImage > DeviceControl : : getSwapChainImages ( ) {
return swapChainImages ;
}
2024-10-05 19:59:15 -05:00
}