|
|
|
@ -4,22 +4,112 @@
|
|
|
|
|
#include <vulkan/vulkan_core.h>
|
|
|
|
|
#include "debug.h"
|
|
|
|
|
#include "list.h"
|
|
|
|
|
#include "mathext.h"
|
|
|
|
|
|
|
|
|
|
static const List requiredVulkanExtensions = {
|
|
|
|
|
.data = (char*[]){
|
|
|
|
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME
|
|
|
|
|
},
|
|
|
|
|
.cap = 1,
|
|
|
|
|
.len = 1,
|
|
|
|
|
.element_size = sizeof(char*)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// get the list of supported vulkan protocol extensions
|
|
|
|
|
static List RenderContextGetExtensions(const RenderContext *self) {
|
|
|
|
|
// get the number of available extensions
|
|
|
|
|
unsigned extCount = 0;
|
|
|
|
|
SDL_Vulkan_GetInstanceExtensions(self->window, &extCount, 0);
|
|
|
|
|
List extensions = list_from_type_with_len(char*, extCount);
|
|
|
|
|
if(!SDL_Vulkan_GetInstanceExtensions(self->window, &extCount, extensions.data)) {
|
|
|
|
|
uint32_t count = 0;
|
|
|
|
|
SDL_Vulkan_GetInstanceExtensions(self->window, &count, 0);
|
|
|
|
|
List extensions = list_from_type_with_len(char*, count);
|
|
|
|
|
if(!SDL_Vulkan_GetInstanceExtensions(self->window, &count, extensions.data)) {
|
|
|
|
|
LOG_WARNING("Failed to fetch vulkan instance extensions '%s'", SDL_GetError());
|
|
|
|
|
}
|
|
|
|
|
return extensions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static List GetDeviceExtensions(VkPhysicalDevice device) {
|
|
|
|
|
uint32_t count = 0;
|
|
|
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &count, NULL);
|
|
|
|
|
List extensions = list_with_len(sizeof(VkExtensionProperties), count);
|
|
|
|
|
if(extensions.len > 0)
|
|
|
|
|
vkEnumerateDeviceExtensionProperties(device, NULL, &count, extensions.data);
|
|
|
|
|
return extensions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static VkSurfaceFormatKHR SelectSurfaceFormat(List availableFormats) {
|
|
|
|
|
list_foreach(VkSurfaceFormatKHR*, format, &availableFormats) {
|
|
|
|
|
if(format->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
|
|
|
|
|
&& format->format == VK_FORMAT_B8G8R8A8_SRGB)
|
|
|
|
|
return *format;
|
|
|
|
|
}
|
|
|
|
|
return *list_at_as(VkSurfaceFormatKHR, &availableFormats, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static VkPresentModeKHR SelectPresentMode(List availableModes) {
|
|
|
|
|
list_foreach(VkPresentModeKHR*, mode, &availableModes) {
|
|
|
|
|
if(*mode == VK_PRESENT_MODE_MAILBOX_KHR)
|
|
|
|
|
return *mode;
|
|
|
|
|
}
|
|
|
|
|
return VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static VkExtent2D SelectSwapExtent(RenderContext *self, const VkSurfaceCapabilitiesKHR *capabilities) {
|
|
|
|
|
if(capabilities->currentExtent.width != UINT32_MAX) {
|
|
|
|
|
return capabilities->currentExtent;
|
|
|
|
|
}
|
|
|
|
|
int width, height;
|
|
|
|
|
SDL_Vulkan_GetDrawableSize(self->window, &width, &height);
|
|
|
|
|
return (VkExtent2D) {
|
|
|
|
|
.width = CLAMP((uint32_t)width, capabilities->minImageExtent.width, capabilities->maxImageExtent.width),
|
|
|
|
|
.height = CLAMP((uint32_t)height, capabilities->minImageExtent.height, capabilities->maxImageExtent.height)
|
|
|
|
|
};;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int CheckExtensionSupport(VkPhysicalDevice device) {
|
|
|
|
|
List supported = GetDeviceExtensions(device);
|
|
|
|
|
int isValid = 1;
|
|
|
|
|
for(size_t i = 0; isValid && i < requiredVulkanExtensions.len; ++i) {
|
|
|
|
|
if(list_contains(&supported, ((VkExtensionProperties*)requiredVulkanExtensions.data) + i) == 0)
|
|
|
|
|
isValid = 0;
|
|
|
|
|
}
|
|
|
|
|
list_empty(&supported);
|
|
|
|
|
ASSERT_RETURN(isValid, 0, "Not all required vulkan extensions are supported");
|
|
|
|
|
return isValid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static SwapChainSupportInfo RenderContextGetSwapChainSupport(RenderContext *self, VkPhysicalDevice device) {
|
|
|
|
|
SwapChainSupportInfo support = {0};
|
|
|
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, self->windowSurface, &support.capabilities);
|
|
|
|
|
uint32_t count = 0;
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, self->windowSurface, &count, NULL);
|
|
|
|
|
if(count > 0) {
|
|
|
|
|
support.formats = list_with_len(sizeof(VkSurfaceFormatKHR), count);
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, self->windowSurface, &count, support.formats.data);
|
|
|
|
|
}
|
|
|
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(device, self->windowSurface, &count, NULL);
|
|
|
|
|
if(count > 0) {
|
|
|
|
|
support.presentModes = list_with_len(sizeof(VkPresentModeKHR), count);
|
|
|
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(device, self->windowSurface, &count, support.presentModes.data);
|
|
|
|
|
}
|
|
|
|
|
return support;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void SwapChainSupportInfoClean(SwapChainSupportInfo *self) {
|
|
|
|
|
list_empty(&self->formats);
|
|
|
|
|
list_empty(&self->presentModes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int CheckSupportedSwapChain(RenderContext *self, VkPhysicalDevice device) {
|
|
|
|
|
SwapChainSupportInfo swapchain = RenderContextGetSwapChainSupport(self, device);
|
|
|
|
|
int validSwapChain = swapchain.formats.len > 0
|
|
|
|
|
&& swapchain.presentModes.len > 0;
|
|
|
|
|
SwapChainSupportInfoClean(&swapchain);
|
|
|
|
|
return validSwapChain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// returns 1 if the GPU reference by device is usable for this application
|
|
|
|
|
// 0 otherwise
|
|
|
|
|
static int GpuIsUsable(VkPhysicalDevice device) {
|
|
|
|
|
static int GpuIsUsable(RenderContext* self, VkPhysicalDevice device) {
|
|
|
|
|
// get properties and features
|
|
|
|
|
VkPhysicalDeviceProperties properties;
|
|
|
|
|
VkPhysicalDeviceFeatures features;
|
|
|
|
@ -28,7 +118,9 @@ static int GpuIsUsable(VkPhysicalDevice device) {
|
|
|
|
|
// integrated or discrete works
|
|
|
|
|
return (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
|
|
|
|
|
// needs geometry shader support
|
|
|
|
|
&& (features.geometryShader);
|
|
|
|
|
&& (features.geometryShader)
|
|
|
|
|
&& CheckExtensionSupport(device)
|
|
|
|
|
&& CheckSupportedSwapChain(self, device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize the SDL window for this application
|
|
|
|
@ -70,7 +162,7 @@ static void RenderContextSelectPhysicalDevice(RenderContext *self) {
|
|
|
|
|
vkEnumeratePhysicalDevices(self->vulkanInstance, &deviceCount, devices.data);
|
|
|
|
|
// search through the available devices for one suitable to our usecase
|
|
|
|
|
list_foreach(VkPhysicalDevice*, device, &devices) {
|
|
|
|
|
if(GpuIsUsable(*device)) {
|
|
|
|
|
if(GpuIsUsable(self, *device)) {
|
|
|
|
|
self->physicalDevice = *device;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
@ -148,15 +240,20 @@ static void RenderContextCreateLogicalDevice(RenderContext *self) {
|
|
|
|
|
const float priorities = 1.f;
|
|
|
|
|
list_foreach(VkDeviceQueueCreateInfo*, info, &queueCreateInfo)
|
|
|
|
|
info->pQueuePriorities = &priorities;
|
|
|
|
|
|
|
|
|
|
List extensions = list_copy(&requiredVulkanExtensions);
|
|
|
|
|
VkDeviceCreateInfo createInfo = {
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
|
|
|
|
.pQueueCreateInfos = queueCreateInfo.data,
|
|
|
|
|
.queueCreateInfoCount = queueCreateInfo.len,
|
|
|
|
|
.enabledExtensionCount = (uint32_t)extensions.len,
|
|
|
|
|
.ppEnabledExtensionNames = extensions.data
|
|
|
|
|
};
|
|
|
|
|
VkPhysicalDeviceFeatures features = {0};
|
|
|
|
|
if(vkCreateDevice(self->physicalDevice, &createInfo, NULL, &self->device) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create vulkan logical device");
|
|
|
|
|
}
|
|
|
|
|
list_empty(&extensions);
|
|
|
|
|
list_empty(&queueCreateInfo);
|
|
|
|
|
vkGetDeviceQueue(self->device, queueFamilies.graphicsQueue, 0, &self->graphicsQueue);
|
|
|
|
|
vkGetDeviceQueue(self->device, queueFamilies.presentQueue, 0, &self->presentQueue);
|
|
|
|
@ -168,17 +265,71 @@ static void RenderContextCreateSurface(RenderContext *self) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void RenderContextCreateSwapchain(RenderContext *self) {
|
|
|
|
|
// convert supported capabilities to prefered settings
|
|
|
|
|
SwapChainSupportInfo support = RenderContextGetSwapChainSupport(self, self->physicalDevice);
|
|
|
|
|
self->swapchainExtent = SelectSwapExtent(self, &support.capabilities);
|
|
|
|
|
VkSurfaceFormatKHR surfaceFormat = SelectSurfaceFormat(support.formats);
|
|
|
|
|
self->swapchainFormat = surfaceFormat.format;
|
|
|
|
|
VkPresentModeKHR presentMode = SelectPresentMode(support.presentModes);
|
|
|
|
|
uint32_t chainLength = support.capabilities.minImageCount;
|
|
|
|
|
if(support.capabilities.maxImageCount > 0)
|
|
|
|
|
chainLength = MAX(chainLength, support.capabilities.maxImageCount);
|
|
|
|
|
VkSwapchainCreateInfoKHR createInfo = {
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
|
|
|
|
.surface = self->windowSurface,
|
|
|
|
|
.minImageCount = chainLength,
|
|
|
|
|
.imageFormat = surfaceFormat.format,
|
|
|
|
|
.imageColorSpace = surfaceFormat.colorSpace,
|
|
|
|
|
.imageExtent = self->swapchainExtent,
|
|
|
|
|
.imageArrayLayers = 1,
|
|
|
|
|
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
|
|
|
|
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
|
|
|
|
.preTransform = support.capabilities.currentTransform,
|
|
|
|
|
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
|
|
|
|
.presentMode = presentMode,
|
|
|
|
|
.clipped = VK_TRUE,
|
|
|
|
|
.oldSwapchain = VK_NULL_HANDLE,
|
|
|
|
|
};
|
|
|
|
|
// if the present and graphics queue are not the same, they need to work together
|
|
|
|
|
// allow this swapchain to be accessed by both
|
|
|
|
|
QueueFamilyIndexes families = RenderContextFindQueueFamilies(self);
|
|
|
|
|
if(families.graphicsQueue != families.presentQueue) {
|
|
|
|
|
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
|
|
|
|
createInfo.queueFamilyIndexCount = 2;
|
|
|
|
|
createInfo.pQueueFamilyIndices = families.as_array;
|
|
|
|
|
}
|
|
|
|
|
SwapChainSupportInfoClean(&support);
|
|
|
|
|
if(vkCreateSwapchainKHR(self->device, &createInfo, NULL, &self->swapchain) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create vulkan swapchain");
|
|
|
|
|
}
|
|
|
|
|
vkGetSwapchainImagesKHR(self->device, self->swapchain, &chainLength, NULL);
|
|
|
|
|
self->swapchainImages = list_with_len(sizeof(VkImage), chainLength);
|
|
|
|
|
vkGetSwapchainImagesKHR(self->device, self->swapchain, &chainLength, self->swapchainImages.data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderContext *RenderContextCreate(const VkApplicationInfo *appinfo) {
|
|
|
|
|
LOG_INFO("Creating render context");
|
|
|
|
|
RenderContext* self = malloc(sizeof(RenderContext));
|
|
|
|
|
memset(self, 0x0, sizeof(RenderContext));
|
|
|
|
|
LOG_INFO("Initializing SDL window");
|
|
|
|
|
RenderContextInitSdlWindow(self, appinfo);
|
|
|
|
|
LOG_INFO("Initializing vulkan instance");
|
|
|
|
|
RenderContextInitVulkanInstance(self, appinfo);
|
|
|
|
|
RenderContextSelectPhysicalDevice(self);
|
|
|
|
|
LOG_INFO("Creating window render surface");
|
|
|
|
|
RenderContextCreateSurface(self);
|
|
|
|
|
LOG_INFO("Selecting physical device");
|
|
|
|
|
RenderContextSelectPhysicalDevice(self);
|
|
|
|
|
LOG_INFO("Creating logical device");
|
|
|
|
|
RenderContextCreateLogicalDevice(self);
|
|
|
|
|
LOG_INFO("Set up swapchain");
|
|
|
|
|
RenderContextCreateSwapchain(self);
|
|
|
|
|
LOG_INFO("Finished setting up vulkan render context");
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RenderContextDestroy(RenderContext *self) {
|
|
|
|
|
vkDestroySwapchainKHR(self->device, self->swapchain, NULL);
|
|
|
|
|
vkDestroySurfaceKHR(self->vulkanInstance, self->windowSurface, NULL);
|
|
|
|
|
vkDestroyDevice(self->device, NULL);
|
|
|
|
|
vkDestroyInstance(self->vulkanInstance, NULL);
|
|
|
|
|