Index: ps/trunk/source/renderer/backend/vulkan/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27578) +++ ps/trunk/source/renderer/backend/vulkan/Device.cpp (revision 27579) @@ -1,1040 +1,1040 @@ /* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Device.h" #include "lib/external_libraries/libsdl.h" #include "lib/hash.h" #include "lib/sysdep/os.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Profile.h" #include "renderer/backend/vulkan/Buffer.h" #include "renderer/backend/vulkan/DescriptorManager.h" #include "renderer/backend/vulkan/DeviceCommandContext.h" #include "renderer/backend/vulkan/DeviceSelection.h" #include "renderer/backend/vulkan/Framebuffer.h" #include "renderer/backend/vulkan/Mapping.h" #include "renderer/backend/vulkan/PipelineState.h" #include "renderer/backend/vulkan/RenderPassManager.h" #include "renderer/backend/vulkan/RingCommandContext.h" #include "renderer/backend/vulkan/SamplerManager.h" #include "renderer/backend/vulkan/ShaderProgram.h" #include "renderer/backend/vulkan/SubmitScheduler.h" #include "renderer/backend/vulkan/SwapChain.h" #include "renderer/backend/vulkan/Texture.h" #include "renderer/backend/vulkan/Utilities.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #include #include #include #include #include #include #include // According to https://wiki.libsdl.org/SDL_Vulkan_LoadLibrary the following // functionality is supported since SDL 2.0.6. #if SDL_VERSION_ATLEAST(2, 0, 6) #include #endif namespace Renderer { namespace Backend { namespace Vulkan { namespace { std::vector GetRequiredSDLExtensions(SDL_Window* window) { if (!window) return {}; const size_t MAX_EXTENSION_COUNT = 16; unsigned int SDLExtensionCount = MAX_EXTENSION_COUNT; const char* SDLExtensions[MAX_EXTENSION_COUNT]; ENSURE(SDL_Vulkan_GetInstanceExtensions(window, &SDLExtensionCount, SDLExtensions)); std::vector requiredExtensions; requiredExtensions.reserve(SDLExtensionCount); std::copy_n(SDLExtensions, SDLExtensionCount, std::back_inserter(requiredExtensions)); return requiredExtensions; } std::vector GetAvailableValidationLayers() { uint32_t layerCount = 0; ENSURE_VK_SUCCESS(vkEnumerateInstanceLayerProperties(&layerCount, nullptr)); std::vector availableLayers(layerCount); ENSURE_VK_SUCCESS(vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data())); for (const VkLayerProperties& layer : availableLayers) { LOGMESSAGE("Vulkan validation layer: '%s' (%s) v%u.%u.%u.%u", layer.layerName, layer.description, VK_API_VERSION_VARIANT(layer.specVersion), VK_API_VERSION_MAJOR(layer.specVersion), VK_API_VERSION_MINOR(layer.specVersion), VK_API_VERSION_PATCH(layer.specVersion)); } std::vector availableValidationLayers; availableValidationLayers.reserve(layerCount); for (const VkLayerProperties& layer : availableLayers) availableValidationLayers.emplace_back(layer.layerName); return availableValidationLayers; } std::vector GetAvailableInstanceExtensions(const char* layerName = nullptr) { uint32_t extensionCount = 0; ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, nullptr)); std::vector extensions(extensionCount); ENSURE_VK_SUCCESS(vkEnumerateInstanceExtensionProperties(layerName, &extensionCount, extensions.data())); std::vector availableExtensions; for (const VkExtensionProperties& extension : extensions) availableExtensions.emplace_back(extension.extensionName); return availableExtensions; } VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* UNUSED(userData)) { if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) || (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)) LOGMESSAGE("Vulkan: %s", callbackData->pMessage); else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { struct HideRule { VkDebugUtilsMessageTypeFlagsEXT flags; std::string_view pattern; bool skip; }; constexpr HideRule hideRules[] = { // Not consumed shader output is a known problem which produces too // many warning. {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, "OutputNotConsumed", false}, // TODO: check vkGetImageMemoryRequirements2 for prefersDedicatedAllocation. {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkBindMemory-small-dedicated-allocation", false}, {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkAllocateMemory-small-allocation", false}, // We have some unnecessary clears which were needed for GL. // Ignore message for now, because they're spawned each frame. {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "ClearCmdBeforeDraw", true}, {VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, "vkCmdClearAttachments-clear-after-load", true}, // TODO: investigate probably false-positive report. {VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, "vkCmdBeginRenderPass-StoreOpDontCareThenLoadOpLoad", true}, }; const auto it = std::find_if(std::begin(hideRules), std::end(hideRules), [messageType, message = std::string_view{callbackData->pMessage}](const HideRule& hideRule) -> bool { return (hideRule.flags & messageType) && message.find(hideRule.pattern) != std::string_view::npos; }); if (it == std::end(hideRules)) LOGWARNING("Vulkan: %s", callbackData->pMessage); else if (!it->skip) LOGMESSAGE("Vulkan: %s", callbackData->pMessage); } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) LOGERROR("Vulkan: %s", callbackData->pMessage); return VK_FALSE; } // A workaround function to meet calling conventions of Vulkan, SDL and GLAD. GLADapiproc GetInstanceProcAddr(VkInstance instance, const char* name) { #if SDL_VERSION_ATLEAST(2, 0, 6) PFN_vkGetInstanceProcAddr function = reinterpret_cast(SDL_Vulkan_GetVkGetInstanceProcAddr()); return reinterpret_cast(function(instance, name)); #else return nullptr; #endif } } // anonymous namespace // static std::unique_ptr CDevice::Create(SDL_Window* window) { if (!window) { LOGERROR("Can't create Vulkan device without window."); return nullptr; } GLADuserptrloadfunc gladLoadFunction = reinterpret_cast(GetInstanceProcAddr); std::unique_ptr device(new CDevice()); device->m_Window = window; #ifdef NDEBUG bool enableDebugMessages = false; CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages); bool enableDebugLabels = false; CFG_GET_VAL("renderer.backend.debuglabels", enableDebugLabels); bool enableDebugScopedLabels = false; CFG_GET_VAL("renderer.backend.debugscopedlabels", enableDebugScopedLabels); #else bool enableDebugMessages = true; bool enableDebugLabels = true; bool enableDebugScopedLabels = true; #endif int gladVulkanVersion = gladLoadVulkanUserPtr(nullptr, gladLoadFunction, nullptr); if (!gladVulkanVersion) { LOGERROR("GLAD unable to load vulkan."); return nullptr; } VkApplicationInfo applicationInfo{}; applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; applicationInfo.pApplicationName = "0 A.D."; applicationInfo.applicationVersion = VK_MAKE_VERSION(0, 0, 27); applicationInfo.pEngineName = "Pyrogenesis"; applicationInfo.engineVersion = applicationInfo.applicationVersion; applicationInfo.apiVersion = VK_API_VERSION_1_1; std::vector requiredInstanceExtensions = GetRequiredSDLExtensions(window); device->m_ValidationLayers = GetAvailableValidationLayers(); auto hasValidationLayer = [&layers = device->m_ValidationLayers](const char* name) -> bool { return std::find(layers.begin(), layers.end(), name) != layers.end(); }; device->m_InstanceExtensions = GetAvailableInstanceExtensions(); auto hasInstanceExtension = [&extensions = device->m_InstanceExtensions](const char* name) -> bool { return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); }; #ifdef NDEBUG bool enableDebugContext = false; CFG_GET_VAL("renderer.backend.debugcontext", enableDebugContext); #else bool enableDebugContext = true; #endif if (!hasInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) enableDebugMessages = enableDebugLabels = enableDebugScopedLabels = false; const bool enableDebugLayers = enableDebugContext || enableDebugMessages || enableDebugLabels || enableDebugScopedLabels; if (enableDebugLayers) requiredInstanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); std::vector requestedValidationLayers; const bool enableValidationFeatures = enableDebugMessages && hasValidationLayer("VK_LAYER_KHRONOS_validation"); if (enableValidationFeatures) requestedValidationLayers.emplace_back("VK_LAYER_KHRONOS_validation"); // https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/docs/synchronization_usage.md VkValidationFeatureEnableEXT validationFeatureEnables[] = { VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT }; VkValidationFeaturesEXT validationFeatures{}; validationFeatures.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; validationFeatures.enabledValidationFeatureCount = std::size(validationFeatureEnables); validationFeatures.pEnabledValidationFeatures = validationFeatureEnables; VkInstanceCreateInfo instanceCreateInfo{}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instanceCreateInfo.pApplicationInfo = &applicationInfo; instanceCreateInfo.enabledExtensionCount = requiredInstanceExtensions.size(); instanceCreateInfo.ppEnabledExtensionNames = requiredInstanceExtensions.data(); if (requestedValidationLayers.empty()) { instanceCreateInfo.enabledLayerCount = 0; instanceCreateInfo.ppEnabledLayerNames = nullptr; } else { instanceCreateInfo.enabledLayerCount = requestedValidationLayers.size(); instanceCreateInfo.ppEnabledLayerNames = requestedValidationLayers.data(); } // Enabling validation features might significantly reduce performance, // even more than the standard validation layer. if (enableValidationFeatures && enableDebugContext) { instanceCreateInfo.pNext = &validationFeatures; } const VkResult createInstanceResult = vkCreateInstance(&instanceCreateInfo, nullptr, &device->m_Instance); if (createInstanceResult != VK_SUCCESS) { if (createInstanceResult == VK_ERROR_INCOMPATIBLE_DRIVER) LOGERROR("Can't create Vulkan instance: incompatible driver."); else if (createInstanceResult == VK_ERROR_EXTENSION_NOT_PRESENT) LOGERROR("Can't create Vulkan instance: extension not present."); else if (createInstanceResult == VK_ERROR_LAYER_NOT_PRESENT) LOGERROR("Can't create Vulkan instance: layer not present."); else LOGERROR("Unknown error during Vulkan instance creation: %d", static_cast(createInstanceResult)); return nullptr; } gladVulkanVersion = gladLoadVulkanUserPtr(nullptr, gladLoadFunction, device->m_Instance); if (!gladVulkanVersion) { LOGERROR("GLAD unable to re-load vulkan after its instance creation."); return nullptr; } if (GLAD_VK_EXT_debug_utils && enableDebugMessages) { VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; debugCreateInfo.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; debugCreateInfo.pfnUserCallback = DebugCallback; debugCreateInfo.pUserData = nullptr; ENSURE_VK_SUCCESS(vkCreateDebugUtilsMessengerEXT( device->m_Instance, &debugCreateInfo, nullptr, &device->m_DebugMessenger)); } if (window) ENSURE(SDL_Vulkan_CreateSurface(window, device->m_Instance, &device->m_Surface)); const std::vector requiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; std::vector availablePhyscialDevices = GetAvailablePhysicalDevices(device->m_Instance, device->m_Surface, requiredDeviceExtensions); for (const SAvailablePhysicalDevice& device : availablePhyscialDevices) { LOGMESSAGE("Vulkan available device: '%s' Type: %u Supported: %c", device.properties.deviceName, static_cast(device.properties.deviceType), IsPhysicalDeviceUnsupported(device) ? 'N' : 'Y'); LOGMESSAGE(" ID: %u VendorID: %u API Version: %u Driver Version: %u", device.properties.deviceID, device.properties.vendorID, device.properties.apiVersion, device.properties.driverVersion); LOGMESSAGE(" hasRequiredExtensions: %c hasOutputToSurfaceSupport: %c", device.hasRequiredExtensions ? 'Y' : 'N', device.hasOutputToSurfaceSupport ? 'Y' : 'N'); LOGMESSAGE(" graphicsQueueFamilyIndex: %u presentQueueFamilyIndex: %u families: %zu", device.graphicsQueueFamilyIndex, device.presentQueueFamilyIndex, device.queueFamilies.size()); LOGMESSAGE(" maxBoundDescriptorSets: %u", device.properties.limits.maxBoundDescriptorSets); for (const VkSurfaceFormatKHR& surfaceFormat : device.surfaceFormats) { LOGMESSAGE(" Surface format: %u colorSpace: %u Supported: %c", static_cast(surfaceFormat.format), static_cast(surfaceFormat.colorSpace), IsSurfaceFormatSupported(surfaceFormat) ? 'Y' : 'N'); } for (uint32_t memoryTypeIndex = 0; memoryTypeIndex < device.memoryProperties.memoryTypeCount; ++memoryTypeIndex) { const VkMemoryType& type = device.memoryProperties.memoryTypes[memoryTypeIndex]; LOGMESSAGE(" Memory Type Index: %u Flags: %u Heap Index: %u", memoryTypeIndex, static_cast(type.propertyFlags), type.heapIndex); } for (uint32_t memoryHeapIndex = 0; memoryHeapIndex < device.memoryProperties.memoryHeapCount; ++memoryHeapIndex) { const VkMemoryHeap& heap = device.memoryProperties.memoryHeaps[memoryHeapIndex]; LOGMESSAGE(" Memory Heap Index: %u Size: %zu Flags: %u", memoryHeapIndex, static_cast(heap.size / 1024), static_cast(heap.flags)); } } device->m_AvailablePhysicalDevices = availablePhyscialDevices; // We need to remove unsupported devices first. availablePhyscialDevices.erase( std::remove_if( availablePhyscialDevices.begin(), availablePhyscialDevices.end(), IsPhysicalDeviceUnsupported), availablePhyscialDevices.end()); if (availablePhyscialDevices.empty()) { LOGERROR("Vulkan can not find any supported and suitable device."); return nullptr; } int deviceIndexOverride = -1; CFG_GET_VAL("renderer.backend.vulkan.deviceindexoverride", deviceIndexOverride); auto choosedDeviceIt = device->m_AvailablePhysicalDevices.end(); if (deviceIndexOverride >= 0) { choosedDeviceIt = std::find_if( device->m_AvailablePhysicalDevices.begin(), device->m_AvailablePhysicalDevices.end(), [deviceIndexOverride](const SAvailablePhysicalDevice& availableDevice) { return availableDevice.index == static_cast(deviceIndexOverride); }); if (choosedDeviceIt == device->m_AvailablePhysicalDevices.end()) LOGWARNING("Device with override index %d not found.", deviceIndexOverride); } if (choosedDeviceIt == device->m_AvailablePhysicalDevices.end()) { // We need to choose the best available device fits our needs. choosedDeviceIt = min_element( availablePhyscialDevices.begin(), availablePhyscialDevices.end(), ComparePhysicalDevices); } device->m_ChoosenDevice = *choosedDeviceIt; const SAvailablePhysicalDevice& choosenDevice = device->m_ChoosenDevice; device->m_AvailablePhysicalDevices.erase(std::remove_if( device->m_AvailablePhysicalDevices.begin(), device->m_AvailablePhysicalDevices.end(), [physicalDevice = choosenDevice.device](const SAvailablePhysicalDevice& device) { return physicalDevice == device.device; }), device->m_AvailablePhysicalDevices.end()); gladVulkanVersion = gladLoadVulkanUserPtr(choosenDevice.device, gladLoadFunction, device->m_Instance); if (!gladVulkanVersion) { LOGERROR("GLAD unable to re-load vulkan after choosing its physical device."); return nullptr; } #if !OS_MACOSX auto hasDeviceExtension = [&extensions = choosenDevice.extensions](const char* name) -> bool { return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); }; const bool hasDescriptorIndexing = hasDeviceExtension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); #else // Metal on macOS doesn't support combined samplers natively. Currently // they break compiling SPIR-V shaders with descriptor indexing into MTL // shaders when using MoltenVK. const bool hasDescriptorIndexing = false; #endif const bool hasNeededDescriptorIndexingFeatures = hasDescriptorIndexing && choosenDevice.descriptorIndexingProperties.maxUpdateAfterBindDescriptorsInAllPools >= 65536 && choosenDevice.descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing && choosenDevice.descriptorIndexingFeatures.runtimeDescriptorArray && choosenDevice.descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount && choosenDevice.descriptorIndexingFeatures.descriptorBindingPartiallyBound && choosenDevice.descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending && choosenDevice.descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind; std::vector deviceExtensions = requiredDeviceExtensions; if (hasDescriptorIndexing) deviceExtensions.emplace_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); device->m_GraphicsQueueFamilyIndex = choosenDevice.graphicsQueueFamilyIndex; const std::array queueFamilyIndices{{ choosenDevice.graphicsQueueFamilyIndex }}; PS::StaticVector queueCreateInfos; const float queuePriority = 1.0f; std::transform(queueFamilyIndices.begin(), queueFamilyIndices.end(), std::back_inserter(queueCreateInfos), [&queuePriority](const size_t queueFamilyIndex) { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.pQueuePriorities = &queuePriority; queueCreateInfo.queueCount = 1; queueCreateInfo.queueFamilyIndex = queueFamilyIndex; return queueCreateInfo; }); // https://github.com/KhronosGroup/Vulkan-Guide/blob/master/chapters/enabling_features.adoc VkPhysicalDeviceFeatures deviceFeatures{}; VkPhysicalDeviceFeatures2 deviceFeatures2{}; VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures{}; deviceFeatures.textureCompressionBC = choosenDevice.features.textureCompressionBC; deviceFeatures.samplerAnisotropy = choosenDevice.features.samplerAnisotropy; deviceFeatures.fillModeNonSolid = choosenDevice.features.fillModeNonSolid; descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = choosenDevice.descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing; descriptorIndexingFeatures.runtimeDescriptorArray = choosenDevice.descriptorIndexingFeatures.runtimeDescriptorArray; descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = choosenDevice.descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount; descriptorIndexingFeatures.descriptorBindingPartiallyBound = choosenDevice.descriptorIndexingFeatures.descriptorBindingPartiallyBound; descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending = choosenDevice.descriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending; descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind = choosenDevice.descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind; deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; deviceFeatures2.features = deviceFeatures; if (hasNeededDescriptorIndexingFeatures) deviceFeatures2.pNext = &descriptorIndexingFeatures; VkDeviceCreateInfo deviceCreateInfo{}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; deviceCreateInfo.queueCreateInfoCount = queueCreateInfos.size(); deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data(); deviceCreateInfo.enabledExtensionCount = deviceExtensions.size(); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); deviceCreateInfo.pEnabledFeatures = nullptr; deviceCreateInfo.pNext = &deviceFeatures2; deviceCreateInfo.enabledLayerCount = 0; deviceCreateInfo.ppEnabledLayerNames = nullptr; const VkResult createDeviceResult = vkCreateDevice( choosenDevice.device, &deviceCreateInfo, nullptr, &device->m_Device); if (createDeviceResult != VK_SUCCESS) { if (createDeviceResult == VK_ERROR_FEATURE_NOT_PRESENT) LOGERROR("Can't create Vulkan device: feature not present."); else if (createDeviceResult == VK_ERROR_EXTENSION_NOT_PRESENT) LOGERROR("Can't create Vulkan device: extension not present."); else LOGERROR("Unknown error during Vulkan device creation: %d", static_cast(createDeviceResult)); return nullptr; } VmaVulkanFunctions vulkanFunctions{}; vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; vulkanFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; vulkanFunctions.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties; vulkanFunctions.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties; vulkanFunctions.vkAllocateMemory = vkAllocateMemory; vulkanFunctions.vkFreeMemory = vkFreeMemory; vulkanFunctions.vkMapMemory = vkMapMemory; vulkanFunctions.vkUnmapMemory = vkUnmapMemory; vulkanFunctions.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges; vulkanFunctions.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges; vulkanFunctions.vkBindBufferMemory = vkBindBufferMemory; vulkanFunctions.vkBindImageMemory = vkBindImageMemory; vulkanFunctions.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements; vulkanFunctions.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements; vulkanFunctions.vkCreateBuffer = vkCreateBuffer; vulkanFunctions.vkDestroyBuffer = vkDestroyBuffer; vulkanFunctions.vkCreateImage = vkCreateImage; vulkanFunctions.vkDestroyImage = vkDestroyImage; vulkanFunctions.vkCmdCopyBuffer = vkCmdCopyBuffer; // Functions promoted to Vulkan 1.1. vulkanFunctions.vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2; vulkanFunctions.vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2; vulkanFunctions.vkBindBufferMemory2KHR = vkBindBufferMemory2; vulkanFunctions.vkBindImageMemory2KHR = vkBindImageMemory2; vulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = vkGetPhysicalDeviceMemoryProperties2; VmaAllocatorCreateInfo allocatorCreateInfo{}; allocatorCreateInfo.instance = device->m_Instance; allocatorCreateInfo.physicalDevice = choosenDevice.device; allocatorCreateInfo.device = device->m_Device; allocatorCreateInfo.vulkanApiVersion = applicationInfo.apiVersion; allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions; const VkResult createVMAAllocatorResult = vmaCreateAllocator(&allocatorCreateInfo, &device->m_VMAAllocator); if (createVMAAllocatorResult != VK_SUCCESS) { LOGERROR("Failed to create VMA allocator: %d", static_cast(createDeviceResult)); return nullptr; } // We need to use VK_SHARING_MODE_CONCURRENT if we have graphics and present // in different queues. vkGetDeviceQueue(device->m_Device, choosenDevice.graphicsQueueFamilyIndex, 0, &device->m_GraphicsQueue); ENSURE(device->m_GraphicsQueue != VK_NULL_HANDLE); Capabilities& capabilities = device->m_Capabilities; capabilities.debugLabels = enableDebugLabels; capabilities.debugScopedLabels = enableDebugScopedLabels; capabilities.S3TC = choosenDevice.features.textureCompressionBC; capabilities.ARBShaders = false; capabilities.ARBShadersShadow = false; capabilities.computeShaders = true; capabilities.instancing = true; capabilities.maxSampleCount = 1; const VkSampleCountFlags sampleCountFlags = choosenDevice.properties.limits.framebufferColorSampleCounts & choosenDevice.properties.limits.framebufferDepthSampleCounts & choosenDevice.properties.limits.framebufferStencilSampleCounts; const std::array allowedSampleCountBits = { VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_16_BIT, }; for (size_t index = 0; index < allowedSampleCountBits.size(); ++index) if (sampleCountFlags & allowedSampleCountBits[index]) device->m_Capabilities.maxSampleCount = 1u << index; capabilities.multisampling = device->m_Capabilities.maxSampleCount > 1; capabilities.anisotropicFiltering = choosenDevice.features.samplerAnisotropy; capabilities.maxAnisotropy = choosenDevice.properties.limits.maxSamplerAnisotropy; capabilities.maxTextureSize = choosenDevice.properties.limits.maxImageDimension2D; device->m_RenderPassManager = std::make_unique(device.get()); device->m_SamplerManager = std::make_unique(device.get()); device->m_SubmitScheduler = std::make_unique( device.get(), device->m_GraphicsQueueFamilyIndex, device->m_GraphicsQueue); bool disableDescriptorIndexing = false; CFG_GET_VAL("renderer.backend.vulkan.disabledescriptorindexing", disableDescriptorIndexing); const bool useDescriptorIndexing = hasNeededDescriptorIndexingFeatures && !disableDescriptorIndexing; device->m_DescriptorManager = std::make_unique(device.get(), useDescriptorIndexing); device->RecreateSwapChain(); device->m_Name = choosenDevice.properties.deviceName; device->m_Version = std::to_string(VK_API_VERSION_VARIANT(choosenDevice.properties.apiVersion)) + "." + std::to_string(VK_API_VERSION_MAJOR(choosenDevice.properties.apiVersion)) + "." + std::to_string(VK_API_VERSION_MINOR(choosenDevice.properties.apiVersion)) + "." + std::to_string(VK_API_VERSION_PATCH(choosenDevice.properties.apiVersion)); device->m_DriverInformation = std::to_string(choosenDevice.properties.driverVersion); // Refs: // * https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html // * https://pcisig.com/membership/member-companies device->m_VendorID = std::to_string(choosenDevice.properties.vendorID); device->m_Extensions = choosenDevice.extensions; return device; } CDevice::CDevice() = default; CDevice::~CDevice() { if (m_Device) vkDeviceWaitIdle(m_Device); // The order of destroying does matter to avoid use-after-free and validation // layers complaints. m_BackbufferReadbackTexture.reset(); m_SubmitScheduler.reset(); ProcessTextureToDestroyQueue(true); m_RenderPassManager.reset(); m_SamplerManager.reset(); m_DescriptorManager.reset(); m_SwapChain.reset(); ProcessObjectToDestroyQueue(true); if (m_VMAAllocator != VK_NULL_HANDLE) vmaDestroyAllocator(m_VMAAllocator); if (m_Device != VK_NULL_HANDLE) vkDestroyDevice(m_Device, nullptr); if (m_Surface != VK_NULL_HANDLE) vkDestroySurfaceKHR(m_Instance, m_Surface, nullptr); if (GLAD_VK_EXT_debug_utils && m_DebugMessenger) vkDestroyDebugUtilsMessengerEXT(m_Instance, m_DebugMessenger, nullptr); if (m_Instance != VK_NULL_HANDLE) vkDestroyInstance(m_Instance, nullptr); } void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { Script::SetProperty(rq, settings, "name", "vulkan"); Script::SetProperty(rq, settings, "extensions", m_Extensions); JS::RootedValue device(rq.cx); Script::CreateObject(rq, &device); ReportAvailablePhysicalDevice(m_ChoosenDevice, rq, device); Script::SetProperty(rq, settings, "choosen_device", device); JS::RootedValue availableDevices(rq.cx); Script::CreateArray(rq, &availableDevices, m_AvailablePhysicalDevices.size()); for (size_t index = 0; index < m_AvailablePhysicalDevices.size(); ++index) { JS::RootedValue device(rq.cx); Script::CreateObject(rq, &device); ReportAvailablePhysicalDevice(m_AvailablePhysicalDevices[index], rq, device); Script::SetPropertyInt(rq, availableDevices, index, device); } - Script::SetProperty(rq, settings, "available_device", availableDevices); + Script::SetProperty(rq, settings, "available_devices", availableDevices); Script::SetProperty(rq, settings, "instance_extensions", m_InstanceExtensions); Script::SetProperty(rq, settings, "validation_layers", m_ValidationLayers); } std::unique_ptr CDevice::CreateGraphicsPipelineState( const SGraphicsPipelineStateDesc& pipelineStateDesc) { return CGraphicsPipelineState::Create(this, pipelineStateDesc); } std::unique_ptr CDevice::CreateVertexInputLayout( const PS::span attributes) { return std::make_unique(this, attributes); } std::unique_ptr CDevice::CreateTexture( const char* name, const ITexture::Type type, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CTexture::Create( this, name, type, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D( const char* name, const uint32_t usage, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture( name, ITexture::Type::TEXTURE_2D, usage, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, SColorAttachment* colorAttachment, SDepthStencilAttachment* depthStencilAttachment) { return CFramebuffer::Create( this, name, colorAttachment, depthStencilAttachment); } std::unique_ptr CDevice::CreateBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { return CreateCBuffer(name, type, size, dynamic); } std::unique_ptr CDevice::CreateCBuffer( const char* name, const IBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, name, type, size, dynamic); } std::unique_ptr CDevice::CreateShaderProgram( const CStr& name, const CShaderDefines& defines) { return CShaderProgram::Create(this, name, defines); } std::unique_ptr CDevice::CreateCommandContext() { return CDeviceCommandContext::Create(this); } bool CDevice::AcquireNextBackbuffer() { if (!IsSwapChainValid()) { vkDeviceWaitIdle(m_Device); RecreateSwapChain(); if (!IsSwapChainValid()) return false; } PROFILE3("AcquireNextBackbuffer"); return m_SubmitScheduler->AcquireNextImage(*m_SwapChain); } IFramebuffer* CDevice::GetCurrentBackbuffer( const AttachmentLoadOp colorAttachmentLoadOp, const AttachmentStoreOp colorAttachmentStoreOp, const AttachmentLoadOp depthStencilAttachmentLoadOp, const AttachmentStoreOp depthStencilAttachmentStoreOp) { return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbuffer( colorAttachmentLoadOp, colorAttachmentStoreOp, depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp) : nullptr; } void CDevice::Present() { if (!IsSwapChainValid()) return; PROFILE3("Present"); m_SubmitScheduler->Present(*m_SwapChain); ProcessObjectToDestroyQueue(); ProcessTextureToDestroyQueue(); ++m_FrameID; } void CDevice::OnWindowResize(const uint32_t width, const uint32_t height) { if (!IsSwapChainValid() || width != m_SwapChain->GetDepthTexture()->GetWidth() || height != m_SwapChain->GetDepthTexture()->GetHeight()) { RecreateSwapChain(); } } bool CDevice::IsTextureFormatSupported(const Format format) const { switch (format) { case Format::UNDEFINED: return false; case Format::R8G8B8_UNORM: return false; case Format::BC1_RGB_UNORM: FALLTHROUGH; case Format::BC1_RGBA_UNORM: FALLTHROUGH; case Format::BC2_UNORM: FALLTHROUGH; case Format::BC3_UNORM: return m_Capabilities.S3TC; default: break; } VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties( m_ChoosenDevice.device, Mapping::FromFormat(format), &formatProperties); return formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; } bool CDevice::IsFramebufferFormatSupported(const Format format) const { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties( m_ChoosenDevice.device, Mapping::FromFormat(format), &formatProperties); if (IsDepthFormat(format)) return formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; return formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; } Format CDevice::GetPreferredDepthStencilFormat( const uint32_t usage, const bool depth, const bool stencil) const { ENSURE(depth || stencil); Format format = Format::UNDEFINED; if (stencil) { // https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/depth.adoc#depth-formats // At least one of VK_FORMAT_D24_UNORM_S8_UINT or VK_FORMAT_D32_SFLOAT_S8_UINT // must also be supported. if (IsFormatSupportedForUsage(Format::D24_UNORM_S8_UINT, usage)) format = Format::D24_UNORM_S8_UINT; else format = Format::D32_SFLOAT_S8_UINT; } else { std::array formatRequestOrder; // TODO: add most known vendors to enum. // https://developer.nvidia.com/blog/vulkan-dos-donts/ if (m_ChoosenDevice.properties.vendorID == 0x10DE) formatRequestOrder = {Format::D24_UNORM, Format::D32_SFLOAT, Format::D16_UNORM}; else formatRequestOrder = {Format::D32_SFLOAT, Format::D24_UNORM, Format::D16_UNORM}; for (const Format formatRequest : formatRequestOrder) if (IsFormatSupportedForUsage(formatRequest, usage)) { format = formatRequest; break; } } return format; } bool CDevice::IsFormatSupportedForUsage(const Format format, const uint32_t usage) const { VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties( m_ChoosenDevice.device, Mapping::FromFormat(format), &formatProperties); VkFormatFeatureFlags expectedFeatures = 0; if (usage & ITexture::Usage::COLOR_ATTACHMENT) expectedFeatures |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; if (usage & ITexture::Usage::DEPTH_STENCIL_ATTACHMENT) expectedFeatures |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; if (usage & ITexture::Usage::SAMPLED) expectedFeatures |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; if (usage & ITexture::Usage::TRANSFER_SRC) expectedFeatures |= VK_FORMAT_FEATURE_TRANSFER_SRC_BIT; if (usage & ITexture::Usage::TRANSFER_DST) expectedFeatures |= VK_FORMAT_FEATURE_TRANSFER_DST_BIT; return (formatProperties.optimalTilingFeatures & expectedFeatures) == expectedFeatures; } void CDevice::ScheduleObjectToDestroy( VkObjectType type, const uint64_t handle, const VmaAllocation allocation) { m_ObjectToDestroyQueue.push({m_FrameID, type, handle, allocation}); } void CDevice::ScheduleTextureToDestroy(const CTexture::UID uid) { m_TextureToDestroyQueue.push({m_FrameID, uid}); } void CDevice::SetObjectName(VkObjectType type, const uint64_t handle, const char* name) { if (!m_Capabilities.debugLabels) return; VkDebugUtilsObjectNameInfoEXT nameInfo{}; nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; nameInfo.objectType = type; nameInfo.objectHandle = handle; nameInfo.pObjectName = name; vkSetDebugUtilsObjectNameEXT(m_Device, &nameInfo); } std::unique_ptr CDevice::CreateRingCommandContext(const size_t size) { return std::make_unique( this, size, m_GraphicsQueueFamilyIndex, *m_SubmitScheduler); } void CDevice::RecreateSwapChain() { m_BackbufferReadbackTexture.reset(); int surfaceDrawableWidth = 0, surfaceDrawableHeight = 0; SDL_Vulkan_GetDrawableSize(m_Window, &surfaceDrawableWidth, &surfaceDrawableHeight); m_SwapChain = CSwapChain::Create( this, m_Surface, surfaceDrawableWidth, surfaceDrawableHeight, std::move(m_SwapChain)); } bool CDevice::IsSwapChainValid() { return m_SwapChain && m_SwapChain->IsValid(); } void CDevice::ProcessObjectToDestroyQueue(const bool ignoreFrameID) { while (!m_ObjectToDestroyQueue.empty() && (ignoreFrameID || m_ObjectToDestroyQueue.front().frameID + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID)) { ObjectToDestroy& object = m_ObjectToDestroyQueue.front(); #if VK_USE_64_BIT_PTR_DEFINES void* handle = reinterpret_cast(object.handle); #else const uint64_t handle = object.handle; #endif switch (object.type) { case VK_OBJECT_TYPE_IMAGE: vmaDestroyImage(GetVMAAllocator(), static_cast(handle), object.allocation); break; case VK_OBJECT_TYPE_BUFFER: vmaDestroyBuffer(GetVMAAllocator(), static_cast(handle), object.allocation); break; case VK_OBJECT_TYPE_IMAGE_VIEW: vkDestroyImageView(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_BUFFER_VIEW: vkDestroyBufferView(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_FRAMEBUFFER: vkDestroyFramebuffer(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_RENDER_PASS: vkDestroyRenderPass(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_SAMPLER: vkDestroySampler(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_SHADER_MODULE: vkDestroyShaderModule(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_PIPELINE_LAYOUT: vkDestroyPipelineLayout(m_Device, static_cast(handle), nullptr); break; case VK_OBJECT_TYPE_PIPELINE: vkDestroyPipeline(m_Device, static_cast(handle), nullptr); break; default: debug_warn("Unsupported object to destroy type."); } m_ObjectToDestroyQueue.pop(); } } void CDevice::ProcessTextureToDestroyQueue(const bool ignoreFrameID) { while (!m_TextureToDestroyQueue.empty() && (ignoreFrameID || m_TextureToDestroyQueue.front().first + NUMBER_OF_FRAMES_IN_FLIGHT < m_FrameID)) { GetDescriptorManager().OnTextureDestroy(m_TextureToDestroyQueue.front().second); m_TextureToDestroyQueue.pop(); } } CTexture* CDevice::GetCurrentBackbufferTexture() { return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbufferTexture() : nullptr; } CTexture* CDevice::GetOrCreateBackbufferReadbackTexture() { if (!IsSwapChainValid()) return nullptr; if (!m_BackbufferReadbackTexture) { CTexture* currentBackbufferTexture = m_SwapChain->GetCurrentBackbufferTexture(); m_BackbufferReadbackTexture = CTexture::CreateReadback( this, "BackbufferReadback", currentBackbufferTexture->GetFormat(), currentBackbufferTexture->GetWidth(), currentBackbufferTexture->GetHeight()); } return m_BackbufferReadbackTexture.get(); } std::unique_ptr CreateDevice(SDL_Window* window) { return Vulkan::CDevice::Create(window); } } // namespace Vulkan } // namespace Backend } // namespace Renderer