#include "engine/renderer/vulkan/vulkan_context.h" #include #include #include #include #include "base/log.h" #include "third_party/vulkan/vk_enum_string_helper.h" #define GET_PROC_ADDR(func, obj, entrypoint) \ { \ entrypoint = (PFN_vk##entrypoint)func(obj, "vk" #entrypoint); \ if (entrypoint == nullptr) { \ DLOG(0) << #func << " failed to find vk"; \ return false; \ } \ } namespace eng { VulkanContext::VulkanContext() { #if defined(_DEBUG) && !defined(__ANDROID__) use_validation_layers_ = true; #endif } VulkanContext::~VulkanContext() { if (instance_ != VK_NULL_HANDLE) { if (use_validation_layers_) DestroyDebugUtilsMessengerEXT(instance_, dbg_messenger_, nullptr); vkDestroyInstance(instance_, nullptr); instance_ = VK_NULL_HANDLE; } } bool VulkanContext::Initialize() { if (instance_ != VK_NULL_HANDLE) return true; if (volkInitialize() != VK_SUCCESS) return false; if (!CreatePhysicalDevice()) return false; return true; } void VulkanContext::Shutdown() { if (device_ != VK_NULL_HANDLE) { for (int i = 0; i < kFrameLag; i++) { vkDestroyFence(device_, fences_[i], nullptr); vkDestroySemaphore(device_, image_acquired_semaphores_[i], nullptr); vkDestroySemaphore(device_, draw_complete_semaphores_[i], nullptr); if (separate_present_queue_) { vkDestroySemaphore(device_, image_ownership_semaphores_[i], nullptr); } } vkDestroyDevice(device_, nullptr); device_ = VK_NULL_HANDLE; } queues_initialized_ = false; separate_present_queue_ = false; swapchain_image_count_ = 0; command_buffers_.clear(); window_ = {}; } VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::DebugMessengerCallback( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { // This error needs to be ignored because the AMD allocator will mix up memory // types on IGP processors. if (strstr(callback_data->pMessage, "Mapping an image with layout") != nullptr && strstr(callback_data->pMessage, "can result in undefined behavior if this memory is used by the " "device") != nullptr) { return VK_FALSE; } // This needs to be ignored because Validator is wrong here. if (strstr(callback_data->pMessage, "SPIR-V module not valid: Pointer operand") != nullptr && strstr(callback_data->pMessage, "must be a memory object") != nullptr) { return VK_FALSE; } // Workaround for Vulkan-Loader usability bug: // https://github.com/KhronosGroup/Vulkan-Loader/issues/262. if (strstr(callback_data->pMessage, "wrong ELF class: ELFCLASS32") != nullptr) { return VK_FALSE; } if (callback_data->pMessageIdName && strstr(callback_data->pMessageIdName, "UNASSIGNED-CoreValidation-DrawState-ClearCmdBeforeDraw") != nullptr) { return VK_FALSE; } std::string type_string; switch (message_type) { case (VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT): type_string = "GENERAL"; break; case (VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT): type_string = "VALIDATION"; break; case (VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT): type_string = "PERFORMANCE"; break; case (VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT): type_string = "VALIDATION|PERFORMANCE"; break; } std::string objects_string; if (callback_data->objectCount > 0) { objects_string = "\n\tObjects - " + std::to_string(callback_data->objectCount); for (uint32_t object = 0; object < callback_data->objectCount; ++object) { objects_string += "\n\t\tObject[" + std::to_string(object) + "]" + " - " + string_VkObjectType(callback_data->pObjects[object].objectType) + ", Handle " + std::to_string(callback_data->pObjects[object].objectHandle); if (nullptr != callback_data->pObjects[object].pObjectName && strlen(callback_data->pObjects[object].pObjectName) > 0) { objects_string += ", Name \"" + std::string(callback_data->pObjects[object].pObjectName) + "\""; } } } std::string labels_string; if (callback_data->cmdBufLabelCount > 0) { labels_string = "\n\tCommand Buffer Labels - " + std::to_string(callback_data->cmdBufLabelCount); for (uint32_t cmd_buf_label = 0; cmd_buf_label < callback_data->cmdBufLabelCount; ++cmd_buf_label) { labels_string += "\n\t\tLabel[" + std::to_string(cmd_buf_label) + "]" + " - " + callback_data->pCmdBufLabels[cmd_buf_label].pLabelName + "{ "; for (int color_idx = 0; color_idx < 4; ++color_idx) { labels_string += std::to_string( callback_data->pCmdBufLabels[cmd_buf_label].color[color_idx]); if (color_idx < 3) { labels_string += ", "; } } labels_string += " }"; } } std::string error_message( type_string + " - Message Id Number: " + std::to_string(callback_data->messageIdNumber) + " | Message Id Name: " + callback_data->pMessageIdName + "\n\t" + callback_data->pMessage + objects_string + labels_string); switch (message_severity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: LOG(0) << error_message; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: LOG(0) << error_message; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: LOG(0) << error_message; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: LOG(0) << error_message; break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT: break; } return VK_FALSE; } VkBool32 VulkanContext::CheckLayers(uint32_t check_count, const char** check_names, uint32_t layer_count, VkLayerProperties* layers) { for (uint32_t i = 0; i < check_count; i++) { VkBool32 found = 0; for (uint32_t j = 0; j < layer_count; j++) { if (!strcmp(check_names[i], layers[j].layerName)) { found = 1; break; } } if (!found) { DLOG(0) << "Can't find layer: " << check_names[i]; return 0; } } return 1; } bool VulkanContext::CreateValidationLayers() { VkResult err; const char* instance_validation_layers_alt1[] = { "VK_LAYER_KHRONOS_validation"}; const char* instance_validation_layers_alt2[] = { "VK_LAYER_LUNARG_standard_validation"}; const char* instance_validation_layers_alt3[] = { "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_object_tracker", "VK_LAYER_LUNARG_core_validation", "VK_LAYER_GOOGLE_unique_objects"}; uint32_t instance_layer_count = 0; err = vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr); if (err) { DLOG(0) << "vkEnumerateInstanceLayerProperties failed. Error: " << string_VkResult(err); return false; } VkBool32 validation_found = 0; uint32_t validation_layer_count = 0; const char** instance_validation_layers = nullptr; if (instance_layer_count > 0) { auto instance_layers = std::make_unique(instance_layer_count); err = vkEnumerateInstanceLayerProperties(&instance_layer_count, instance_layers.get()); if (err) { DLOG(0) << "vkEnumerateInstanceLayerProperties failed. Error: " << string_VkResult(err); return false; } validation_layer_count = std::size(instance_validation_layers_alt1); instance_validation_layers = instance_validation_layers_alt1; validation_found = CheckLayers(validation_layer_count, instance_validation_layers, instance_layer_count, instance_layers.get()); // use alternative (deprecated, removed in SDK 1.1.126.0) set of validation // layers. if (!validation_found) { validation_layer_count = std::size(instance_validation_layers_alt2); instance_validation_layers = instance_validation_layers_alt2; validation_found = CheckLayers(validation_layer_count, instance_validation_layers, instance_layer_count, instance_layers.get()); } // use alternative (deprecated, removed in SDK 1.1.121.1) set of validation // layers. if (!validation_found) { validation_layer_count = std::size(instance_validation_layers_alt3); instance_validation_layers = instance_validation_layers_alt3; validation_found = CheckLayers(validation_layer_count, instance_validation_layers, instance_layer_count, instance_layers.get()); } } if (validation_found) { enabled_layer_count_ = validation_layer_count; for (uint32_t i = 0; i < validation_layer_count; i++) { enabled_layers_[i] = instance_validation_layers[i]; } } else { return false; } return true; } bool VulkanContext::InitializeExtensions() { VkResult err; uint32_t instance_extension_count = 0; enabled_extension_count_ = 0; enabled_layer_count_ = 0; VkBool32 surfaceExtFound = 0; VkBool32 platformSurfaceExtFound = 0; memset(extension_names_, 0, sizeof(extension_names_)); err = vkEnumerateInstanceExtensionProperties( nullptr, &instance_extension_count, nullptr); if (err) { DLOG(0) << "vkEnumerateInstanceExtensionProperties failed. Error: " << string_VkResult(err); return false; } if (instance_extension_count > 0) { auto instance_extensions = std::make_unique(instance_extension_count); err = vkEnumerateInstanceExtensionProperties( nullptr, &instance_extension_count, instance_extensions.get()); if (err) { DLOG(0) << "vkEnumerateInstanceExtensionProperties failed. Error: " << string_VkResult(err); return false; } for (uint32_t i = 0; i < instance_extension_count; i++) { if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { surfaceExtFound = 1; extension_names_[enabled_extension_count_++] = VK_KHR_SURFACE_EXTENSION_NAME; } if (!strcmp(GetPlatformSurfaceExtension(), instance_extensions[i].extensionName)) { platformSurfaceExtFound = 1; extension_names_[enabled_extension_count_++] = GetPlatformSurfaceExtension(); } if (!strcmp(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, instance_extensions[i].extensionName)) { if (use_validation_layers_) { extension_names_[enabled_extension_count_++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME; } } if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) { if (use_validation_layers_) { extension_names_[enabled_extension_count_++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; } } if (enabled_extension_count_ >= kMaxExtensions) { DLOG(0) << "Enabled extension count reaches kMaxExtensions"; return false; } } } if (!surfaceExtFound) { DLOG(0) << "No surface extension found."; return false; } if (!platformSurfaceExtFound) { DLOG(0) << "No platform surface extension found."; return false; } return true; } bool VulkanContext::CreatePhysicalDevice() { if (use_validation_layers_) { CreateValidationLayers(); } if (!InitializeExtensions()) return false; const VkApplicationInfo app = { /*sType*/ VK_STRUCTURE_TYPE_APPLICATION_INFO, /*pNext*/ nullptr, /*pApplicationName*/ "kaliber", /*applicationVersion*/ 0, /*pEngineName*/ "kaliber", /*engineVersion*/ 0, /*apiVersion*/ VK_API_VERSION_1_0, }; VkInstanceCreateInfo inst_info = { /*sType*/ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*pApplicationInfo*/ &app, /*enabledLayerCount*/ enabled_layer_count_, /*ppEnabledLayerNames*/ (const char* const*)enabled_layers_, /*enabledExtensionCount*/ enabled_extension_count_, /*ppEnabledExtensionNames*/ (const char* const*)extension_names_, }; // This is info for a temp callback to use during CreateInstance. After the // instance is created, we use the instance-based function to register the // final callback. VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_info; if (use_validation_layers_) { dbg_messenger_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; dbg_messenger_info.pNext = nullptr; dbg_messenger_info.flags = 0; dbg_messenger_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; dbg_messenger_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; dbg_messenger_info.pfnUserCallback = DebugMessengerCallback; dbg_messenger_info.pUserData = this; inst_info.pNext = &dbg_messenger_info; } uint32_t gpu_count; if (instance_ == VK_NULL_HANDLE) { VkResult err = vkCreateInstance(&inst_info, nullptr, &instance_); if (err == VK_ERROR_INCOMPATIBLE_DRIVER) { DLOG(0) << "Cannot find a compatible Vulkan installable client driver (ICD)."; return false; } if (err == VK_ERROR_EXTENSION_NOT_PRESENT) { DLOG(0) << "Cannot find a specified extension library. Make sure your layers " "path is set appropriately. "; return false; } if (err) { DLOG(0) << "vkCreateInstance failed. Error: " << string_VkResult(err); return false; } } volkLoadInstance(instance_); // Make initial call to query gpu_count. VkResult err = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr); if (err) { DLOG(0) << "vkEnumeratePhysicalDevices failed. Error: " << string_VkResult(err); return false; } if (gpu_count == 0) { DLOG(0) << "vkEnumeratePhysicalDevices reported zero accessible devices."; return false; } auto physical_devices = std::make_unique(gpu_count); err = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices.get()); if (err) { DLOG(0) << "vkEnumeratePhysicalDevices failed. Error: " << string_VkResult(err); return false; } // Grab the first physical device for now. gpu_ = physical_devices[0]; // Look for device extensions. uint32_t device_extension_count = 0; VkBool32 swapchain_ext_found = 0; enabled_extension_count_ = 0; memset(extension_names_, 0, sizeof(extension_names_)); err = vkEnumerateDeviceExtensionProperties(gpu_, nullptr, &device_extension_count, nullptr); if (err) { DLOG(0) << "vkEnumerateDeviceExtensionProperties failed. Error: " << string_VkResult(err); return false; } if (device_extension_count > 0) { auto device_extensions = std::make_unique(device_extension_count); err = vkEnumerateDeviceExtensionProperties( gpu_, nullptr, &device_extension_count, device_extensions.get()); if (err) { DLOG(0) << "vkEnumerateDeviceExtensionProperties failed. Error: " << string_VkResult(err); return false; } for (uint32_t i = 0; i < device_extension_count; i++) { if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, device_extensions[i].extensionName)) { swapchain_ext_found = 1; extension_names_[enabled_extension_count_++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; } if (enabled_extension_count_ >= kMaxExtensions) { DLOG(0) << "Enabled extension count reaches kMaxExtensions"; return false; } } // Enable VK_KHR_maintenance1 extension for old vulkan drivers. for (uint32_t i = 0; i < device_extension_count; i++) { if (!strcmp(VK_KHR_MAINTENANCE1_EXTENSION_NAME, device_extensions[i].extensionName)) { extension_names_[enabled_extension_count_++] = VK_KHR_MAINTENANCE1_EXTENSION_NAME; } if (enabled_extension_count_ >= kMaxExtensions) { DLOG(0) << "Enabled extension count reaches kMaxExtensions"; return false; } } } if (!swapchain_ext_found) { DLOG(0) << "vkEnumerateDeviceExtensionProperties failed to find " "the " VK_KHR_SWAPCHAIN_EXTENSION_NAME " extension."; return false; } if (use_validation_layers_) { CreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance_, "vkCreateDebugUtilsMessengerEXT"); DestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance_, "vkDestroyDebugUtilsMessengerEXT"); if (nullptr == CreateDebugUtilsMessengerEXT || nullptr == DestroyDebugUtilsMessengerEXT) { DLOG(0) << "GetProcAddr: Failed to init VK_EXT_debug_utils"; return false; } err = CreateDebugUtilsMessengerEXT(instance_, &dbg_messenger_info, nullptr, &dbg_messenger_); switch (err) { case VK_SUCCESS: break; case VK_ERROR_OUT_OF_HOST_MEMORY: DLOG(0) << "CreateDebugUtilsMessengerEXT: out of host memory"; return false; default: DLOG(0) << "CreateDebugUtilsMessengerEXT: unknown failure"; return false; break; } } vkGetPhysicalDeviceProperties(gpu_, &gpu_props_); LOG(0) << "Vulkan:"; LOG(0) << " Name: " << gpu_props_.deviceName; LOG(0) << " Tame: " << string_VkPhysicalDeviceType(gpu_props_.deviceType); LOG(0) << " Vendor ID: " << gpu_props_.vendorID; LOG(0) << " API version: " << VK_VERSION_MAJOR(gpu_props_.apiVersion) << "." << VK_VERSION_MINOR(gpu_props_.apiVersion) << "." << VK_VERSION_PATCH(gpu_props_.apiVersion); LOG(0) << " Driver version: " << VK_VERSION_MAJOR(gpu_props_.driverVersion) << "." << VK_VERSION_MINOR(gpu_props_.driverVersion) << "." << VK_VERSION_PATCH(gpu_props_.driverVersion); // Call with NULL data to get count, vkGetPhysicalDeviceQueueFamilyProperties(gpu_, &queue_family_count_, nullptr); if (queue_family_count_ == 0) { DLOG(0) << "Failed to query queue family count."; return false; } queue_props_ = std::make_unique(queue_family_count_); vkGetPhysicalDeviceQueueFamilyProperties(gpu_, &queue_family_count_, queue_props_.get()); // Query fine-grained feature support for this device. // If app has specific feature requirements it should check supported features // based on this query. vkGetPhysicalDeviceFeatures(gpu_, &physical_device_features_); GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetPhysicalDeviceSurfaceSupportKHR); GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetPhysicalDeviceSurfaceCapabilitiesKHR); GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetPhysicalDeviceSurfaceFormatsKHR); GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetPhysicalDeviceSurfacePresentModesKHR); GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetSwapchainImagesKHR); return true; } bool VulkanContext::CreateDevice() { VkResult err; float queue_priorities[1] = {0.0}; VkDeviceQueueCreateInfo queues[2]; queues[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queues[0].pNext = nullptr; queues[0].queueFamilyIndex = graphics_queue_family_index_; queues[0].queueCount = 1; queues[0].pQueuePriorities = queue_priorities; queues[0].flags = 0; VkDeviceCreateInfo sdevice = { /*sType*/ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*queueCreateInfoCount*/ 1, /*pQueueCreateInfos*/ queues, /*enabledLayerCount*/ 0, /*ppEnabledLayerNames*/ nullptr, /*enabledExtensionCount*/ enabled_extension_count_, /*ppEnabledExtensionNames*/ (const char* const*)extension_names_, /*pEnabledFeatures*/ &physical_device_features_, // If specific features // are required, pass // them in here }; if (separate_present_queue_) { queues[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queues[1].pNext = nullptr; queues[1].queueFamilyIndex = present_queue_family_index_; queues[1].queueCount = 1; queues[1].pQueuePriorities = queue_priorities; queues[1].flags = 0; sdevice.queueCreateInfoCount = 2; } err = vkCreateDevice(gpu_, &sdevice, nullptr, &device_); if (err) { DLOG(0) << "vkCreateDevice failed. Error: " << string_VkResult(err); return false; } return true; } bool VulkanContext::InitializeQueues(VkSurfaceKHR surface) { // Iterate over each queue to learn whether it supports presenting: auto supports_present = std::make_unique(queue_family_count_); for (uint32_t i = 0; i < queue_family_count_; i++) { GetPhysicalDeviceSurfaceSupportKHR(gpu_, i, surface, &supports_present[i]); } // Search for a graphics and a present queue in the array of queue families, // try to find one that supports both. uint32_t graphics_queue_family_index = std::numeric_limits::max(); uint32_t present_queue_family_index = std::numeric_limits::max(); for (uint32_t i = 0; i < queue_family_count_; i++) { if ((queue_props_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { if (graphics_queue_family_index == std::numeric_limits::max()) { graphics_queue_family_index = i; } if (supports_present[i] == VK_TRUE) { graphics_queue_family_index = i; present_queue_family_index = i; break; } } } if (present_queue_family_index == std::numeric_limits::max()) { // If didn't find a queue that supports both graphics and present, then find // a separate present queue. for (uint32_t i = 0; i < queue_family_count_; ++i) { if (supports_present[i] == VK_TRUE) { present_queue_family_index = i; break; } } } // Generate error if could not find both a graphics and a present queue if (graphics_queue_family_index == std::numeric_limits::max() || present_queue_family_index == std::numeric_limits::max()) { DLOG(0) << "Could not find both graphics and present queues."; return false; } graphics_queue_family_index_ = graphics_queue_family_index; present_queue_family_index_ = present_queue_family_index; separate_present_queue_ = (graphics_queue_family_index_ != present_queue_family_index_); CreateDevice(); PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr; GET_PROC_ADDR(vkGetInstanceProcAddr, instance_, GetDeviceProcAddr); GET_PROC_ADDR(GetDeviceProcAddr, device_, CreateSwapchainKHR); GET_PROC_ADDR(GetDeviceProcAddr, device_, DestroySwapchainKHR); GET_PROC_ADDR(GetDeviceProcAddr, device_, GetSwapchainImagesKHR); GET_PROC_ADDR(GetDeviceProcAddr, device_, AcquireNextImageKHR); GET_PROC_ADDR(GetDeviceProcAddr, device_, QueuePresentKHR); vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &graphics_queue_); if (!separate_present_queue_) { present_queue_ = graphics_queue_; } else { vkGetDeviceQueue(device_, present_queue_family_index_, 0, &present_queue_); } // Get the list of VkFormat's that are supported. uint32_t format_count; VkResult err = GetPhysicalDeviceSurfaceFormatsKHR(gpu_, surface, &format_count, nullptr); if (err) { DLOG(0) << "GetPhysicalDeviceSurfaceFormatsKHR failed. Error: " << string_VkResult(err); return false; } auto surf_formats = std::make_unique(format_count); err = GetPhysicalDeviceSurfaceFormatsKHR(gpu_, surface, &format_count, surf_formats.get()); if (err) { DLOG(0) << "GetPhysicalDeviceSurfaceFormatsKHR failed. Error: " << string_VkResult(err); return false; } #if defined(__ANDROID__) VkFormat desired_format = VK_FORMAT_R8G8B8A8_UNORM; #elif defined(__linux__) VkFormat desired_format = VK_FORMAT_B8G8R8A8_UNORM; #endif // If the format list includes just one entry of VK_FORMAT_UNDEFINED, the // surface has no preferred format. Otherwise, at least one supported format // will be returned. if (format_count == 1 && surf_formats[0].format == VK_FORMAT_UNDEFINED) { format_ = desired_format; color_space_ = surf_formats[0].colorSpace; } else if (format_count < 1) { DLOG(0) << "Format count less than 1."; return false; } else { // Find the first format that we support. format_ = VK_FORMAT_UNDEFINED; const VkFormat allowed_formats[] = {VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM}; for (uint32_t afi = 0; afi < std::size(allowed_formats); afi++) { for (uint32_t sfi = 0; sfi < format_count; sfi++) { if (surf_formats[sfi].format == allowed_formats[afi]) { format_ = surf_formats[sfi].format; color_space_ = surf_formats[sfi].colorSpace; goto end_of_find_format; } } } end_of_find_format: if (format_ == VK_FORMAT_UNDEFINED) { DLOG(0) << "No usable surface format found."; return false; } } if (!CreateSemaphores()) return false; queues_initialized_ = true; return true; } bool VulkanContext::CreateSemaphores() { VkResult err; // Create semaphores to synchronize acquiring presentable buffers before // rendering and waiting for drawing to be complete before presenting. VkSemaphoreCreateInfo semaphoreCreateInfo = { /*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, }; // Create fences that we can use to throttle if we get too far ahead of the // image presents. VkFenceCreateInfo fence_ci = {/*sType*/ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ VK_FENCE_CREATE_SIGNALED_BIT}; for (uint32_t i = 0; i < kFrameLag; i++) { err = vkCreateFence(device_, &fence_ci, nullptr, &fences_[i]); if (err) { DLOG(0) << "vkCreateFence failed. Error: " << string_VkResult(err); return false; } err = vkCreateSemaphore(device_, &semaphoreCreateInfo, nullptr, &image_acquired_semaphores_[i]); if (err) { DLOG(0) << "vkCreateSemaphore failed. Error: " << string_VkResult(err); return false; } err = vkCreateSemaphore(device_, &semaphoreCreateInfo, nullptr, &draw_complete_semaphores_[i]); if (err) { DLOG(0) << "vkCreateSemaphore failed. Error: " << string_VkResult(err); return false; } if (separate_present_queue_) { err = vkCreateSemaphore(device_, &semaphoreCreateInfo, nullptr, &image_ownership_semaphores_[i]); if (err) { DLOG(0) << "vkCreateSemaphore failed. Error: " << string_VkResult(err); return false; } } } frame_index_ = 0; // Get Memory information and properties. vkGetPhysicalDeviceMemoryProperties(gpu_, &memory_properties_); return true; } void VulkanContext::ResizeWindow(int width, int height) { window_.width = width; window_.height = height; UpdateSwapChain(&window_); } void VulkanContext::DestroyWindow() { CleanUpSwapChain(&window_); vkDestroySurfaceKHR(instance_, window_.surface, nullptr); } VkFramebuffer VulkanContext::GetFramebuffer() { return window_.swapchain_image_resources[window_.current_buffer].frame_buffer; } bool VulkanContext::CleanUpSwapChain(Window* window) { if (!window->swapchain) return true; vkDeviceWaitIdle(device_); vkDestroyImageView(device_, window->depth_view, nullptr); vkDestroyImage(device_, window->depth_image, nullptr); vkFreeMemory(device_, window->depth_image_memory, nullptr); window->depth_view = VK_NULL_HANDLE; window->depth_image = VK_NULL_HANDLE; window->depth_image_memory = VK_NULL_HANDLE; DestroySwapchainKHR(device_, window->swapchain, nullptr); window->swapchain = VK_NULL_HANDLE; vkDestroyRenderPass(device_, window->render_pass, nullptr); if (window->swapchain_image_resources) { for (uint32_t i = 0; i < swapchain_image_count_; i++) { vkDestroyImageView(device_, window->swapchain_image_resources[i].view, nullptr); vkDestroyFramebuffer( device_, window->swapchain_image_resources[i].frame_buffer, nullptr); } window->swapchain_image_resources.reset(); } if (separate_present_queue_) vkDestroyCommandPool(device_, window->present_cmd_pool, nullptr); return true; } bool VulkanContext::UpdateSwapChain(Window* window) { VkResult err; if (window->swapchain) CleanUpSwapChain(window); // Check the surface capabilities and formats. VkSurfaceCapabilitiesKHR surf_capabilities; err = GetPhysicalDeviceSurfaceCapabilitiesKHR(gpu_, window->surface, &surf_capabilities); if (err) { DLOG(0) << "GetPhysicalDeviceSurfaceCapabilitiesKHR failed. Error: " << string_VkResult(err); return false; } uint32_t present_mode_count; err = GetPhysicalDeviceSurfacePresentModesKHR(gpu_, window->surface, &present_mode_count, nullptr); if (err) { DLOG(0) << "GetPhysicalDeviceSurfacePresentModesKHR failed. Error: " << string_VkResult(err); return false; } auto present_modes = std::make_unique(present_mode_count); err = GetPhysicalDeviceSurfacePresentModesKHR( gpu_, window->surface, &present_mode_count, present_modes.get()); if (err) { DLOG(0) << "GetPhysicalDeviceSurfacePresentModesKHR failed. Error: " << string_VkResult(err); return false; } // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. if (surf_capabilities.currentExtent.width == 0xFFFFFFFF) { // If the surface size is undefined, the size is set to the size of the // images requested, which must fit within the minimum and maximum values. window->swapchain_extent.width = window->width; window->swapchain_extent.height = window->height; if (window->swapchain_extent.width < surf_capabilities.minImageExtent.width) { window->swapchain_extent.width = surf_capabilities.minImageExtent.width; } else if (window->swapchain_extent.width > surf_capabilities.maxImageExtent.width) { window->swapchain_extent.width = surf_capabilities.maxImageExtent.width; } if (window->swapchain_extent.height < surf_capabilities.minImageExtent.height) { window->swapchain_extent.height = surf_capabilities.minImageExtent.height; } else if (window->swapchain_extent.height > surf_capabilities.maxImageExtent.height) { window->swapchain_extent.height = surf_capabilities.maxImageExtent.height; } } else { // If the surface size is defined, the swap chain size must match window->swapchain_extent = surf_capabilities.currentExtent; window->width = surf_capabilities.currentExtent.width; window->height = surf_capabilities.currentExtent.height; } if (window->width == 0 || window->height == 0) { // likely window minimized, no swapchain created return true; } // The application will render an image, then pass it to the presentation // engine via vkQueuePresentKHR. The presentation engine will display the // image for the next VSync cycle, and then it will make it available to the // application again. The only present modes which support VSync are: // // VK_PRESENT_MODE_FIFO_KHR: At each VSync signal, the image in front of the // queue displays on screen and is then released. The application will acquire // one of the available ones, draw to it and then hand it over to the // presentation engine, which will push it to the back of the queue. If // rendering is fast the queue can become full. The CPU and the GPU will idle // until an image is available again. This behavior works well on mobile // because it limits overheating and saves battery life. // // VK_PRESENT_MODE_MAILBOX_KHR: The application can acquire a new image // straight away, render to it, and hand it over to the presentation engine. // If an image is queued for presentation, it will be discarded. Being able to // keep submitting new frames lets the application ensure it has the latest // user input, so input latency can be lower versus FIFO. If the application // doesn't throttle CPU and GPU, one of them may be fully utilized, resulting // in higher power consumption. VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; VkPresentModeKHR fallback_present_mode = VK_PRESENT_MODE_FIFO_KHR; if (swapchain_present_mode != fallback_present_mode) { for (size_t i = 0; i < present_mode_count; ++i) { if (present_modes[i] == swapchain_present_mode) { // Supported. fallback_present_mode = swapchain_present_mode; break; } } } if (swapchain_present_mode != fallback_present_mode) { LOG(0) << "Present mode " << swapchain_present_mode << " is not supported"; swapchain_present_mode = fallback_present_mode; } // 2 for double buffering, 3 for triple buffering. // Double buffering works well if frames can be processed within the interval // between VSync signals, which is 16.6ms at a rate of 60 fps. The rendered // image is presented to the swapchain, and the previously presented one is // made available to the application again. If the GPU cannot process frames // fast enough, VSync will be missed and the application will have to wait for // another whole VSync cycle, which caps framerate at 30 fps. This may be ok, // but triple buffering can deliver higher framerate. uint32_t desired_num_of_swapchain_images = 3; if (desired_num_of_swapchain_images < surf_capabilities.minImageCount) { desired_num_of_swapchain_images = surf_capabilities.minImageCount; } // If maxImageCount is 0, we can ask for as many images as we want; otherwise // we're limited to maxImageCount. if ((surf_capabilities.maxImageCount > 0) && (desired_num_of_swapchain_images > surf_capabilities.maxImageCount)) { // Application must settle for fewer images than desired. desired_num_of_swapchain_images = surf_capabilities.maxImageCount; } VkSurfaceTransformFlagsKHR pre_transform; if (surf_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } else { pre_transform = surf_capabilities.currentTransform; } // Find a supported composite alpha mode. One of these is guaranteed to be // set. VkCompositeAlphaFlagBitsKHR composite_alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; VkCompositeAlphaFlagBitsKHR composite_alpha_flags[4] = { VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, }; for (uint32_t i = 0; i < std::size(composite_alpha_flags); i++) { if (surf_capabilities.supportedCompositeAlpha & composite_alpha_flags[i]) { composite_alpha = composite_alpha_flags[i]; break; } } VkSwapchainCreateInfoKHR swapchain_ci = { /*sType*/ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, /*pNext*/ nullptr, /*flags*/ 0, /*surface*/ window->surface, /*minImageCount*/ desired_num_of_swapchain_images, /*imageFormat*/ format_, /*imageColorSpace*/ color_space_, /*imageExtent*/ { /*width*/ window->swapchain_extent.width, /*height*/ window->swapchain_extent.height, }, /*imageArrayLayers*/ 1, /*imageUsage*/ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, /*imageSharingMode*/ VK_SHARING_MODE_EXCLUSIVE, /*queueFamilyIndexCount*/ 0, /*pQueueFamilyIndices*/ nullptr, /*preTransform*/ (VkSurfaceTransformFlagBitsKHR)pre_transform, /*compositeAlpha*/ composite_alpha, /*presentMode*/ swapchain_present_mode, /*clipped*/ true, /*oldSwapchain*/ VK_NULL_HANDLE, }; err = CreateSwapchainKHR(device_, &swapchain_ci, nullptr, &window->swapchain); if (err) { DLOG(0) << "CreateSwapchainKHR failed. Error: " << string_VkResult(err); return false; } uint32_t sp_image_count; err = GetSwapchainImagesKHR(device_, window->swapchain, &sp_image_count, nullptr); if (err) { DLOG(0) << "GetSwapchainImagesKHR failed. Error: " << string_VkResult(err); return false; } if (swapchain_image_count_ == 0) { // Assign for the first time. swapchain_image_count_ = sp_image_count; } else if (swapchain_image_count_ != sp_image_count) { DLOG(0) << "Swapchain image count mismatch"; return false; } auto swapchain_images = std::make_unique(swapchain_image_count_); err = GetSwapchainImagesKHR(device_, window->swapchain, &swapchain_image_count_, swapchain_images.get()); if (err) { DLOG(0) << "GetSwapchainImagesKHR failed. Error: " << string_VkResult(err); return false; } window->swapchain_image_resources = std::make_unique(swapchain_image_count_); for (uint32_t i = 0; i < swapchain_image_count_; i++) { VkImageViewCreateInfo color_image_view = { /*sType*/ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*image*/ swapchain_images[i], /*viewType*/ VK_IMAGE_VIEW_TYPE_2D, /*format*/ format_, /*components*/ { /*r*/ VK_COMPONENT_SWIZZLE_R, /*g*/ VK_COMPONENT_SWIZZLE_G, /*b*/ VK_COMPONENT_SWIZZLE_B, /*a*/ VK_COMPONENT_SWIZZLE_A, }, /*subresourceRange*/ {/*aspectMask*/ VK_IMAGE_ASPECT_COLOR_BIT, /*baseMipLevel*/ 0, /*levelCount*/ 1, /*baseArrayLayer*/ 0, /*layerCount*/ 1}, }; window->swapchain_image_resources[i].image = swapchain_images[i]; color_image_view.image = window->swapchain_image_resources[i].image; err = vkCreateImageView(device_, &color_image_view, nullptr, &window->swapchain_image_resources[i].view); if (err) { DLOG(0) << "vkCreateImageView failed. Error: " << string_VkResult(err); return false; } } // Framebuffer { const VkAttachmentDescription color_attachment = { /*flags*/ 0, /*format*/ format_, /*samples*/ VK_SAMPLE_COUNT_1_BIT, /*loadOp*/ VK_ATTACHMENT_LOAD_OP_CLEAR, /*storeOp*/ VK_ATTACHMENT_STORE_OP_STORE, /*stencilLoadOp*/ VK_ATTACHMENT_LOAD_OP_DONT_CARE, /*stencilStoreOp*/ VK_ATTACHMENT_STORE_OP_DONT_CARE, /*initialLayout*/ VK_IMAGE_LAYOUT_UNDEFINED, /*finalLayout*/ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }; const VkAttachmentReference color_reference = { /*attachment*/ 0, /*layout*/ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; const VkAttachmentDescription depth_attachment = { /*flags*/ 0, /*format*/ VK_FORMAT_D24_UNORM_S8_UINT, /*samples*/ VK_SAMPLE_COUNT_1_BIT, /*loadOp*/ VK_ATTACHMENT_LOAD_OP_CLEAR, /*storeOp*/ VK_ATTACHMENT_STORE_OP_DONT_CARE, /*stencilLoadOp*/ VK_ATTACHMENT_LOAD_OP_DONT_CARE, /*stencilStoreOp*/ VK_ATTACHMENT_STORE_OP_DONT_CARE, /*initialLayout*/ VK_IMAGE_LAYOUT_UNDEFINED, /*finalLayout*/ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; const VkAttachmentReference depth_reference = { /*attachment*/ 1, /*layout*/ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; const VkSubpassDescription subpass = { /*flags*/ 0, /*pipelineBindPoint*/ VK_PIPELINE_BIND_POINT_GRAPHICS, /*inputAttachmentCount*/ 0, /*pInputAttachments*/ nullptr, /*colorAttachmentCount*/ 1, /*pColorAttachments*/ &color_reference, /*pResolveAttachments*/ nullptr, /*pDepthStencilAttachment*/ &depth_reference, /*preserveAttachmentCount*/ 0, /*pPreserveAttachments*/ nullptr, }; VkSubpassDependency dependency = { /*srcSubpass*/ VK_SUBPASS_EXTERNAL, /*dstSubpass*/ 0, /*srcStageMask*/ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, /*dstStageMask*/ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, /*srcAccessMask*/ 0, /*dstAccessMask*/ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, /*dependencyFlags*/ 0, }; std::array attachments = {color_attachment, depth_attachment}; const VkRenderPassCreateInfo rp_info = { /*sTyp*/ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*attachmentCount*/ attachments.size(), /*pAttachments*/ attachments.data(), /*subpassCount*/ 1, /*pSubpasses*/ &subpass, /*dependencyCount*/ 1, /*pDependencies*/ &dependency, }; err = vkCreateRenderPass(device_, &rp_info, nullptr, &window->render_pass); if (err) { DLOG(0) << "vkCreateRenderPass failed. Error: " << string_VkResult(err); return false; } CreateDepthImage(window); for (uint32_t i = 0; i < swapchain_image_count_; i++) { std::array attachments = { window->swapchain_image_resources[i].view, window->depth_view}; const VkFramebufferCreateInfo fb_info = { /*sType*/ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*renderPass*/ window->render_pass, /*attachmentCount*/ attachments.size(), /*pAttachments*/ attachments.data(), /*width*/ (uint32_t)window->width, /*height*/ (uint32_t)window->height, /*layers*/ 1, }; err = vkCreateFramebuffer( device_, &fb_info, nullptr, &window->swapchain_image_resources[i].frame_buffer); if (err) { DLOG(0) << "vkCreateFramebuffer failed. Error: " << string_VkResult(err); return false; } } } // Separate present queue if (separate_present_queue_) { const VkCommandPoolCreateInfo present_cmd_pool_info = { /*sType*/ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*queueFamilyIndex*/ present_queue_family_index_, }; err = vkCreateCommandPool(device_, &present_cmd_pool_info, nullptr, &window->present_cmd_pool); if (err) { DLOG(0) << "vkCreateCommandPool failed. Error: " << string_VkResult(err); return false; } const VkCommandBufferAllocateInfo present_cmd_info = { /*sType*/ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, /*pNext*/ nullptr, /*commandPool*/ window->present_cmd_pool, /*level*/ VK_COMMAND_BUFFER_LEVEL_PRIMARY, /*commandBufferCount*/ 1, }; for (uint32_t i = 0; i < swapchain_image_count_; i++) { err = vkAllocateCommandBuffers( device_, &present_cmd_info, &window->swapchain_image_resources[i].graphics_to_present_cmd); if (err) { DLOG(0) << "vkAllocateCommandBuffers failed. Error: " << string_VkResult(err); return false; } const VkCommandBufferBeginInfo cmd_buf_info = { /*sType*/ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, /*pNext*/ nullptr, /*flags*/ VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, /*pInheritanceInfo*/ nullptr, }; err = vkBeginCommandBuffer( window->swapchain_image_resources[i].graphics_to_present_cmd, &cmd_buf_info); if (err) { DLOG(0) << "vkBeginCommandBuffer failed. Error: " << string_VkResult(err); return false; } VkImageMemoryBarrier image_ownership_barrier = { /*sType*/ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, /*pNext*/ nullptr, /*srcAccessMask*/ 0, /*dstAccessMask*/ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, /*oldLayout*/ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, /*newLayout*/ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, /*srcQueueFamilyIndex*/ graphics_queue_family_index_, /*dstQueueFamilyIndex*/ present_queue_family_index_, /*image*/ window->swapchain_image_resources[i].image, /*subresourceRange*/ {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; vkCmdPipelineBarrier( window->swapchain_image_resources[i].graphics_to_present_cmd, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_ownership_barrier); err = vkEndCommandBuffer( window->swapchain_image_resources[i].graphics_to_present_cmd); if (err) { DLOG(0) << "vkEndCommandBuffer failed. Error: " << string_VkResult(err); return false; } } } // Reset current buffer. window->current_buffer = 0; return true; } bool VulkanContext::CreateDepthImage(Window* window) { VkImageCreateInfo depth_image_ci = { /*sType*/ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*imageType*/ VK_IMAGE_TYPE_2D, /*format*/ VK_FORMAT_D24_UNORM_S8_UINT, /*extent*/ { /*width*/ window->swapchain_extent.width, /*height*/ window->swapchain_extent.height, /*depth*/ 1, }, /*mipLevels*/ 1, /*arrayLayers*/ 1, /*samples*/ VK_SAMPLE_COUNT_1_BIT, /*tiling*/ VK_IMAGE_TILING_OPTIMAL, /*usage*/ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, /*sharingMode*/ VK_SHARING_MODE_EXCLUSIVE, /*queueFamilyIndexCount*/ 0, /*pQueueFamilyIndices*/ nullptr, /*initialLayout*/ VK_IMAGE_LAYOUT_UNDEFINED, }; VkResult err = vkCreateImage(device_, &depth_image_ci, nullptr, &window->depth_image); if (err) { DLOG(0) << "vkCreateImage failed. Error: " << string_VkResult(err); return false; } VkMemoryRequirements mem_requirements; vkGetImageMemoryRequirements(device_, window->depth_image, &mem_requirements); VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(gpu_, &memProperties); uint32_t mti = 0; for (; mti < memProperties.memoryTypeCount; mti++) { if ((mem_requirements.memoryTypeBits & (1 << mti)) && (memProperties.memoryTypes[mti].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { break; } } if (mti == memProperties.memoryTypeCount) { DLOG(0) << "Memort type index not found."; return false; } VkMemoryAllocateInfo alloc_info = { /*sType*/ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, /*pNext*/ nullptr, /*allocationSize*/ mem_requirements.size, /*memoryTypeIndex*/ mti, }; err = vkAllocateMemory(device_, &alloc_info, nullptr, &window->depth_image_memory); if (err) { DLOG(0) << "vkAllocateMemory failed. Error: " << string_VkResult(err); return false; } vkBindImageMemory(device_, window->depth_image, window->depth_image_memory, 0); VkImageViewCreateInfo image_view_create_info = { /*sType*/ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, /*pNext*/ nullptr, /*flags*/ 0, /*image*/ window->depth_image, /*viewType*/ VK_IMAGE_VIEW_TYPE_2D, /*format*/ VK_FORMAT_D24_UNORM_S8_UINT, /*components*/ { /*r*/ VK_COMPONENT_SWIZZLE_R, /*g*/ VK_COMPONENT_SWIZZLE_G, /*b*/ VK_COMPONENT_SWIZZLE_B, /*a*/ VK_COMPONENT_SWIZZLE_A, }, /*subresourceRange*/ { /*aspectMask*/ VK_IMAGE_ASPECT_DEPTH_BIT, /*baseMipLevel*/ 0, /*levelCount*/ 1, /*baseArrayLayer*/ 0, /*layerCount*/ 1, }, }; err = vkCreateImageView(device_, &image_view_create_info, nullptr, &window->depth_view); if (err) { vkDestroyImage(device_, window->depth_image, nullptr); vkFreeMemory(device_, window->depth_image_memory, nullptr); DLOG(0) << "vkCreateImageView failed with error " << std::to_string(err); return false; } return true; } void VulkanContext::AppendCommandBuffer(const VkCommandBuffer& command_buffer, bool front) { if (front) command_buffers_.insert(command_buffers_.begin(), command_buffer); else command_buffers_.push_back(command_buffer); } void VulkanContext::Flush(bool all) { // Ensure everything else pending is executed. vkDeviceWaitIdle(device_); // Flush the current frame. VkSubmitInfo submit_info; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = nullptr; submit_info.pWaitDstStageMask = nullptr; submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = nullptr; submit_info.commandBufferCount = command_buffers_.size() - (all ? 0 : 1); submit_info.pCommandBuffers = command_buffers_.data(); submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = nullptr; VkResult err = vkQueueSubmit(graphics_queue_, 1, &submit_info, VK_NULL_HANDLE); command_buffers_[0] = nullptr; if (err) { DLOG(0) << "vkQueueSubmit failed. Error: " << string_VkResult(err); return; } auto draw_command_buffer = command_buffers_.back(); command_buffers_.clear(); if (!all) command_buffers_.push_back(draw_command_buffer); vkDeviceWaitIdle(device_); } bool VulkanContext::PrepareBuffers() { if (!queues_initialized_) return true; VkResult err; // Ensure no more than kFrameLag renderings are outstanding. vkWaitForFences(device_, 1, &fences_[frame_index_], VK_TRUE, std::numeric_limits::max()); vkResetFences(device_, 1, &fences_[frame_index_]); DCHECK(window_.swapchain != VK_NULL_HANDLE); do { // Get the index of the next available swapchain image: err = AcquireNextImageKHR(device_, window_.swapchain, std::numeric_limits::max(), image_acquired_semaphores_[frame_index_], VK_NULL_HANDLE, &window_.current_buffer); if (err == VK_ERROR_OUT_OF_DATE_KHR) { // swapchain is out of date (e.g. the window was resized) and must be // recreated: DLOG(0) << "Swapchain is out of date, recreating."; UpdateSwapChain(&window_); } else if (err == VK_SUBOPTIMAL_KHR) { // swapchain is not as optimal as it could be, but the platform's // presentation engine will still present the image correctly. DLOG(0) << "Swapchain is suboptimal, recreating."; UpdateSwapChain(&window_); break; } else if (err != VK_SUCCESS) { DLOG(0) << "AcquireNextImageKHR failed. Error: " << string_VkResult(err); return false; } } while (err != VK_SUCCESS); return true; } size_t VulkanContext::GetAndResetFPS() { int ret = fps_; fps_ = 0; return ret; } bool VulkanContext::SwapBuffers() { if (!queues_initialized_) return true; VkResult err; // Wait for the image acquired semaphore to be signaled to ensure that the // image won't be rendered to until the presentation engine has fully released // ownership to the application, and it is okay to render to the image. VkPipelineStageFlags pipe_stage_flags; VkSubmitInfo submit_info; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = nullptr; submit_info.pWaitDstStageMask = &pipe_stage_flags; pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &image_acquired_semaphores_[frame_index_]; submit_info.commandBufferCount = command_buffers_.size(); submit_info.pCommandBuffers = command_buffers_.data(); submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &draw_complete_semaphores_[frame_index_]; err = vkQueueSubmit(graphics_queue_, 1, &submit_info, fences_[frame_index_]); if (err) { DLOG(0) << "vkQueueSubmit failed. Error: " << string_VkResult(err); return false; } command_buffers_.clear(); if (separate_present_queue_) { // If we are using separate queues, change image ownership to the present // queue before presenting, waiting for the draw complete semaphore and // signalling the ownership released semaphore when finished. VkFence null_fence = VK_NULL_HANDLE; pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &draw_complete_semaphores_[frame_index_]; submit_info.commandBufferCount = 0; VkCommandBuffer* cmdbufptr = (VkCommandBuffer*)alloca(sizeof(VkCommandBuffer*)); submit_info.pCommandBuffers = cmdbufptr; DCHECK(window_.swapchain != VK_NULL_HANDLE); cmdbufptr[submit_info.commandBufferCount] = window_.swapchain_image_resources[window_.current_buffer] .graphics_to_present_cmd; submit_info.commandBufferCount++; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &image_ownership_semaphores_[frame_index_]; err = vkQueueSubmit(present_queue_, 1, &submit_info, null_fence); if (err) { DLOG(0) << "vkQueueSubmit failed. Error: " << string_VkResult(err); return false; } } // If we are using separate queues we have to wait for image ownership, // otherwise wait for draw complete. VkPresentInfoKHR present = { /*sType*/ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, /*pNext*/ nullptr, /*waitSemaphoreCount*/ 1, /*pWaitSemaphores*/ (separate_present_queue_) ? &image_ownership_semaphores_[frame_index_] : &draw_complete_semaphores_[frame_index_], /*swapchainCount*/ 0, /*pSwapchain*/ nullptr, /*pImageIndices*/ nullptr, /*pResults*/ nullptr, }; VkSwapchainKHR* swapchains = (VkSwapchainKHR*)alloca(sizeof(VkSwapchainKHR*)); uint32_t* pImageIndices = (uint32_t*)alloca(sizeof(uint32_t*)); present.pSwapchains = swapchains; present.pImageIndices = pImageIndices; DCHECK(window_.swapchain != VK_NULL_HANDLE); swapchains[present.swapchainCount] = window_.swapchain; pImageIndices[present.swapchainCount] = window_.current_buffer; present.swapchainCount++; err = QueuePresentKHR(present_queue_, &present); frame_index_ += 1; frame_index_ %= kFrameLag; fps_++; if (err == VK_ERROR_OUT_OF_DATE_KHR) { // Swapchain is out of date (e.g. the window was resized) and must be // recreated. DLOG(0) << "Swapchain is out of date."; } else if (err == VK_SUBOPTIMAL_KHR) { // Swapchain is not as optimal as it could be, but the platform's // presentation engine will still present the image correctly. DLOG(0) << "Swapchain is Suboptimal."; } else if (err) { DLOG(0) << "QueuePresentKHR failed. Error: " << string_VkResult(err); return false; } return true; } } // namespace eng