From 25d8a48c3c667e6b5aa62bbfdb4b7e3c8a8b1200 Mon Sep 17 00:00:00 2001 From: Amber Date: Tue, 25 Oct 2022 10:35:22 +0200 Subject: [PATCH] Add swapchain creation. --- src/main.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index d194e4d..b161e13 100644 --- a/src/main.c +++ b/src/main.c @@ -17,6 +17,19 @@ const char* validation_layers[] = { "VK_LAYER_KHRONOS_validation", }; +const char* required_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +typedef struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + uint32_t num_formats; + VkSurfaceFormatKHR* formats; + uint32_t num_modes; + VkPresentModeKHR* present_modes; +} SwapChainSupportDetails; + + typedef struct ApplicationState { GLFWwindow* window; //information about the connection between application and vulkan itself @@ -31,6 +44,9 @@ typedef struct ApplicationState { VkSurfaceKHR surface; //presentation queue VkQueue present_queue; + //swapchain details: + SwapChainSupportDetails details; + VkSwapchainKHR swapchain; } ApplicationState; typedef struct QueueFamilyIndices { @@ -164,6 +180,60 @@ QueueFamilyIndices find_queue_families(ApplicationState* state, VkPhysicalDevice return ret; } +//get supported formats and modes +SwapChainSupportDetails get_swapchain_details(ApplicationState* state, VkPhysicalDevice device) { + SwapChainSupportDetails ret = {0}; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, state->surface, &ret.capabilities); + + uint32_t format_count; + + vkGetPhysicalDeviceSurfaceFormatsKHR(device, state->surface, &format_count, NULL); + ret.num_formats = format_count; + ret.formats = malloc(sizeof(VkSurfaceFormatKHR) * format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, state->surface, &format_count, ret.formats); + + uint32_t present_modes_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, state->surface, &present_modes_count, NULL); + ret.num_modes = present_modes_count; + ret.present_modes = malloc(sizeof(VkSurfaceFormatKHR) * present_modes_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, state->surface, &format_count, ret.present_modes); + + printf("Device has %x present modes and %x formats for surface\n", present_modes_count, format_count); + + return ret; +} + +bool extensions_supported(ApplicationState* state, VkPhysicalDevice device) { + uint32_t num_extensions; + vkEnumerateDeviceExtensionProperties(device, NULL, &num_extensions, NULL); + + VkExtensionProperties* available_extensions = malloc(sizeof(VkExtensionProperties) * num_extensions); + vkEnumerateDeviceExtensionProperties(device, NULL, &num_extensions, available_extensions); + + printf("Device extensions:\n"); + for (int i = 0; i < num_extensions; i++) { + printf("\t%s\n", available_extensions[i].extensionName); + } + + for (int i = 0; i < ARRSIZE(required_extensions); i++) { + bool found = false; + for (int j = 0; j < num_extensions; j++) { + if (!strcmp(required_extensions[i], available_extensions[j].extensionName)) { + found = true; + } + } + + if (!found) { + return false; + } + } + + free(available_extensions); + + return true; +} + void pick_physical_device(ApplicationState* state) { uint32_t device_count = 0; vkEnumeratePhysicalDevices(state->instance, &device_count, NULL); @@ -189,13 +259,22 @@ void pick_physical_device(ApplicationState* state) { QueueFamilyIndices families = find_queue_families(state, device); + SwapChainSupportDetails details = get_swapchain_details(state, device); + //for now we only require that a graphics queue is present - if (families.graphics_present && families.present_present) { + if (families.graphics_present && families.present_present && extensions_supported(state, device) && + //we only care that at least one mode and one format exist + details.num_modes != 0 && details.num_formats != 0) { + state->details = details; printf("graphics queue at %x, present queue at %x\n", families.graphics_queue, families.present_queue); break; + } else { + free (details.present_modes); + free (details.formats); + printf("device does not support all necessary extensions and queues"); } } - + //TODO print unsupported message state->physical_device = devices[i]; free(devices); } @@ -244,7 +323,8 @@ void create_logical_device(ApplicationState* state) { create_info.pQueueCreateInfos = queue_create_infos; create_info.queueCreateInfoCount = j; create_info.pEnabledFeatures = &device_features; - create_info.enabledExtensionCount = 0; + create_info.enabledExtensionCount = ARRSIZE(required_extensions); + create_info.ppEnabledExtensionNames = required_extensions; if (VALIDATE) { create_info.enabledLayerCount = ARRSIZE(validation_layers); @@ -273,11 +353,131 @@ void create_surface(ApplicationState* state) { } } +//We prefer one format but it may not be available +VkSurfaceFormatKHR choose_swap_surface_format(VkSurfaceFormatKHR* formats, uint32_t num) { + for (int i = 0; i < num; i++) { + if (formats[i].format == VK_FORMAT_B8G8R8_SRGB && formats[i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { + return formats[i]; + } + } + + return formats[0]; +} + +//presentation modes determines how images show up on screen +//VK_PRESENT_MODE_IMMEDIATE_KHR: Images submitted by your application are transferred to the screen right away, which may result in tearing. + +//VK_PRESENT_MODE_FIFO_KHR: The swap chain is a queue where the display takes an image from the front of the queue when the display is +//refreshed and the program inserts rendered images at the back of the queue. If the queue is full then the program has to wait. +//This is most similar to vertical sync as found in modern games. The moment that the display is refreshed is known as "vertical blank". + +//VK_PRESENT_MODE_FIFO_RELAXED_KHR: This mode only differs from the previous one if the application is late and the queue was empty at +//the last vertical blank. Instead of waiting for the next vertical blank, the image is transferred right away when it finally arrives. +//This may result in visible tearing. + +//VK_PRESENT_MODE_MAILBOX_KHR: This is another variation of the second mode. Instead of blocking the application when the queue is full, +//the images that are already queued are simply replaced with the newer ones. This mode can be used to render frames as fast as possible +//while still avoiding tearing, resulting in fewer latency issues than standard vertical sync. This is commonly known as "triple buffering", +//although the existence of three buffers alone does not necessarily mean that the framerate is unlocked. +VkPresentModeKHR choose_swap_present_mode(VkPresentModeKHR* modes, uint32_t num) { + for (int i = 0; i < num; i++) { + if (modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + return modes[i]; + } + } + + //guaranteed to exist + return VK_PRESENT_MODE_FIFO_KHR; +} + +uint32_t clamp(uint32_t val, uint32_t min, uint32_t max) { + if (val > max) { + return max; + } else if (val < min) { + return min; + } + return val; +} + +//resolutoion of the swapchain images +VkExtent2D choose_swap_extent(ApplicationState* state, VkSurfaceCapabilitiesKHR capabilities) { + if (capabilities.currentExtent.width != UINT32_MAX) { + return capabilities.currentExtent; + } else { + int width, height; + + glfwGetFramebufferSize(state->window, &width, &height); + + VkExtent2D extent; + extent.width = clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + extent.height = clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return extent; + } +} + +void create_swap_chain(ApplicationState* state) { + VkSurfaceFormatKHR format = choose_swap_surface_format(state->details.formats, state->details.num_formats); + VkPresentModeKHR mode = choose_swap_present_mode(state->details.present_modes, state->details.num_modes); + VkExtent2D extent = choose_swap_extent(state, state->details.capabilities); + + uint32_t image_count = state->details.capabilities.minImageCount + 1; + + if (state->details.capabilities.maxImageCount > 0 && image_count > state->details.capabilities.maxImageCount) { + image_count = state->details.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR create_info; + create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + create_info.surface = state->surface; + create_info.minImageCount = image_count; + create_info.imageFormat = format.format; + create_info.imageColorSpace = format.colorSpace; + create_info.imageExtent = extent; + //1 unless stereoscopic 3d + create_info.imageArrayLayers = 1; + //we directly render to this image + create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + //next we need to determine if images will be shared by multiple queues + + QueueFamilyIndices families = find_queue_families(state, state->physical_device); + uint32_t indices[] = {families.graphics_queue, families.present_queue}; + + if (families.present_queue != families.graphics_queue) { + create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + create_info.queueFamilyIndexCount = 2; + create_info.pQueueFamilyIndices = indices; + } else { + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + create_info.queueFamilyIndexCount = 0; + create_info.pQueueFamilyIndices = NULL; + } + + //no transformations (such as rotations and so on) + create_info.preTransform = state->details.capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = mode; + //pixels that are obscured by other windows do not matter + create_info.clipped = VK_TRUE; + + //we don't handle resize for now + create_info.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(state->device, &create_info, NULL, &state->swapchain) != VK_SUCCESS) { + printf("failed to create swpachain\n"); + exit(1); + } + + printf("swapchain created\n"); +} + void init_vulkan(ApplicationState* state) { create_instance(state); create_surface(state); pick_physical_device(state); create_logical_device(state); + create_swap_chain(state); } void main_loop(ApplicationState* state) { @@ -287,6 +487,7 @@ void main_loop(ApplicationState* state) { } void terminate(ApplicationState* state) { + vkDestroySwapchainKHR(state->device, state->swapchain, NULL); vkDestroyDevice(state->device, NULL); vkDestroySurfaceKHR(state->instance, state->surface, NULL); vkDestroyInstance(state->instance, NULL);