/** * @file vulkanBase.cp * @author Piotr Krygier (everyonecancode@gmail.com) * @brief Base configuration for Vulkan API * @version 0.1 * @date 2022-05-13 * * @copyright Copyright (c) 2022 * */ #include "vulkan_base.h" #include #include "utilities/localization.h" #include #include "utilities/commons.h" #include "utilities/errors_common.h" #include "utilities/logger.h" #include "vulkan/vulkan_core.h" #include "vulkan_errors.h" #include "vulkan_commons.h" #include "vulkan_commands.h" #include "vulkan_buffers.h" #include "vulkan_image.h" #include "vulkan_render_pass.h" #include "vulkan_swapchain.h" #include "pipeline_builder.h" #include #include #include #define APPLICATION_NAME "RedScarfEngine PoC" #define ENGINE_NAME "RedScarf Engine" /** * @brief Calback for debug messenger * * @param message_severity Message severity * @param message_type Type of a message * @param callback_data Contains all the callback related data in the VkDebugUtilsMessengerCallbackDataEXT structure * @param user_data Data provided by the user * @return VkBool32 Should always return VK_FALSE. The VK_TRUE value is reserved for use in layer development */ static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { enum log_level_t log_level = LOGLEVEL_TRACE; (void)message_severity; (void)message_type; (void)user_data; /* There is no guarantee, that the error message will have single severity flag set, so we can't use switch() * here. We have to check flags and set severity to most important one */ if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { log_level = LOGLEVEL_ERROR; } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { log_level = LOGLEVEL_WARNING; } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { log_level = LOGLEVEL_INFO; } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { log_level = LOGLEVEL_TRACE; } LOGGER(log_level, callback_data->pMessage); return VK_FALSE; } /** * @brief Proxy for vkCreateDebugUtilsMessengerEXT * * @param instance Vulkan instance object * @param create_info VkDebugUtilsMessengerCreateInfoEXT structure * @param allocator Memory allocator * @param debug_messenger Debug messanger handle * @return VkResult VK_SUCCESS on success */ static VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* create_info, const VkAllocationCallbacks* allocator, VkDebugUtilsMessengerEXT* debug_messenger) { PFN_vkCreateDebugUtilsMessengerEXT func = NULL; func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != NULL) { return func(instance, create_info, allocator, debug_messenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } return VK_SUCCESS; } /** * @brief Setup debugging system for Vulkan API * * @param context Graphics context * */ static rse_err_t setup_debug_messenger(VkInstance instance, VkDebugUtilsMessengerEXT* messenger) { VkDebugUtilsMessengerCreateInfoEXT create_info = {0}; create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; create_info.pNext = NULL; create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; create_info.pfnUserCallback = debug_callback; create_info.pUserData = NULL; // Optional create_info.flags = 0U; if (CreateDebugUtilsMessengerEXT(instance, &create_info, NULL, messenger) != VK_SUCCESS) { LOGF(_("Failed to setup debug messenger\n")); return VULKAN_ERROR_DEBUG_MESSENGER_CREATION_FAILED; } return RSE_ERROR_NO_ERROR; } /** * @brief Proxy for vkDestroyDebugUtilsMessengerEXT * * @param instance Vulkan instance object * @param debug_messenger Debug messanger handle * @param allocator Memory allocator */ static void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debug_messenger, const VkAllocationCallbacks* allocator) { PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != NULL) { func(instance, debug_messenger, allocator); } } /** * @brief Create the Vulkan Instance object * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_INSTANCE_INIT_FAILED */ static rse_err_t create_instance(VkInstance* instance) { size_t i = 0U; size_t j = 0U; uint8_t supported = 0U; VkResult result = VK_SUCCESS; uint32_t extensionCount = 0U; uint32_t layersCount = 0U; VkApplicationInfo application_info = {0}; VkInstanceCreateInfo create_info = {0}; VkLayerProperties* layer_properties; VkExtensionProperties* extensions_properties; const char* enabled_instance_extensions_names[] = { "VK_KHR_surface", "VK_KHR_device_group_creation", #ifdef __linux__ #ifdef WAYLAND "VK_KHR_wayland_surface", #else "VK_KHR_xcb_surface", #endif /* WAYLAND */ #elif defined(_WIN32) || defined(WIN32) "VK_KHR_win32_surface", #endif /* _WIN32 || WIN32 */ "VK_EXT_debug_utils", }; #ifndef NDEBUG uint32_t enabled_instance_extensions_count = 4; uint32_t enabled_instance_layers_count = 1; const char* enabled_instance_layers_names[] = { "VK_LAYER_KHRONOS_validation", }; #else uint32_t enabled_instance_extensions_count = 3; uint32_t enabled_instance_layers_count = 0; const char* enabled_instance_layers_names[] = {}; #endif /* NDEBUG */ vkEnumerateInstanceLayerProperties(&layersCount, NULL); rse_malloc(layer_properties, sizeof(VkLayerProperties) * layersCount); vkEnumerateInstanceLayerProperties(&layersCount, layer_properties); vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL); rse_malloc(extensions_properties, sizeof(VkExtensionProperties) * extensionCount); vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions_properties); #ifndef NDEBUG LOGD("Supported Layers: \n"); for (i = 0; i < layersCount; i++) { LOGD("\t-%s\n", layer_properties[i].layerName); } LOGD("Supported Extensions: \n"); for (size_t i = 0; i < extensionCount; i++) { LOGD("\t-%s\n", extensions_properties[i].extensionName); } #endif /* Check if selected layers are supported*/ for (i = 0; i < enabled_instance_layers_count; ++i) { for(j = 0; i < layersCount; ++j) { if(strcmp(layer_properties[j].layerName, enabled_instance_layers_names[i]) == 0) { supported = 1; break; } } if(supported == 0) { LOGF(_("Selected layer is not supported: %s\n"), layer_properties[j].layerName); return VULKAN_ERROR_LAYER_NOT_SUPPORTED; } supported = 0; } /* Check if selected extensions are supported*/ for (i = 0; i < enabled_instance_extensions_count; ++i) { for(j = 0; j < extensionCount; ++j) { if(strcmp(extensions_properties[j].extensionName, enabled_instance_extensions_names[i]) == 0) { supported = 1; break; } } if(supported == 0) { LOGF(_("Selected extension is not supported: %s\n"), extensions_properties[j].extensionName); return VULKAN_ERROR_EXTENSION_NOT_SUPPORTED; } supported = 0; } rse_free(layer_properties); rse_free(extensions_properties); application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; application_info.pNext = NULL; application_info.pApplicationName = APPLICATION_NAME; application_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); application_info.pEngineName = ENGINE_NAME; application_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); application_info.apiVersion = VK_API_VERSION_1_4; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pNext = NULL; create_info.flags = 0U; create_info.pApplicationInfo = &application_info; create_info.enabledLayerCount = enabled_instance_layers_count; create_info.ppEnabledLayerNames = enabled_instance_layers_names; create_info.enabledExtensionCount = enabled_instance_extensions_count; create_info.ppEnabledExtensionNames = enabled_instance_extensions_names; result = vkCreateInstance(&create_info, NULL, instance); if (VK_SUCCESS != result) { LOGF(_("Vulkan instance initialization failed\n")); return VULKAN_ERROR_INSTANCE_INIT_FAILED; } return RSE_ERROR_NO_ERROR; } /** * @brief Create a Surface, connection between Vulkan and actual window * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: */ static rse_err_t create_surface(VkInstance instance, GLFWwindow* window_handle, VkSurfaceKHR* surface) { if (VK_SUCCESS != glfwCreateWindowSurface(instance, window_handle, NULL, surface)) { LOGF(_("Failed to create window surface\n")); return VULKAN_ERROR_SURFACE_CREATION_FAILED; } return RSE_ERROR_NO_ERROR; } /** * @brief Choose physical device. Used later for vkDevice * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_NO_PHYSICAL_DEVICE_FOUND * VULKAN_ERROR_NO_SUITABLE_PHYSICAL_DEVICE_FOUND */ static rse_err_t pick_physical_device(VkInstance instance, VkPhysicalDevice* physical_device) { uint32_t physical_device_count = 0U; VkPhysicalDevice* physical_devices = NULL; VkPhysicalDeviceProperties device_properties = {0};; VkPhysicalDeviceFeatures device_features = {0}; /* Get number of existing physical devices */ vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL); if (0 == physical_device_count) { LOGF(_("No physical graphical physical devices found in system\n")); return VULKAN_ERROR_NO_PHYSICAL_DEVICE_FOUND; } rse_malloc(physical_devices, sizeof(VkPhysicalDevice) * physical_device_count); vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices); /* Check for suitability */ for (size_t physicalDeviceIdx = 0U; physicalDeviceIdx < physical_device_count; ++physicalDeviceIdx) { vkGetPhysicalDeviceProperties(physical_devices[physicalDeviceIdx], &device_properties); vkGetPhysicalDeviceFeatures(physical_devices[physicalDeviceIdx], &device_features); /* Expect discrete graphics device type */ if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { /* Application MUST have geometry shader */ if (device_features.geometryShader) { *physical_device = physical_devices[physicalDeviceIdx]; } } } if (VK_NULL_HANDLE == physical_device) { /* No suitable device found */ LOGF(_("No suitable physical device found\n")); return VULKAN_ERROR_NO_SUITABLE_PHYSICAL_DEVICE_FOUND; } return RSE_ERROR_NO_ERROR; } /** * @brief Create vkDevice object * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_QUEUE_NOT_SUPPORTED * VULKAN_ERROR_DEVICE_CREATION_FAILED */ static rse_err_t create_device(struct graphics_context_t* context) { bool graphics_family_found = false; bool presentation_familiy_found = false; float* graphics_familiy_queue_priorities = NULL; float* presentation_familiy_queue_priorities = NULL; uint32_t device_queues_count = 0; uint32_t graphics_family_idx = 0; uint32_t presentation_familiy_idx = 0; uint32_t queue_families_property_count = 0U; uint32_t enabled_device_extensions_count = 2; VkBool32 bindless_supported = VK_FALSE; VkDeviceCreateInfo device_create_info = {0}; VkQueueFamilyProperties* queue_family_properties = NULL; VkDeviceQueueCreateInfo device_queue_createinfos[2] = {0}; VkPhysicalDeviceFeatures2 physical_features2 = {0}; VkPhysicalDeviceDescriptorIndexingFeatures indexing_features = {0}; const char* enabled_device_extensions_names[] = { "VK_KHR_swapchain", "VK_EXT_descriptor_indexing", }; /* Enable descriptori indexing for bindless textures */ indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; physical_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physical_features2.pNext = &indexing_features; vkGetPhysicalDeviceFeatures2(context->physical_device, &physical_features2); bindless_supported = indexing_features.descriptorBindingPartiallyBound && indexing_features.runtimeDescriptorArray; if (bindless_supported == VK_FALSE) { LOGF(_("Required features for bindless design not supported\n")); return VULKAN_ERROR_MISSING_FEATURES_FOR_BINDLESS_DESIGN; } /* Get information about supported queue families */ vkGetPhysicalDeviceQueueFamilyProperties(context->physical_device, &queue_families_property_count, NULL); rse_malloc(queue_family_properties, sizeof(VkQueueFamilyProperties) * queue_families_property_count); vkGetPhysicalDeviceQueueFamilyProperties(context->physical_device, &queue_families_property_count, queue_family_properties); /* Get queue families that support graphics operations AND have most * queues. */ { size_t numberOfQueues = 0; for (uint32_t familyIdx = 0; familyIdx < queue_families_property_count; ++familyIdx) { LOGI("Checking queue: %ld", familyIdx); if (queue_family_properties[familyIdx].queueFlags & VK_QUEUE_GRAPHICS_BIT) { if (queue_family_properties[familyIdx].queueCount > numberOfQueues) { graphics_family_idx = familyIdx; graphics_family_found = true; LOGI("Setting graphics family idx to %ld", familyIdx); numberOfQueues = queue_family_properties[familyIdx].queueCount; device_queues_count++; } } /* Check for family with surface support */ VkBool32 presentSupport = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(context->physical_device, familyIdx, context->surface, &presentSupport); if (presentSupport) { presentation_familiy_idx = familyIdx; presentation_familiy_found = true; /* If this is the same queue family as in graphics family, don't increase queue count */ if (presentation_familiy_idx != graphics_family_idx) { device_queues_count++; } } if (graphics_family_found && presentation_familiy_found) { break; } } } /* This is not magic number ;P. We want to support exact number of required queues flags */ if (0 > graphics_family_idx) { LOGF(_("Selected physical device does not support required queue capabilities\n")); return VULKAN_ERROR_QUEUE_NOT_SUPPORTED; } context->queue_family_indices[0] = graphics_family_idx; rse_malloc(graphics_familiy_queue_priorities, sizeof(float) * queue_family_properties[graphics_family_idx].queueCount); for (size_t i = 0; i < queue_family_properties[graphics_family_idx].queueCount; i++) { graphics_familiy_queue_priorities[i] = 1.0f; } rse_malloc(presentation_familiy_queue_priorities, sizeof(float) * queue_family_properties[presentation_familiy_idx].queueCount); for (size_t i = 0; i < queue_family_properties[presentation_familiy_idx].queueCount; i++) { presentation_familiy_queue_priorities[i] = 1.0f; } device_queue_createinfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; device_queue_createinfos[0].pNext = NULL; device_queue_createinfos[0].flags = 0; device_queue_createinfos[0].queueFamilyIndex = graphics_family_idx; device_queue_createinfos[0].queueCount = queue_family_properties[graphics_family_idx].queueCount; device_queue_createinfos[0].pQueuePriorities = graphics_familiy_queue_priorities; device_queue_createinfos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; device_queue_createinfos[1].pNext = NULL; device_queue_createinfos[1].flags = 0; device_queue_createinfos[1].queueFamilyIndex = presentation_familiy_idx; device_queue_createinfos[1].queueCount = queue_family_properties[presentation_familiy_idx].queueCount; device_queue_createinfos[1].pQueuePriorities = presentation_familiy_queue_priorities; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.pNext = &physical_features2; device_create_info.flags = 0U; device_create_info.queueCreateInfoCount = device_queues_count; device_create_info.pQueueCreateInfos = device_queue_createinfos; device_create_info.enabledLayerCount = 0; device_create_info.ppEnabledLayerNames = NULL; device_create_info.enabledExtensionCount = enabled_device_extensions_count; device_create_info.ppEnabledExtensionNames = enabled_device_extensions_names; device_create_info.pEnabledFeatures = NULL; if (VK_SUCCESS != vkCreateDevice(context->physical_device, &device_create_info, NULL, &context->device)) { LOGF(_("Failed to create logical device\n")); return VULKAN_ERROR_DEVICE_CREATION_FAILED; } vkGetDeviceQueue(context->device, graphics_family_idx, 0, &context->graphics_queue); vkGetDeviceQueue(context->device, presentation_familiy_idx, 0, &context->present_queue); rse_free(presentation_familiy_queue_priorities); rse_free(graphics_familiy_queue_priorities); rse_free(queue_family_properties); return RSE_ERROR_NO_ERROR; } /** * @brief Create a Memory Allocator object * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_ALLOCATOR_CREATION_FAILED */ static rse_err_t create_memory_allocator(struct graphics_context_t* context) { VmaVulkanFunctions vulkan_functions = {0}; vulkan_functions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr; vulkan_functions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr; VmaAllocatorCreateInfo allocator_create_info = {0}; allocator_create_info.vulkanApiVersion = VK_API_VERSION_1_3; allocator_create_info.physicalDevice = context->physical_device; allocator_create_info.device = context->device; allocator_create_info.instance = context->instance; allocator_create_info.pVulkanFunctions = &vulkan_functions; if (VK_SUCCESS != vmaCreateAllocator(&allocator_create_info, &context->allocator)) { LOGF(_("Failed to create Vulkan Memory Allocator\n")); return VULKAN_ERROR_ALLOCATOR_CREATION_FAILED; } return RSE_ERROR_NO_ERROR; } /** * @brief Create a Sync Objects * * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: * VULKAN_ERROR_SYNCOBJCTS_CREATION_FAILED */ static rse_err_t create_sync_objects(struct graphics_context_t* context) { VkSemaphoreCreateInfo semaphore_info = {0}; semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo fenceInfo = {0}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) { if (VK_SUCCESS != vkCreateSemaphore(context->device, &semaphore_info, NULL, &context->image_available_semaphores[i]) || VK_SUCCESS != vkCreateSemaphore(context->device, &semaphore_info, NULL, &context->render_finished_semaphores[i]) || VK_SUCCESS != vkCreateFence(context->device, &fenceInfo, NULL, &context->in_flight_fences[i])) { LOGF(_("Failed to create sync objects.\n")); return VULKAN_ERROR_SYNCOBJCTS_CREATION_FAILED; } } return RSE_ERROR_NO_ERROR; } rse_err_t init_vulkan(struct graphics_context_t* context) { rse_err_t status = RSE_ERROR_NO_ERROR; STATUS_CHECK(create_instance(&context->instance)); STATUS_CHECK(setup_debug_messenger(context->instance, &context->debug_messenger)); STATUS_CHECK(create_surface(context->instance, context->window_handle, &context->surface)); STATUS_CHECK(pick_physical_device(context->instance, &context->physical_device)); STATUS_CHECK(create_device(context)); STATUS_CHECK(create_memory_allocator(context)); STATUS_CHECK(create_render_pass(context)); STATUS_CHECK(init_commands(context)); STATUS_CHECK(create_buffers(context)); STATUS_CHECK(init_vulkan_images(context)); STATUS_CHECK(create_swapchain_and_framebuffers(context)); return status; } rse_err_t run_vulkan(struct graphics_context_t* context) { rse_err_t status = RSE_ERROR_NO_ERROR; STATUS_CHECK(create_sync_objects(context)); init_time(); return status; } rse_err_t draw_frame(struct graphics_context_t* context) { uint32_t image_index = 0U; VkResult result = VK_FALSE; VkSubmitInfo submit_info = {0}; VkSemaphore wait_semaphores[1]; VkPipelineStageFlags wait_stages[1]; VkSemaphore signal_semaphores[1]; VkPresentInfoKHR present_info = {0}; VkSwapchainKHR swap_chains[1]; vkWaitForFences(context->device, 1, &context->in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX); update_time(); result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, context->image_available_semaphores[context->current_frame], VK_NULL_HANDLE, &image_index); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreate_swapchain(context); return RSE_ERROR_NO_ERROR; } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { LOGF(_("Failed to acquire swapchain image\n")); return VULKAN_ERROR_ACQUIRE_SWAPCHAIN_FAILED; } // update_uniform_buffers(context); /* Only reset the fence if we are submitting work */ vkResetFences(context->device, 1, &context->in_flight_fences[context->current_frame]); reset_command_buffer(context); record_command_buffer(context, image_index); submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; wait_semaphores[0] = context->image_available_semaphores[context->current_frame]; wait_stages[0] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = wait_semaphores; submit_info.pWaitDstStageMask = wait_stages; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &context->command_buffers[context->current_frame]; signal_semaphores[0] = context->render_finished_semaphores[context->current_frame]; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = signal_semaphores; if (vkQueueSubmit(context->graphics_queue, 1, &submit_info, context->in_flight_fences[context->current_frame]) != VK_SUCCESS) { LOGF(_("Failed to submit draw command buffer!\n")); return VULKAN_ERROR_DRAW_FAILED; } present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present_info.waitSemaphoreCount = 1; present_info.pWaitSemaphores = signal_semaphores; swap_chains[0] = context->swapchain; present_info.swapchainCount = 1; present_info.pSwapchains = swap_chains; present_info.pImageIndices = &image_index; result = vkQueuePresentKHR(context->present_queue, &present_info); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || context->is_framebuffer_resized) { context->is_framebuffer_resized = false; recreate_swapchain(context); } else if (result != VK_SUCCESS) { LOGF(_("Failed to submit draw command buffer!\n")); return VULKAN_ERROR_DRAW_FAILED; } context->current_frame = (context->current_frame + 1) % SWAP_BUFFER_COUNT; return RSE_ERROR_NO_ERROR; } void deinit_vulkan(struct graphics_context_t* context) { destroy_buffers(context); for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) { vkDestroySemaphore(context->device, context->image_available_semaphores[i], NULL); vkDestroySemaphore(context->device, context->render_finished_semaphores[i], NULL); vkDestroyFence(context->device, context->in_flight_fences[i], NULL); } destroy_textures(context); destroy_commands(context); destroy_render_pass(context); cleanup_swapchain(context); destroy_pipelines(context); vmaDestroyAllocator(context->allocator); vkDestroyDevice(context->device, NULL); DestroyDebugUtilsMessengerEXT(context->instance, context->debug_messenger, NULL); vkDestroySurfaceKHR(context->instance, context->surface, NULL); vkDestroyInstance(context->instance, NULL); }