mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-27 02:19:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1115 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1115 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2016 Dolphin Emulator Project
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| #include "VideoBackends/Vulkan/VulkanContext.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <array>
 | |
| #include <cstring>
 | |
| 
 | |
| #include "Common/Assert.h"
 | |
| #include "Common/Contains.h"
 | |
| #include "Common/Logging/Log.h"
 | |
| 
 | |
| #include "VideoCommon/DriverDetails.h"
 | |
| #include "VideoCommon/VideoCommon.h"
 | |
| 
 | |
| namespace Vulkan
 | |
| {
 | |
| static constexpr const char* VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation";
 | |
| 
 | |
| std::unique_ptr<VulkanContext> g_vulkan_context;
 | |
| 
 | |
| template <typename Chain, typename Element>
 | |
| static void InsertIntoChain(Chain* chain, Element* element)
 | |
| {
 | |
|   element->pNext = chain->pNext;
 | |
|   chain->pNext = element;
 | |
| }
 | |
| 
 | |
| VulkanContext::PhysicalDeviceInfo::PhysicalDeviceInfo(VkPhysicalDevice device)
 | |
| {
 | |
|   VkPhysicalDeviceFeatures features;
 | |
|   VkPhysicalDeviceProperties2 properties2;
 | |
|   VkPhysicalDeviceProperties& properties = properties2.properties;
 | |
| 
 | |
|   vkGetPhysicalDeviceProperties(device, &properties);
 | |
|   vkGetPhysicalDeviceFeatures(device, &features);
 | |
|   apiVersion = vkGetPhysicalDeviceProperties2 ? properties.apiVersion : VK_API_VERSION_1_0;
 | |
| 
 | |
|   if (apiVersion >= VK_API_VERSION_1_1)
 | |
|   {
 | |
|     VkPhysicalDeviceSubgroupProperties properties_subgroup = {};
 | |
|     VkPhysicalDeviceVulkan12Properties properties_vk12 = {};
 | |
|     properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
 | |
|     properties2.pNext = nullptr;
 | |
|     properties_subgroup.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
 | |
|     InsertIntoChain(&properties2, &properties_subgroup);
 | |
| 
 | |
|     if (apiVersion >= VK_API_VERSION_1_2)
 | |
|     {
 | |
|       properties_vk12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES;
 | |
|       InsertIntoChain(&properties2, &properties_vk12);
 | |
|     }
 | |
| 
 | |
|     vkGetPhysicalDeviceProperties2(device, &properties2);
 | |
| 
 | |
|     if (apiVersion >= VK_API_VERSION_1_2)
 | |
|     {
 | |
|       driverID = properties_vk12.driverID;
 | |
|     }
 | |
| 
 | |
|     subgroupSize = properties_subgroup.subgroupSize;
 | |
| 
 | |
|     // We require basic ops (for gl_SubgroupInvocationID), ballot (for subgroupBallot,
 | |
|     // subgroupBallotFindLSB), and arithmetic (for subgroupMin/subgroupMax).
 | |
|     // Shuffle is enabled as a workaround until SPIR-V >= 1.5 is enabled with broadcast(uniform)
 | |
|     // support.
 | |
|     constexpr VkSubgroupFeatureFlags required_operations =
 | |
|         VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT |
 | |
|         VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT;
 | |
|     shaderSubgroupOperations =
 | |
|         (properties_subgroup.supportedOperations & required_operations) == required_operations &&
 | |
|         properties_subgroup.supportedStages & VK_SHADER_STAGE_FRAGMENT_BIT;
 | |
|   }
 | |
| 
 | |
|   memcpy(deviceName, properties.deviceName, sizeof(deviceName));
 | |
|   memcpy(pipelineCacheUUID, properties.pipelineCacheUUID, sizeof(pipelineCacheUUID));
 | |
|   vendorID = properties.vendorID;
 | |
|   deviceID = properties.deviceID;
 | |
|   minUniformBufferOffsetAlignment =
 | |
|       std::max<VkDeviceSize>(properties.limits.minUniformBufferOffsetAlignment, 1);
 | |
|   bufferImageGranularity = std::max<VkDeviceSize>(properties.limits.bufferImageGranularity, 1);
 | |
|   maxTexelBufferElements = properties.limits.maxTexelBufferElements;
 | |
|   maxImageDimension2D = properties.limits.maxImageDimension2D;
 | |
|   framebufferColorSampleCounts = properties.limits.framebufferColorSampleCounts;
 | |
|   framebufferDepthSampleCounts = properties.limits.framebufferDepthSampleCounts;
 | |
|   memcpy(pointSizeRange, properties.limits.pointSizeRange, sizeof(pointSizeRange));
 | |
|   maxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
 | |
| 
 | |
|   dualSrcBlend = features.dualSrcBlend != VK_FALSE;
 | |
|   geometryShader = features.geometryShader != VK_FALSE;
 | |
|   samplerAnisotropy = features.samplerAnisotropy != VK_FALSE;
 | |
|   logicOp = features.logicOp != VK_FALSE;
 | |
|   fragmentStoresAndAtomics = features.fragmentStoresAndAtomics != VK_FALSE;
 | |
|   sampleRateShading = features.sampleRateShading != VK_FALSE;
 | |
|   largePoints = features.largePoints != VK_FALSE;
 | |
|   shaderStorageImageMultisample = features.shaderStorageImageMultisample != VK_FALSE;
 | |
|   shaderTessellationAndGeometryPointSize =
 | |
|       features.shaderTessellationAndGeometryPointSize != VK_FALSE;
 | |
|   occlusionQueryPrecise = features.occlusionQueryPrecise != VK_FALSE;
 | |
|   shaderClipDistance = features.shaderClipDistance != VK_FALSE;
 | |
|   depthClamp = features.depthClamp != VK_FALSE;
 | |
|   textureCompressionBC = features.textureCompressionBC != VK_FALSE;
 | |
| }
 | |
| 
 | |
| VkPhysicalDeviceFeatures VulkanContext::PhysicalDeviceInfo::features() const
 | |
| {
 | |
|   VkPhysicalDeviceFeatures features;
 | |
|   memset(&features, 0, sizeof(features));
 | |
|   features.dualSrcBlend = dualSrcBlend ? VK_TRUE : VK_FALSE;
 | |
|   features.geometryShader = geometryShader ? VK_TRUE : VK_FALSE;
 | |
|   features.samplerAnisotropy = samplerAnisotropy ? VK_TRUE : VK_FALSE;
 | |
|   features.logicOp = logicOp ? VK_TRUE : VK_FALSE;
 | |
|   features.fragmentStoresAndAtomics = fragmentStoresAndAtomics ? VK_TRUE : VK_FALSE;
 | |
|   features.sampleRateShading = sampleRateShading ? VK_TRUE : VK_FALSE;
 | |
|   features.largePoints = largePoints ? VK_TRUE : VK_FALSE;
 | |
|   features.shaderStorageImageMultisample = shaderStorageImageMultisample ? VK_TRUE : VK_FALSE;
 | |
|   features.shaderTessellationAndGeometryPointSize =
 | |
|       shaderTessellationAndGeometryPointSize ? VK_TRUE : VK_FALSE;
 | |
|   features.occlusionQueryPrecise = occlusionQueryPrecise ? VK_TRUE : VK_FALSE;
 | |
|   features.shaderClipDistance = shaderClipDistance ? VK_TRUE : VK_FALSE;
 | |
|   features.depthClamp = depthClamp ? VK_TRUE : VK_FALSE;
 | |
|   features.textureCompressionBC = textureCompressionBC ? VK_TRUE : VK_FALSE;
 | |
|   return features;
 | |
| }
 | |
| 
 | |
| VulkanContext::VulkanContext(VkInstance instance, VkPhysicalDevice physical_device)
 | |
|     : m_instance(instance), m_physical_device(physical_device), m_device_info(physical_device)
 | |
| {
 | |
| }
 | |
| 
 | |
| VulkanContext::~VulkanContext()
 | |
| {
 | |
|   if (m_allocator != VK_NULL_HANDLE)
 | |
|     vmaDestroyAllocator(m_allocator);
 | |
|   if (m_device != VK_NULL_HANDLE)
 | |
|     vkDestroyDevice(m_device, nullptr);
 | |
| 
 | |
|   if (m_debug_utils_messenger != VK_NULL_HANDLE)
 | |
|     DisableDebugUtils();
 | |
| 
 | |
|   vkDestroyInstance(m_instance, nullptr);
 | |
| }
 | |
| 
 | |
| bool VulkanContext::CheckValidationLayerAvailablility()
 | |
| {
 | |
|   u32 extension_count = 0;
 | |
|   VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::vector<VkExtensionProperties> extension_list(extension_count);
 | |
|   res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extension_list.data());
 | |
|   ASSERT(res == VK_SUCCESS);
 | |
| 
 | |
|   u32 layer_count = 0;
 | |
|   res = vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::vector<VkLayerProperties> layer_list(layer_count);
 | |
|   res = vkEnumerateInstanceLayerProperties(&layer_count, layer_list.data());
 | |
|   ASSERT(res == VK_SUCCESS);
 | |
| 
 | |
|   bool supports_validation_layers = Common::Contains(
 | |
|       layer_list, std::string_view{VALIDATION_LAYER_NAME}, &VkLayerProperties::layerName);
 | |
| 
 | |
|   bool supports_debug_utils =
 | |
|       Common::Contains(extension_list, std::string_view{VK_EXT_DEBUG_UTILS_EXTENSION_NAME},
 | |
|                        &VkExtensionProperties::extensionName);
 | |
| 
 | |
|   if (!supports_debug_utils && supports_validation_layers)
 | |
|   {
 | |
|     // If the instance doesn't support debug utils but we're using validation layers,
 | |
|     // try to use the implementation of the extension provided by the validation layers.
 | |
|     extension_count = 0;
 | |
|     res = vkEnumerateInstanceExtensionProperties(VALIDATION_LAYER_NAME, &extension_count, nullptr);
 | |
|     if (res != VK_SUCCESS)
 | |
|     {
 | |
|       LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     extension_list.resize(extension_count);
 | |
|     res = vkEnumerateInstanceExtensionProperties(VALIDATION_LAYER_NAME, &extension_count,
 | |
|                                                  extension_list.data());
 | |
|     ASSERT(res == VK_SUCCESS);
 | |
|     supports_debug_utils =
 | |
|         Common::Contains(extension_list, std::string_view{VK_EXT_DEBUG_UTILS_EXTENSION_NAME},
 | |
|                          &VkExtensionProperties::extensionName);
 | |
|   }
 | |
| 
 | |
|   // Check for both VK_EXT_debug_utils and VK_LAYER_KHRONOS_validation
 | |
|   return supports_debug_utils && supports_validation_layers;
 | |
| }
 | |
| 
 | |
| static u32 getAPIVersion()
 | |
| {
 | |
|   u32 supported_version;
 | |
|   u32 used_version = VK_API_VERSION_1_0;
 | |
|   if (vkEnumerateInstanceVersion && vkEnumerateInstanceVersion(&supported_version) == VK_SUCCESS)
 | |
|   {
 | |
|     // The device itself may not support 1.1, so we check that before using any 1.1 functionality.
 | |
|     if (supported_version >= VK_API_VERSION_1_2)
 | |
|       used_version = VK_API_VERSION_1_2;
 | |
|     else if (supported_version >= VK_API_VERSION_1_1)
 | |
|       used_version = VK_API_VERSION_1_1;
 | |
|     WARN_LOG_FMT(HOST_GPU, "Using Vulkan 1.{}, supported: {}.{}", VK_VERSION_MINOR(used_version),
 | |
|                  VK_VERSION_MAJOR(supported_version), VK_VERSION_MINOR(supported_version));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     WARN_LOG_FMT(HOST_GPU, "Using Vulkan 1.0");
 | |
|   }
 | |
|   return used_version;
 | |
| }
 | |
| 
 | |
| VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool enable_debug_utils,
 | |
|                                                bool enable_validation_layer,
 | |
|                                                u32* out_vk_api_version)
 | |
| {
 | |
|   std::vector<const char*> enabled_extensions;
 | |
|   if (!SelectInstanceExtensions(&enabled_extensions, wstype, enable_debug_utils,
 | |
|                                 enable_validation_layer))
 | |
|     return VK_NULL_HANDLE;
 | |
| 
 | |
|   VkApplicationInfo app_info = {};
 | |
|   app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
 | |
|   app_info.pNext = nullptr;
 | |
|   app_info.pApplicationName = "Dolphin Emulator";
 | |
|   app_info.applicationVersion = VK_MAKE_VERSION(5, 0, 0);
 | |
|   app_info.pEngineName = "Dolphin Emulator";
 | |
|   app_info.engineVersion = VK_MAKE_VERSION(5, 0, 0);
 | |
|   app_info.apiVersion = getAPIVersion();
 | |
| 
 | |
|   *out_vk_api_version = app_info.apiVersion;
 | |
| 
 | |
|   VkInstanceCreateInfo instance_create_info = {};
 | |
|   instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
 | |
|   instance_create_info.pNext = nullptr;
 | |
|   instance_create_info.flags = 0;
 | |
|   instance_create_info.pApplicationInfo = &app_info;
 | |
|   instance_create_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
 | |
|   instance_create_info.ppEnabledExtensionNames = enabled_extensions.data();
 | |
|   instance_create_info.enabledLayerCount = 0;
 | |
|   instance_create_info.ppEnabledLayerNames = nullptr;
 | |
| 
 | |
|   // Enable validation layer if the user enabled them in the settings
 | |
|   if (enable_validation_layer)
 | |
|   {
 | |
|     instance_create_info.enabledLayerCount = 1;
 | |
|     instance_create_info.ppEnabledLayerNames = &VALIDATION_LAYER_NAME;
 | |
|   }
 | |
| 
 | |
|   VkInstance instance;
 | |
|   VkResult res = vkCreateInstance(&instance_create_info, nullptr, &instance);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkCreateInstance failed: ");
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return instance;
 | |
| }
 | |
| 
 | |
| bool VulkanContext::SelectInstanceExtensions(std::vector<const char*>* extension_list,
 | |
|                                              WindowSystemType wstype, bool enable_debug_utils,
 | |
|                                              bool validation_layer_enabled)
 | |
| {
 | |
|   u32 extension_count = 0;
 | |
|   VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (extension_count == 0)
 | |
|   {
 | |
|     ERROR_LOG_FMT(VIDEO, "Vulkan: No extensions supported by instance.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::vector<VkExtensionProperties> available_extension_list(extension_count);
 | |
|   res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count,
 | |
|                                                available_extension_list.data());
 | |
|   ASSERT(res == VK_SUCCESS);
 | |
| 
 | |
|   u32 validation_layer_extension_count = 0;
 | |
|   std::vector<VkExtensionProperties> validation_layer_extension_list;
 | |
|   if (validation_layer_enabled)
 | |
|   {
 | |
|     res = vkEnumerateInstanceExtensionProperties(VALIDATION_LAYER_NAME,
 | |
|                                                  &validation_layer_extension_count, nullptr);
 | |
|     if (res != VK_SUCCESS)
 | |
|     {
 | |
|       LOG_VULKAN_ERROR(res,
 | |
|                        "vkEnumerateInstanceExtensionProperties failed for validation layers: ");
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       validation_layer_extension_list.resize(validation_layer_extension_count);
 | |
|       res = vkEnumerateInstanceExtensionProperties(VALIDATION_LAYER_NAME,
 | |
|                                                    &validation_layer_extension_count,
 | |
|                                                    validation_layer_extension_list.data());
 | |
|       ASSERT(res == VK_SUCCESS);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (const auto& extension_properties : available_extension_list)
 | |
|     INFO_LOG_FMT(VIDEO, "Available extension: {}", extension_properties.extensionName);
 | |
| 
 | |
|   for (const auto& extension_properties : validation_layer_extension_list)
 | |
|   {
 | |
|     INFO_LOG_FMT(VIDEO, "Available extension in validation layer: {}",
 | |
|                  extension_properties.extensionName);
 | |
|   }
 | |
| 
 | |
|   auto AddExtension = [&](const char* name, bool required) {
 | |
|     bool extension_supported =
 | |
|         Common::Contains(available_extension_list, std::string_view{name},
 | |
|                          &VkExtensionProperties::extensionName) ||
 | |
|         Common::Contains(validation_layer_extension_list, std::string_view{name},
 | |
|                          &VkExtensionProperties::extensionName);
 | |
| 
 | |
|     if (extension_supported)
 | |
|     {
 | |
|       INFO_LOG_FMT(VIDEO, "Enabling extension: {}", name);
 | |
|       extension_list->push_back(name);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (required)
 | |
|       ERROR_LOG_FMT(VIDEO, "Vulkan: Missing required extension {}.", name);
 | |
| 
 | |
|     return false;
 | |
|   };
 | |
| 
 | |
|   // Common extensions
 | |
|   if (wstype != WindowSystemType::Headless && !AddExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
| #if defined(VK_USE_PLATFORM_WIN32_KHR)
 | |
|   if (wstype == WindowSystemType::Windows &&
 | |
|       !AddExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| #endif
 | |
| #if defined(VK_USE_PLATFORM_XLIB_KHR)
 | |
|   if (wstype == WindowSystemType::X11 && !AddExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| #endif
 | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR)
 | |
|   if (wstype == WindowSystemType::Android &&
 | |
|       !AddExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| #endif
 | |
| #if defined(VK_USE_PLATFORM_METAL_EXT)
 | |
|   if (wstype == WindowSystemType::MacOS && !AddExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
 | |
|   if (wstype != WindowSystemType::Headless)
 | |
|   {
 | |
|     AddExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false);
 | |
|   }
 | |
| 
 | |
|   // VK_EXT_debug_utils
 | |
|   if (AddExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false))
 | |
|   {
 | |
|     g_backend_info.bSupportsSettingObjectNames = true;
 | |
|   }
 | |
|   else if (enable_debug_utils)
 | |
|   {
 | |
|     WARN_LOG_FMT(VIDEO, "Vulkan: Debug utils requested, but extension is not available.");
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| VulkanContext::GPUList VulkanContext::EnumerateGPUs(VkInstance instance)
 | |
| {
 | |
|   u32 gpu_count = 0;
 | |
|   VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   GPUList gpus;
 | |
|   gpus.resize(gpu_count);
 | |
| 
 | |
|   res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data());
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   return gpus;
 | |
| }
 | |
| 
 | |
| void VulkanContext::PopulateBackendInfo(BackendInfo* backend_info)
 | |
| {
 | |
|   backend_info->api_type = APIType::Vulkan;
 | |
|   backend_info->bSupports3DVision = false;                  // D3D-exclusive.
 | |
|   backend_info->bSupportsEarlyZ = true;                     // Assumed support.
 | |
|   backend_info->bSupportsPrimitiveRestart = true;           // Assumed support.
 | |
|   backend_info->bSupportsBindingLayout = false;             // Assumed support.
 | |
|   backend_info->bSupportsPaletteConversion = true;          // Assumed support.
 | |
|   backend_info->bSupportsClipControl = true;                // Assumed support.
 | |
|   backend_info->bSupportsMultithreading = true;             // Assumed support.
 | |
|   backend_info->bSupportsComputeShaders = true;             // Assumed support.
 | |
|   backend_info->bSupportsGPUTextureDecoding = true;         // Assumed support.
 | |
|   backend_info->bSupportsBitfield = true;                   // Assumed support.
 | |
|   backend_info->bSupportsPartialDepthCopies = true;         // Assumed support.
 | |
|   backend_info->bSupportsShaderBinaries = true;             // Assumed support.
 | |
|   backend_info->bSupportsPipelineCacheData = false;         // Handled via pipeline caches.
 | |
|   backend_info->bSupportsDynamicSamplerIndexing = true;     // Assumed support.
 | |
|   backend_info->bSupportsPostProcessing = true;             // Assumed support.
 | |
|   backend_info->bSupportsBackgroundCompiling = true;        // Assumed support.
 | |
|   backend_info->bSupportsCopyToVram = true;                 // Assumed support.
 | |
|   backend_info->bSupportsReversedDepthRange = true;         // Assumed support.
 | |
|   backend_info->bSupportsExclusiveFullscreen = false;       // Dependent on OS and features.
 | |
|   backend_info->bSupportsDualSourceBlend = false;           // Dependent on features.
 | |
|   backend_info->bSupportsGeometryShaders = false;           // Dependent on features.
 | |
|   backend_info->bSupportsGSInstancing = false;              // Dependent on features.
 | |
|   backend_info->bSupportsBBox = false;                      // Dependent on features.
 | |
|   backend_info->bSupportsFragmentStoresAndAtomics = false;  // Dependent on features.
 | |
|   backend_info->bSupportsSSAA = false;                      // Dependent on features.
 | |
|   backend_info->bSupportsDepthClamp = false;                // Dependent on features.
 | |
|   backend_info->bSupportsST3CTextures = false;              // Dependent on features.
 | |
|   backend_info->bSupportsBPTCTextures = false;              // Dependent on features.
 | |
|   backend_info->bSupportsLogicOp = false;                   // Dependent on features.
 | |
|   backend_info->bSupportsLargePoints = false;               // Dependent on features.
 | |
|   backend_info->bSupportsFramebufferFetch = false;          // Dependent on OS and features.
 | |
|   backend_info->bSupportsCoarseDerivatives = true;          // Assumed support.
 | |
|   backend_info->bSupportsTextureQueryLevels = true;         // Assumed support.
 | |
|   backend_info->bSupportsLodBiasInSampler = false;          // Dependent on OS.
 | |
|   backend_info->bSupportsSettingObjectNames = false;        // Dependent on features.
 | |
|   backend_info->bSupportsPartialMultisampleResolve = true;  // Assumed support.
 | |
|   backend_info->bSupportsDynamicVertexLoader = true;        // Assumed support.
 | |
|   backend_info->bSupportsVSLinePointExpand = true;          // Assumed support.
 | |
|   backend_info->bSupportsHDROutput = true;                  // Assumed support.
 | |
|   backend_info->bSupportsUnrestrictedDepthRange = false;    // Dependent on features.
 | |
| }
 | |
| 
 | |
| void VulkanContext::PopulateBackendInfoAdapters(BackendInfo* backend_info, const GPUList& gpu_list)
 | |
| {
 | |
|   backend_info->Adapters.clear();
 | |
|   for (VkPhysicalDevice physical_device : gpu_list)
 | |
|   {
 | |
|     VkPhysicalDeviceProperties properties;
 | |
|     vkGetPhysicalDeviceProperties(physical_device, &properties);
 | |
|     backend_info->Adapters.push_back(properties.deviceName);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void VulkanContext::PopulateBackendInfoFeatures(BackendInfo* backend_info, VkPhysicalDevice gpu,
 | |
|                                                 const PhysicalDeviceInfo& info)
 | |
| {
 | |
|   backend_info->MaxTextureSize = info.maxImageDimension2D;
 | |
|   backend_info->bUsesLowerLeftOrigin = false;
 | |
|   backend_info->bSupportsDualSourceBlend = info.dualSrcBlend;
 | |
|   backend_info->bSupportsGeometryShaders = info.geometryShader;
 | |
|   backend_info->bSupportsGSInstancing = info.geometryShader;
 | |
|   backend_info->bSupportsBBox = backend_info->bSupportsFragmentStoresAndAtomics =
 | |
|       info.fragmentStoresAndAtomics;
 | |
|   backend_info->bSupportsSSAA = info.sampleRateShading;
 | |
|   backend_info->bSupportsLogicOp = info.logicOp;
 | |
| 
 | |
|   // Metal doesn't support this.
 | |
|   backend_info->bSupportsLodBiasInSampler = info.driverID != VK_DRIVER_ID_MOLTENVK;
 | |
| 
 | |
|   // Disable geometry shader when shaderTessellationAndGeometryPointSize is not supported.
 | |
|   // Seems this is needed for gl_Layer.
 | |
|   if (!info.shaderTessellationAndGeometryPointSize)
 | |
|   {
 | |
|     backend_info->bSupportsGeometryShaders = VK_FALSE;
 | |
|     backend_info->bSupportsGSInstancing = VK_FALSE;
 | |
|   }
 | |
| 
 | |
|   // Depth clamping implies shaderClipDistance and depthClamp
 | |
|   backend_info->bSupportsDepthClamp = info.depthClamp && info.shaderClipDistance;
 | |
| 
 | |
|   // textureCompressionBC implies BC1 through BC7, which is a superset of DXT1/3/5, which we need.
 | |
|   backend_info->bSupportsST3CTextures = info.textureCompressionBC;
 | |
|   backend_info->bSupportsBPTCTextures = info.textureCompressionBC;
 | |
| 
 | |
|   // Some devices don't support point sizes >1 (e.g. Adreno).
 | |
|   // If we can't use a point size above our maximum IR, use triangles instead for EFB pokes.
 | |
|   // This means a 6x increase in the size of the vertices, though.
 | |
|   backend_info->bSupportsLargePoints =
 | |
|       info.largePoints && info.pointSizeRange[0] <= 1.0f && info.pointSizeRange[1] >= 16;
 | |
| 
 | |
|   std::string device_name = info.deviceName;
 | |
|   u32 vendor_id = info.vendorID;
 | |
|   bool is_moltenvk = info.driverID == VK_DRIVER_ID_MOLTENVK;
 | |
| 
 | |
|   // Only Apple family GPUs support framebuffer fetch.
 | |
|   // We currently use a hacked MoltenVK to implement this, so don't attempt outside of MVK
 | |
|   if (is_moltenvk && (vendor_id == 0x106B || device_name.find("Apple") != std::string::npos))
 | |
|   {
 | |
|     backend_info->bSupportsFramebufferFetch = true;
 | |
|   }
 | |
| 
 | |
|   // Our usage of primitive restart appears to be broken on AMD's binary drivers.
 | |
|   // Seems to be fine on GCN Gen 1-2, unconfirmed on GCN Gen 3, causes driver resets on GCN Gen 4.
 | |
|   if (DriverDetails::HasBug(DriverDetails::BUG_PRIMITIVE_RESTART))
 | |
|     backend_info->bSupportsPrimitiveRestart = false;
 | |
| 
 | |
|   // Reversed depth range is broken on some drivers, or is broken when used in combination
 | |
|   // with depth clamping. Fall back to inverted depth range for these.
 | |
|   if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_REVERSED_DEPTH_RANGE))
 | |
|     backend_info->bSupportsReversedDepthRange = false;
 | |
| 
 | |
|   // Dynamic sampler indexing locks up Intel GPUs on MoltenVK/Metal
 | |
|   if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING))
 | |
|     backend_info->bSupportsDynamicSamplerIndexing = false;
 | |
| }
 | |
| 
 | |
| void VulkanContext::PopulateBackendInfoMultisampleModes(BackendInfo* backend_info,
 | |
|                                                         VkPhysicalDevice gpu,
 | |
|                                                         const PhysicalDeviceInfo& info)
 | |
| {
 | |
|   // Query image support for the EFB texture formats.
 | |
|   VkImageFormatProperties efb_color_properties = {};
 | |
|   vkGetPhysicalDeviceImageFormatProperties(
 | |
|       gpu, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
 | |
|       VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 0, &efb_color_properties);
 | |
|   VkImageFormatProperties efb_depth_properties = {};
 | |
|   vkGetPhysicalDeviceImageFormatProperties(
 | |
|       gpu, VK_FORMAT_D32_SFLOAT, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
 | |
|       VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 0, &efb_depth_properties);
 | |
| 
 | |
|   // We can only support MSAA if it's supported on our render target formats.
 | |
|   VkSampleCountFlags supported_sample_counts =
 | |
|       info.framebufferColorSampleCounts & info.framebufferDepthSampleCounts &
 | |
|       efb_color_properties.sampleCounts & efb_depth_properties.sampleCounts;
 | |
| 
 | |
|   // No AA
 | |
|   backend_info->AAModes.clear();
 | |
|   backend_info->AAModes.emplace_back(1);
 | |
| 
 | |
|   // 2xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_2_BIT)
 | |
|     backend_info->AAModes.emplace_back(2);
 | |
| 
 | |
|   // 4xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_4_BIT)
 | |
|     backend_info->AAModes.emplace_back(4);
 | |
| 
 | |
|   // 8xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_8_BIT)
 | |
|     backend_info->AAModes.emplace_back(8);
 | |
| 
 | |
|   // 16xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_16_BIT)
 | |
|     backend_info->AAModes.emplace_back(16);
 | |
| 
 | |
|   // 32xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_32_BIT)
 | |
|     backend_info->AAModes.emplace_back(32);
 | |
| 
 | |
|   // 64xMSAA/SSAA
 | |
|   if (supported_sample_counts & VK_SAMPLE_COUNT_64_BIT)
 | |
|     backend_info->AAModes.emplace_back(64);
 | |
| }
 | |
| 
 | |
| std::unique_ptr<VulkanContext> VulkanContext::Create(VkInstance instance, VkPhysicalDevice gpu,
 | |
|                                                      VkSurfaceKHR surface, bool enable_debug_utils,
 | |
|                                                      bool enable_validation_layer,
 | |
|                                                      u32 vk_api_version)
 | |
| {
 | |
|   std::unique_ptr<VulkanContext> context = std::make_unique<VulkanContext>(instance, gpu);
 | |
| 
 | |
|   // Initialize DriverDetails so that we can check for bugs to disable features if needed.
 | |
|   context->InitDriverDetails();
 | |
| 
 | |
|   // Enable debug messages if the "Host GPU" log category is enabled.
 | |
|   if (enable_debug_utils)
 | |
|     context->EnableDebugUtils();
 | |
| 
 | |
|   // Attempt to create the device.
 | |
|   if (!context->CreateDevice(surface, enable_validation_layer) ||
 | |
|       !context->CreateAllocator(vk_api_version))
 | |
|   {
 | |
|     // Since we are destroying the instance, we're also responsible for destroying the surface.
 | |
|     if (surface != VK_NULL_HANDLE)
 | |
|       vkDestroySurfaceKHR(instance, surface, nullptr);
 | |
| 
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return context;
 | |
| }
 | |
| 
 | |
| bool VulkanContext::SelectDeviceExtensions(bool enable_surface)
 | |
| {
 | |
|   u32 extension_count = 0;
 | |
|   VkResult res =
 | |
|       vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, nullptr);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkEnumerateDeviceExtensionProperties failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (extension_count == 0)
 | |
|   {
 | |
|     ERROR_LOG_FMT(VIDEO, "Vulkan: No extensions supported by device.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::vector<VkExtensionProperties> available_extension_list(extension_count);
 | |
|   res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count,
 | |
|                                              available_extension_list.data());
 | |
|   ASSERT(res == VK_SUCCESS);
 | |
| 
 | |
|   for (const auto& extension_properties : available_extension_list)
 | |
|     INFO_LOG_FMT(VIDEO, "Available extension: {}", extension_properties.extensionName);
 | |
| 
 | |
|   auto AddExtension = [&](const char* name, bool required) {
 | |
|     if (Common::Contains(available_extension_list, std::string_view{name},
 | |
|                          &VkExtensionProperties::extensionName))
 | |
|     {
 | |
|       INFO_LOG_FMT(VIDEO, "Enabling extension: {}", name);
 | |
|       m_device_extensions.push_back(name);
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (required)
 | |
|       ERROR_LOG_FMT(VIDEO, "Vulkan: Missing required extension {}.", name);
 | |
| 
 | |
|     return false;
 | |
|   };
 | |
| 
 | |
|   if (enable_surface && !AddExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true))
 | |
|     return false;
 | |
| 
 | |
| #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
 | |
|   // VK_EXT_full_screen_exclusive
 | |
|   if (AddExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, true))
 | |
|     INFO_LOG_FMT(VIDEO, "Using VK_EXT_full_screen_exclusive for exclusive fullscreen.");
 | |
| #endif
 | |
| 
 | |
|   AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
 | |
|   AddExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false);
 | |
| 
 | |
|   if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DEPTH_CLAMP_CONTROL))
 | |
|   {
 | |
|     // Unrestricted depth range is one of the few extensions that changes the behavior
 | |
|     // of Vulkan just by being enabled, so we rely on lazy evaluation to ensure it is
 | |
|     // not enabled unless depth clamp control is supported.
 | |
|     g_backend_info.bSupportsUnrestrictedDepthRange =
 | |
|         AddExtension(VK_EXT_DEPTH_CLAMP_CONTROL_EXTENSION_NAME, false) &&
 | |
|         AddExtension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, false);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void VulkanContext::WarnMissingDeviceFeatures()
 | |
| {
 | |
|   if (!m_device_info.largePoints)
 | |
|     WARN_LOG_FMT(VIDEO, "Vulkan: Missing large points feature. CPU EFB writes will be slower.");
 | |
|   if (!m_device_info.occlusionQueryPrecise)
 | |
|   {
 | |
|     WARN_LOG_FMT(VIDEO,
 | |
|                  "Vulkan: Missing precise occlusion queries. Perf queries will be inaccurate.");
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool VulkanContext::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
 | |
| {
 | |
|   u32 queue_family_count;
 | |
|   vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr);
 | |
|   if (queue_family_count == 0)
 | |
|   {
 | |
|     ERROR_LOG_FMT(VIDEO, "No queue families found on specified vulkan physical device.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_count);
 | |
|   vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count,
 | |
|                                            queue_family_properties.data());
 | |
|   INFO_LOG_FMT(VIDEO, "{} vulkan queue families", queue_family_count);
 | |
| 
 | |
|   // Find graphics and present queues.
 | |
|   m_graphics_queue_family_index = queue_family_count;
 | |
|   m_present_queue_family_index = queue_family_count;
 | |
|   for (uint32_t i = 0; i < queue_family_count; i++)
 | |
|   {
 | |
|     VkBool32 graphics_supported = queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT;
 | |
|     if (graphics_supported)
 | |
|     {
 | |
|       m_graphics_queue_family_index = i;
 | |
|       // Quit now, no need for a present queue.
 | |
|       if (!surface)
 | |
|       {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (surface)
 | |
|     {
 | |
|       VkBool32 present_supported;
 | |
|       VkResult res =
 | |
|           vkGetPhysicalDeviceSurfaceSupportKHR(m_physical_device, i, surface, &present_supported);
 | |
|       if (res != VK_SUCCESS)
 | |
|       {
 | |
|         LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       if (present_supported)
 | |
|       {
 | |
|         m_present_queue_family_index = i;
 | |
|       }
 | |
| 
 | |
|       // Prefer one queue family index that does both graphics and present.
 | |
|       if (graphics_supported && present_supported)
 | |
|       {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (m_graphics_queue_family_index == queue_family_count)
 | |
|   {
 | |
|     ERROR_LOG_FMT(VIDEO, "Vulkan: Failed to find an acceptable graphics queue.");
 | |
|     return false;
 | |
|   }
 | |
|   if (surface && m_present_queue_family_index == queue_family_count)
 | |
|   {
 | |
|     ERROR_LOG_FMT(VIDEO, "Vulkan: Failed to find an acceptable present queue.");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   VkDeviceCreateInfo device_info = {};
 | |
|   device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
 | |
|   device_info.pNext = nullptr;
 | |
|   device_info.flags = 0;
 | |
| 
 | |
|   static constexpr float queue_priorities[] = {1.0f};
 | |
|   VkDeviceQueueCreateInfo graphics_queue_info = {};
 | |
|   graphics_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
 | |
|   graphics_queue_info.pNext = nullptr;
 | |
|   graphics_queue_info.flags = 0;
 | |
|   graphics_queue_info.queueFamilyIndex = m_graphics_queue_family_index;
 | |
|   graphics_queue_info.queueCount = 1;
 | |
|   graphics_queue_info.pQueuePriorities = queue_priorities;
 | |
| 
 | |
|   VkDeviceQueueCreateInfo present_queue_info = {};
 | |
|   present_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
 | |
|   present_queue_info.pNext = nullptr;
 | |
|   present_queue_info.flags = 0;
 | |
|   present_queue_info.queueFamilyIndex = m_present_queue_family_index;
 | |
|   present_queue_info.queueCount = 1;
 | |
|   present_queue_info.pQueuePriorities = queue_priorities;
 | |
| 
 | |
|   std::array<VkDeviceQueueCreateInfo, 2> queue_infos = {{
 | |
|       graphics_queue_info,
 | |
|       present_queue_info,
 | |
|   }};
 | |
| 
 | |
|   device_info.queueCreateInfoCount = 1;
 | |
|   if (m_graphics_queue_family_index != m_present_queue_family_index &&
 | |
|       m_present_queue_family_index != queue_family_count)
 | |
|   {
 | |
|     device_info.queueCreateInfoCount = 2;
 | |
|   }
 | |
|   device_info.pQueueCreateInfos = queue_infos.data();
 | |
| 
 | |
|   if (!SelectDeviceExtensions(surface != VK_NULL_HANDLE))
 | |
|     return false;
 | |
| 
 | |
|   // convert std::string list to a char pointer list which we can feed in
 | |
|   std::vector<const char*> extension_name_pointers;
 | |
|   for (const std::string& name : m_device_extensions)
 | |
|     extension_name_pointers.push_back(name.c_str());
 | |
| 
 | |
|   device_info.enabledLayerCount = 0;
 | |
|   device_info.ppEnabledLayerNames = nullptr;
 | |
|   device_info.enabledExtensionCount = static_cast<uint32_t>(extension_name_pointers.size());
 | |
|   device_info.ppEnabledExtensionNames = extension_name_pointers.data();
 | |
| 
 | |
|   WarnMissingDeviceFeatures();
 | |
| 
 | |
|   VkPhysicalDeviceFeatures device_features = m_device_info.features();
 | |
|   device_info.pEnabledFeatures = &device_features;
 | |
| 
 | |
|   // Enable debug layer on debug builds
 | |
|   if (enable_validation_layer)
 | |
|   {
 | |
|     device_info.enabledLayerCount = 1;
 | |
|     device_info.ppEnabledLayerNames = &VALIDATION_LAYER_NAME;
 | |
|   }
 | |
| 
 | |
|   VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkCreateDevice failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // With the device created, we can fill the remaining entry points.
 | |
|   if (!LoadVulkanDeviceFunctions(m_device))
 | |
|     return false;
 | |
| 
 | |
|   // Grab the graphics and present queues.
 | |
|   vkGetDeviceQueue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue);
 | |
|   if (surface)
 | |
|   {
 | |
|     vkGetDeviceQueue(m_device, m_present_queue_family_index, 0, &m_present_queue);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool VulkanContext::CreateAllocator(u32 vk_api_version)
 | |
| {
 | |
|   VmaAllocatorCreateInfo allocator_info = {};
 | |
|   allocator_info.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
 | |
|   allocator_info.physicalDevice = m_physical_device;
 | |
|   allocator_info.device = m_device;
 | |
|   allocator_info.preferredLargeHeapBlockSize = 64 << 20;
 | |
|   allocator_info.pAllocationCallbacks = nullptr;
 | |
|   allocator_info.pDeviceMemoryCallbacks = nullptr;
 | |
|   allocator_info.pHeapSizeLimit = nullptr;
 | |
|   allocator_info.pVulkanFunctions = nullptr;
 | |
|   allocator_info.instance = m_instance;
 | |
|   allocator_info.vulkanApiVersion = vk_api_version;
 | |
|   allocator_info.pTypeExternalMemoryHandleTypes = nullptr;
 | |
| 
 | |
|   if (SupportsDeviceExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME))
 | |
|     allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
 | |
| 
 | |
|   VkResult res = vmaCreateAllocator(&allocator_info, &m_allocator);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vmaCreateAllocator failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static VKAPI_ATTR VkBool32 VKAPI_CALL
 | |
| DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
 | |
|                    VkDebugUtilsMessageTypeFlagsEXT messageTypes,
 | |
|                    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
 | |
| {
 | |
|   const std::string log_message = fmt::format("Vulkan debug message: {}", pCallbackData->pMessage);
 | |
|   if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
 | |
|     ERROR_LOG_FMT(HOST_GPU, "{}", log_message);
 | |
|   else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
 | |
|     WARN_LOG_FMT(HOST_GPU, "{}", log_message);
 | |
|   else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)
 | |
|     INFO_LOG_FMT(HOST_GPU, "{}", log_message);
 | |
|   else
 | |
|     DEBUG_LOG_FMT(HOST_GPU, "{}", log_message);
 | |
| 
 | |
|   return VK_FALSE;
 | |
| }
 | |
| 
 | |
| bool VulkanContext::EnableDebugUtils()
 | |
| {
 | |
|   // Already enabled?
 | |
|   if (m_debug_utils_messenger != VK_NULL_HANDLE)
 | |
|     return true;
 | |
| 
 | |
|   // Check for presence of the functions before calling
 | |
|   if (!vkCreateDebugUtilsMessengerEXT || !vkDestroyDebugUtilsMessengerEXT ||
 | |
|       !vkSubmitDebugUtilsMessageEXT)
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   VkDebugUtilsMessengerCreateInfoEXT messenger_info = {
 | |
|       VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
 | |
|       nullptr,
 | |
|       0,
 | |
|       VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
 | |
|           VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
 | |
|           VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
 | |
|           VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
 | |
|       VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
 | |
|           VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
 | |
|       DebugUtilsCallback,
 | |
|       nullptr};
 | |
| 
 | |
|   VkResult res = vkCreateDebugUtilsMessengerEXT(m_instance, &messenger_info, nullptr,
 | |
|                                                 &m_debug_utils_messenger);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkCreateDebugUtilsMessengerEXT failed: ");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void VulkanContext::DisableDebugUtils()
 | |
| {
 | |
|   if (m_debug_utils_messenger != VK_NULL_HANDLE)
 | |
|   {
 | |
|     vkDestroyDebugUtilsMessengerEXT(m_instance, m_debug_utils_messenger, nullptr);
 | |
|     m_debug_utils_messenger = VK_NULL_HANDLE;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool VulkanContext::SupportsDeviceExtension(const char* name) const
 | |
| {
 | |
|   return std::ranges::any_of(m_device_extensions,
 | |
|                              [name](const std::string& extension) { return extension == name; });
 | |
| }
 | |
| 
 | |
| static bool DriverIsMesa(VkDriverId driver_id)
 | |
| {
 | |
|   switch (driver_id)
 | |
|   {
 | |
|   case VK_DRIVER_ID_MESA_RADV:
 | |
|   case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
 | |
|   case VK_DRIVER_ID_MESA_LLVMPIPE:
 | |
|   case VK_DRIVER_ID_MESA_TURNIP:
 | |
|   case VK_DRIVER_ID_MESA_V3DV:
 | |
|   case VK_DRIVER_ID_MESA_PANVK:
 | |
|   case VK_DRIVER_ID_MESA_VENUS:
 | |
|   case VK_DRIVER_ID_MESA_DOZEN:
 | |
|   case VK_DRIVER_ID_MESA_NVK:
 | |
|   case VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA:
 | |
|   case VK_DRIVER_ID_MESA_HONEYKRISP:
 | |
|     return true;
 | |
|   default:
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static DriverDetails::Driver GetMesaDriver(VkDriverId driver_id)
 | |
| {
 | |
|   switch (driver_id)
 | |
|   {
 | |
|     // clang-format off
 | |
|   case VK_DRIVER_ID_MESA_RADV:              return DriverDetails::DRIVER_R600;
 | |
|   case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA: return DriverDetails::DRIVER_I965;
 | |
|   case VK_DRIVER_ID_MESA_NVK:               return DriverDetails::DRIVER_NOUVEAU;
 | |
|   case VK_DRIVER_ID_MESA_TURNIP:            return DriverDetails::DRIVER_FREEDRENO;
 | |
|   default:                                  return DriverDetails::DRIVER_UNKNOWN;
 | |
|     // clang-format on
 | |
|   }
 | |
| }
 | |
| 
 | |
| void VulkanContext::InitDriverDetails()
 | |
| {
 | |
|   DriverDetails::Vendor vendor;
 | |
|   DriverDetails::Driver driver;
 | |
| 
 | |
|   // String comparisons aren't ideal, but there doesn't seem to be any other way to tell
 | |
|   // which vendor a driver is for. These names are based on the reports submitted to
 | |
|   // vulkan.gpuinfo.org, as of 19/09/2017.
 | |
|   std::string device_name = m_device_info.deviceName;
 | |
|   u32 vendor_id = m_device_info.vendorID;
 | |
|   // Note: driver_id may be 0 on vulkan < 1.2
 | |
|   VkDriverId driver_id = m_device_info.driverID;
 | |
| 
 | |
|   if (DriverIsMesa(driver_id))
 | |
|   {
 | |
|     vendor = DriverDetails::VENDOR_MESA;
 | |
|     driver = GetMesaDriver(driver_id);
 | |
|   }
 | |
|   else if (vendor_id == 0x10DE)
 | |
|   {
 | |
|     // Currently, there is only the official NV binary driver.
 | |
|     // "NVIDIA" does not appear in the device name.
 | |
|     vendor = DriverDetails::VENDOR_NVIDIA;
 | |
|     driver = DriverDetails::DRIVER_NVIDIA;
 | |
|   }
 | |
|   else if (vendor_id == 0x1002 || vendor_id == 0x1022 ||
 | |
|            device_name.find("AMD") != std::string::npos)
 | |
|   {
 | |
|     // RADV always advertises its name in the device string.
 | |
|     // If not RADV, assume the AMD binary driver.
 | |
|     if (device_name.find("RADV") != std::string::npos)
 | |
|     {
 | |
|       vendor = DriverDetails::VENDOR_MESA;
 | |
|       driver = DriverDetails::DRIVER_R600;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       vendor = DriverDetails::VENDOR_ATI;
 | |
|       driver = DriverDetails::DRIVER_ATI;
 | |
|     }
 | |
|   }
 | |
|   else if (vendor_id == 0x8086 || vendor_id == 0x8087 ||
 | |
|            device_name.find("Intel") != std::string::npos)
 | |
|   {
 | |
| // Apart from the driver version, Intel does not appear to provide a way to
 | |
| // differentiate between anv and the binary driver (Skylake+). Assume to be
 | |
| // using anv if we're not running on Windows or macOS.
 | |
| #if defined(WIN32) || defined(__APPLE__)
 | |
|     vendor = DriverDetails::VENDOR_INTEL;
 | |
|     driver = DriverDetails::DRIVER_INTEL;
 | |
| #else
 | |
|     vendor = DriverDetails::VENDOR_MESA;
 | |
|     driver = DriverDetails::DRIVER_I965;
 | |
| #endif
 | |
|   }
 | |
|   else if (vendor_id == 0x5143 || device_name.find("Adreno") != std::string::npos)
 | |
|   {
 | |
|     // Currently only the Qualcomm binary driver exists for Adreno.
 | |
|     vendor = DriverDetails::VENDOR_QUALCOMM;
 | |
|     driver = DriverDetails::DRIVER_QUALCOMM;
 | |
|   }
 | |
|   else if (vendor_id == 0x13B6 || device_name.find("Mali") != std::string::npos)
 | |
|   {
 | |
|     // Currently only the ARM binary driver exists for Mali.
 | |
|     vendor = DriverDetails::VENDOR_ARM;
 | |
|     driver = DriverDetails::DRIVER_ARM;
 | |
|   }
 | |
|   else if (vendor_id == 0x1010 || device_name.find("PowerVR") != std::string::npos)
 | |
|   {
 | |
|     // Currently only the binary driver exists for PowerVR.
 | |
|     vendor = DriverDetails::VENDOR_IMGTEC;
 | |
|     driver = DriverDetails::DRIVER_IMGTEC;
 | |
|   }
 | |
|   else if (device_name.find("Apple") != std::string::npos)
 | |
|   {
 | |
|     vendor = DriverDetails::VENDOR_APPLE;
 | |
|     driver = DriverDetails::DRIVER_PORTABILITY;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     WARN_LOG_FMT(VIDEO, "Unknown Vulkan driver vendor, please report it to us.");
 | |
|     WARN_LOG_FMT(VIDEO, "Vendor ID: {:#X}, Device Name: {}", vendor_id, device_name);
 | |
|     vendor = DriverDetails::VENDOR_UNKNOWN;
 | |
|     driver = DriverDetails::DRIVER_UNKNOWN;
 | |
|   }
 | |
| 
 | |
|   // Vulkan on macOS goes through Metal, and is not susceptible to the same bugs
 | |
|   // as the vendor's native Vulkan drivers. We use a different driver fields to
 | |
|   // differentiate MoltenVK.
 | |
|   if (driver_id == VK_DRIVER_ID_MOLTENVK)
 | |
|     driver = DriverDetails::DRIVER_PORTABILITY;
 | |
| 
 | |
|   DriverDetails::Init(DriverDetails::API_VULKAN, vendor, driver,
 | |
|                       static_cast<double>(m_device_info.driverVersion),
 | |
|                       DriverDetails::Family::UNKNOWN, std::move(device_name));
 | |
| }
 | |
| 
 | |
| bool VulkanContext::SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface)
 | |
| {
 | |
| #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN
 | |
|   if (!surface || !vkGetPhysicalDeviceSurfaceCapabilities2KHR ||
 | |
|       !SupportsDeviceExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME))
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   VkPhysicalDeviceSurfaceInfo2KHR si = {};
 | |
|   si.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR;
 | |
|   si.surface = surface;
 | |
| 
 | |
|   auto platform_info = GetPlatformExclusiveFullscreenInfo(wsi);
 | |
|   si.pNext = &platform_info;
 | |
| 
 | |
|   VkSurfaceCapabilities2KHR caps = {};
 | |
|   caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR;
 | |
| 
 | |
|   VkSurfaceCapabilitiesFullScreenExclusiveEXT fullscreen_caps = {};
 | |
|   fullscreen_caps.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT;
 | |
|   fullscreen_caps.fullScreenExclusiveSupported = VK_TRUE;
 | |
|   caps.pNext = &fullscreen_caps;
 | |
| 
 | |
|   VkResult res = vkGetPhysicalDeviceSurfaceCapabilities2KHR(m_physical_device, &si, &caps);
 | |
|   if (res != VK_SUCCESS)
 | |
|   {
 | |
|     LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilities2KHR failed:");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return fullscreen_caps.fullScreenExclusiveSupported;
 | |
| #else
 | |
|   return false;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #ifdef WIN32
 | |
| VkSurfaceFullScreenExclusiveWin32InfoEXT
 | |
| VulkanContext::GetPlatformExclusiveFullscreenInfo(const WindowSystemInfo& wsi)
 | |
| {
 | |
|   VkSurfaceFullScreenExclusiveWin32InfoEXT info = {};
 | |
|   info.sType = VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT;
 | |
|   info.hmonitor =
 | |
|       MonitorFromWindow(static_cast<HWND>(wsi.render_surface), MONITOR_DEFAULTTOPRIMARY);
 | |
|   return info;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| }  // namespace Vulkan
 |