Files

1618 lines
63 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)
{
// Feature/property structs for EXT extensions may only be chained when the device
// advertises the extension.
bool has_fdm_extension = false;
{
u32 ext_count = 0;
if (vkEnumerateDeviceExtensionProperties(device, nullptr, &ext_count, nullptr) ==
VK_SUCCESS &&
ext_count > 0)
{
std::vector<VkExtensionProperties> exts(ext_count);
if (vkEnumerateDeviceExtensionProperties(device, nullptr, &ext_count, exts.data()) ==
VK_SUCCESS)
{
has_fdm_extension =
Common::Contains(exts, std::string_view{VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME},
&VkExtensionProperties::extensionName);
}
}
}
VkPhysicalDeviceSubgroupProperties properties_subgroup = {};
VkPhysicalDeviceVulkan12Properties properties_vk12 = {};
VkPhysicalDeviceMultiviewProperties properties_multiview = {};
VkPhysicalDeviceFragmentDensityMapPropertiesEXT properties_fdm = {};
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);
properties_multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
InsertIntoChain(&properties2, &properties_multiview);
if (has_fdm_extension)
{
properties_fdm.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_PROPERTIES_EXT;
InsertIntoChain(&properties2, &properties_fdm);
}
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 (has_fdm_extension)
{
minFragmentDensityTexelSize = properties_fdm.minFragmentDensityTexelSize;
maxFragmentDensityTexelSize = properties_fdm.maxFragmentDensityTexelSize;
}
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;
maxMultiviewViewCount = properties_multiview.maxMultiviewViewCount;
maxMultiviewInstanceIndex = properties_multiview.maxMultiviewInstanceIndex;
VkPhysicalDeviceFeatures2 features2 = {};
VkPhysicalDeviceMultiviewFeatures features_multiview = {};
VkPhysicalDeviceTimelineSemaphoreFeatures features_timeline_semaphore = {};
VkPhysicalDeviceFragmentDensityMapFeaturesEXT features_fdm = {};
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features_multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
InsertIntoChain(&features2, &features_multiview);
if (apiVersion >= VK_API_VERSION_1_2)
{
features_timeline_semaphore.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES;
InsertIntoChain(&features2, &features_timeline_semaphore);
}
if (has_fdm_extension)
{
features_fdm.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT;
InsertIntoChain(&features2, &features_fdm);
}
vkGetPhysicalDeviceFeatures2(device, &features2);
multiview = features_multiview.multiview != VK_FALSE && maxMultiviewViewCount >= 2;
timelineSemaphore = features_timeline_semaphore.timelineSemaphore != VK_FALSE;
fragmentDensityMap = features_fdm.fragmentDensityMap != VK_FALSE;
fragmentDensityMapNonSubsampled =
features_fdm.fragmentDensityMapNonSubsampledImages != VK_FALSE;
}
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;
maxFragmentOutputAttachments = properties.limits.maxFragmentOutputAttachments;
maxFragmentDualSrcAttachments = properties.limits.maxFragmentDualSrcAttachments;
framebufferColorSampleCounts = properties.limits.framebufferColorSampleCounts;
framebufferDepthSampleCounts = properties.limits.framebufferDepthSampleCounts;
memcpy(pointSizeRange, properties.limits.pointSizeRange, sizeof(pointSizeRange));
maxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy;
dualSrcBlend = features.dualSrcBlend != VK_FALSE;
independentBlend = features.independentBlend != 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.independentBlend = independentBlend ? 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);
DisableAddressBindingReport();
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 max_api_version = 0)
{
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;
if (max_api_version != 0 && used_version > max_api_version)
used_version = max_api_version;
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, const std::vector<std::string>& extra_instance_extensions,
u32 max_api_version)
{
std::vector<const char*> enabled_extensions;
if (!SelectInstanceExtensions(&enabled_extensions, wstype, enable_debug_utils,
enable_validation_layer, extra_instance_extensions))
return VK_NULL_HANDLE;
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pNext = nullptr;
#ifdef BAIJI_STEAMVR
app_info.pApplicationName = "BaijiSteamVR";
#else
app_info.pApplicationName = "Dolphin Emulator";
#endif
app_info.applicationVersion = VK_MAKE_VERSION(5, 0, 0);
#ifdef BAIJI_STEAMVR
app_info.pEngineName = "BaijiSteamVR";
#else
app_info.pEngineName = "Dolphin Emulator";
#endif
app_info.engineVersion = VK_MAKE_VERSION(5, 0, 0);
app_info.apiVersion = getAPIVersion(max_api_version);
*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 = VK_NULL_HANDLE;
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, const std::vector<std::string>& extra_extensions)
{
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.");
}
// Add any extra extensions requested by external systems (e.g. OpenXR).
for (const auto& ext : extra_extensions)
{
if (!AddExtension(ext.c_str(), false))
{
WARN_LOG_FMT(VIDEO, "Vulkan: Requested extra instance extension {} not available.", ext);
}
}
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;
backend_info->bSupportsMultiview = info.multiview;
// EFB foveation renders the (non-subsampled) EFB through a fragment density map, and
// is only wired for the multiview EFB path.
backend_info->bSupportsVRFoveatedEFB =
info.multiview && info.fragmentDensityMap && info.fragmentDensityMapNonSubsampled;
backend_info->max_fragment_dual_src_attachments = info.maxFragmentDualSrcAttachments;
#if defined(_WIN32) || defined(ANDROID)
// Windows desktop and Quest standalone (Adreno). Not MoltenVK — its framebuffer-fetch
// blending path bypasses the per-attachment blend states the coverage MRT relies on.
VkImageFormatProperties r8_properties = {};
const VkResult r8_result = vkGetPhysicalDeviceImageFormatProperties(
gpu, VK_FORMAT_R8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
0, &r8_properties);
backend_info->vr_passthrough_coverage_sample_counts =
r8_result == VK_SUCCESS ? r8_properties.sampleCounts : 0;
backend_info->bSupportsVRPassthroughCoverage =
info.independentBlend && info.maxFragmentOutputAttachments >= 2 &&
backend_info->vr_passthrough_coverage_sample_counts != 0;
INFO_LOG_FMT(VIDEO,
"VR passthrough coverage support: {} (independentBlend={} "
"maxFragmentOutputAttachments={} r8SampleCounts={:#x} r8Result={})",
backend_info->bSupportsVRPassthroughCoverage, static_cast<bool>(info.independentBlend),
info.maxFragmentOutputAttachments,
backend_info->vr_passthrough_coverage_sample_counts, static_cast<int>(r8_result));
#else
backend_info->bSupportsVRPassthroughCoverage = false;
backend_info->vr_passthrough_coverage_sample_counts = 0;
#endif
// 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,
const std::vector<std::string>& extra_device_extensions)
{
std::unique_ptr<VulkanContext> context = std::make_unique<VulkanContext>(instance, gpu);
context->m_extra_device_extensions = extra_device_extensions;
// 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;
}
// Records a ring buffer entry per GPU allocation, so only run it when GPU debugging is on.
// Without it a device fault still reports its address, just not the owning resource.
if (enable_debug_utils)
context->EnableAddressBindingReport();
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);
#ifdef ENABLE_VR
// VR foveation: XR_FB_foveation_vulkan hands us runtime-owned fragment density maps
// that our render passes read via VK_EXT_fragment_density_map (Quest-class devices).
AddExtension(VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME, false);
// Diagnostic data for the restart-only OpenXR device loss. This is inert until a device fault
// occurs, at which point it can expose NVIDIA fault addresses and vendor fault codes.
AddExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, false);
// Reports every GPU virtual-address bind/unbind so a device fault address can be traced back to
// the resource that owned it. Diagnostic companion to VK_EXT_device_fault.
AddExtension(VK_EXT_DEVICE_ADDRESS_BINDING_REPORT_EXTENSION_NAME, false);
#endif
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);
}
// Add any extra extensions requested by external systems (e.g. OpenXR).
for (const auto& ext : m_extra_device_extensions)
{
if (!AddExtension(ext.c_str(), false))
{
WARN_LOG_FMT(VIDEO, "Vulkan: Requested extra device extension {} not available.", ext);
}
}
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();
// When the device exposes Vulkan 1.1+ and supports multiview, enable it via a
// pNext chain so the VS can use gl_ViewIndex for OpenXR stereo. Otherwise fall
// back to the legacy pEnabledFeatures pointer.
VkPhysicalDeviceFeatures2 features2 = {};
VkPhysicalDeviceMultiviewFeatures multiview_features = {};
VkPhysicalDeviceTimelineSemaphoreFeatures timeline_semaphore_features = {};
VkPhysicalDeviceFaultFeaturesEXT device_fault_features = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT};
VkPhysicalDeviceAddressBindingReportFeaturesEXT address_binding_features = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ADDRESS_BINDING_REPORT_FEATURES_EXT};
const bool use_multiview =
m_device_info.apiVersion >= VK_API_VERSION_1_1 && m_device_info.multiview;
// OpenXR runtimes (e.g. Virtual Desktop) that list VK_KHR_timeline_semaphore as a
// required device extension create timeline semaphores on this device; the extension
// alone is not enough — the timelineSemaphore feature must also be enabled, or every
// runtime wait/signal on the semaphore is undefined behavior (device-lost in practice).
const bool use_timeline_semaphore =
m_device_info.timelineSemaphore &&
Common::Contains(m_device_extensions, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME);
// VR foveation: enable the fragmentDensityMap feature when the extension made it into
// the device extension list and the device supports it. nonSubsampledImages is what
// lets a fragment density map drive render passes over ordinary images (the EFB);
// without it only the runtime's subsampled swapchain images can be foveated.
VkPhysicalDeviceFragmentDensityMapFeaturesEXT fdm_features = {};
const bool use_fragment_density_map =
m_device_info.fragmentDensityMap && m_device_info.apiVersion >= VK_API_VERSION_1_1 &&
Common::Contains(m_device_extensions, VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME);
bool use_device_fault = false;
if (m_device_info.apiVersion >= VK_API_VERSION_1_1 && vkGetPhysicalDeviceFeatures2 &&
Common::Contains(m_device_extensions, VK_EXT_DEVICE_FAULT_EXTENSION_NAME))
{
VkPhysicalDeviceFeatures2 fault_query{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
fault_query.pNext = &device_fault_features;
vkGetPhysicalDeviceFeatures2(m_physical_device, &fault_query);
use_device_fault = device_fault_features.deviceFault == VK_TRUE;
device_fault_features.deviceFault = use_device_fault ? VK_TRUE : VK_FALSE;
device_fault_features.deviceFaultVendorBinary = VK_FALSE;
}
bool use_address_binding = false;
if (m_device_info.apiVersion >= VK_API_VERSION_1_1 && vkGetPhysicalDeviceFeatures2 &&
Common::Contains(m_device_extensions, VK_EXT_DEVICE_ADDRESS_BINDING_REPORT_EXTENSION_NAME))
{
VkPhysicalDeviceFeatures2 binding_query{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
binding_query.pNext = &address_binding_features;
vkGetPhysicalDeviceFeatures2(m_physical_device, &binding_query);
use_address_binding = address_binding_features.reportAddressBinding == VK_TRUE;
address_binding_features.reportAddressBinding = use_address_binding ? VK_TRUE : VK_FALSE;
}
if (use_fragment_density_map)
{
fdm_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT;
fdm_features.fragmentDensityMap = VK_TRUE;
fdm_features.fragmentDensityMapDynamic = VK_FALSE;
fdm_features.fragmentDensityMapNonSubsampledImages =
m_device_info.fragmentDensityMapNonSubsampled ? VK_TRUE : VK_FALSE;
}
if (use_multiview || use_timeline_semaphore || use_fragment_density_map || use_device_fault ||
use_address_binding)
{
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features2.features = device_features;
if (use_multiview)
{
multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
multiview_features.multiview = VK_TRUE;
InsertIntoChain(&features2, &multiview_features);
m_multiview_enabled = true;
}
if (use_timeline_semaphore)
{
timeline_semaphore_features.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES;
timeline_semaphore_features.timelineSemaphore = VK_TRUE;
InsertIntoChain(&features2, &timeline_semaphore_features);
m_timeline_semaphore_enabled = true;
INFO_LOG_FMT(VIDEO, "Vulkan: Enabling timelineSemaphore feature for the OpenXR runtime.");
}
if (use_fragment_density_map)
{
InsertIntoChain(&features2, &fdm_features);
m_fragment_density_map_enabled = true;
m_fdm_non_subsampled_enabled = fdm_features.fragmentDensityMapNonSubsampledImages == VK_TRUE;
INFO_LOG_FMT(VIDEO, "Vulkan: Enabling fragmentDensityMap feature for VR foveation "
"(nonSubsampledImages: {}).",
m_fdm_non_subsampled_enabled ? "yes - EFB foveation available" :
"no - swapchain foveation only");
}
if (use_device_fault)
{
InsertIntoChain(&features2, &device_fault_features);
m_device_fault_enabled = true;
INFO_LOG_FMT(VIDEO, "Vulkan: Enabling VK_EXT_device_fault diagnostics.");
}
if (use_address_binding)
{
InsertIntoChain(&features2, &address_binding_features);
m_address_binding_report_enabled = true;
INFO_LOG_FMT(VIDEO, "Vulkan: Enabling VK_EXT_device_address_binding_report diagnostics.");
}
device_info.pNext = &features2;
device_info.pEnabledFeatures = nullptr;
}
else
{
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;
}
void VulkanContext::LogDeviceFaultInfo() const
{
if (!m_device_fault_enabled || m_device == VK_NULL_HANDLE)
{
ERROR_LOG_FMT(VIDEO, "Vulkan device fault diagnostics were not available on this device.");
return;
}
const auto get_fault_info = reinterpret_cast<PFN_vkGetDeviceFaultInfoEXT>(
vkGetDeviceProcAddr(m_device, "vkGetDeviceFaultInfoEXT"));
if (!get_fault_info)
{
ERROR_LOG_FMT(VIDEO, "Vulkan device fault function could not be loaded.");
return;
}
VkDeviceFaultCountsEXT counts{VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT};
VkResult result = get_fault_info(m_device, &counts, nullptr);
if (result != VK_SUCCESS)
{
ERROR_LOG_FMT(VIDEO, "Vulkan device fault count query failed: {} ({}).",
VkResultToString(result), static_cast<int>(result));
return;
}
ERROR_LOG_FMT(VIDEO,
"Vulkan device fault counts: addresses={} vendor_records={} vendor_binary={} bytes.",
counts.addressInfoCount, counts.vendorInfoCount,
static_cast<unsigned long long>(counts.vendorBinarySize));
std::vector<VkDeviceFaultAddressInfoEXT> addresses(counts.addressInfoCount);
std::vector<VkDeviceFaultVendorInfoEXT> vendor_info(counts.vendorInfoCount);
// Do not request the vendor binary blob on the second call. NVIDIA's driver writes
// vendorBinarySize bytes to pVendorBinaryData even when it is null, so leaving the size at the
// value reported by the count query crashes the process before the fault address is logged. We
// only need the address records, so zero the size here.
counts.vendorBinarySize = 0;
VkDeviceFaultInfoEXT info{VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT};
info.pAddressInfos = addresses.empty() ? nullptr : addresses.data();
info.pVendorInfos = vendor_info.empty() ? nullptr : vendor_info.data();
info.pVendorBinaryData = nullptr;
result = get_fault_info(m_device, &counts, &info);
if (result != VK_SUCCESS && result != VK_INCOMPLETE)
{
ERROR_LOG_FMT(VIDEO, "Vulkan device fault detail query failed: {} ({}).",
VkResultToString(result), static_cast<int>(result));
return;
}
const auto address_type_name = [](VkDeviceFaultAddressTypeEXT type) -> const char* {
switch (type)
{
case VK_DEVICE_FAULT_ADDRESS_TYPE_READ_INVALID_EXT:
return "read-invalid";
case VK_DEVICE_FAULT_ADDRESS_TYPE_WRITE_INVALID_EXT:
return "write-invalid";
case VK_DEVICE_FAULT_ADDRESS_TYPE_EXECUTE_INVALID_EXT:
return "execute-invalid";
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_UNKNOWN_EXT:
return "instruction-pointer-unknown";
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_INVALID_EXT:
return "instruction-pointer-invalid";
case VK_DEVICE_FAULT_ADDRESS_TYPE_INSTRUCTION_POINTER_FAULT_EXT:
return "instruction-pointer-fault";
default:
return "none";
}
};
ERROR_LOG_FMT(VIDEO, "Vulkan device fault description: {}", info.description);
for (size_t i = 0; i < addresses.size(); ++i)
{
const auto& address = addresses[i];
ERROR_LOG_FMT(VIDEO,
"Vulkan device fault address[{}]: type={} ({}) address={:#018x} precision={:#x}.",
i, static_cast<int>(address.addressType), address_type_name(address.addressType),
static_cast<unsigned long long>(address.reportedAddress),
static_cast<unsigned long long>(address.addressPrecision));
LogAddressBindingsForFault(address.reportedAddress);
}
for (size_t i = 0; i < vendor_info.size(); ++i)
{
const auto& vendor = vendor_info[i];
ERROR_LOG_FMT(VIDEO,
"Vulkan device fault vendor[{}]: code={:#018x} data={:#018x} description={}.",
i, static_cast<unsigned long long>(vendor.vendorFaultCode),
static_cast<unsigned long long>(vendor.vendorFaultData), vendor.description);
}
}
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;
}
}
namespace
{
constexpr size_t ADDRESS_BINDING_LOG_CAPACITY = 16384;
VKAPI_ATTR VkBool32 VKAPI_CALL AddressBindingCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
{
if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT) == 0)
return VK_FALSE;
auto* context = static_cast<VulkanContext*>(pUserData);
if (context == nullptr || pCallbackData == nullptr)
return VK_FALSE;
const VkDeviceAddressBindingCallbackDataEXT* binding = nullptr;
for (const auto* next = static_cast<const VkBaseInStructure*>(pCallbackData->pNext);
next != nullptr; next = next->pNext)
{
if (next->sType == VK_STRUCTURE_TYPE_DEVICE_ADDRESS_BINDING_CALLBACK_DATA_EXT)
{
binding = reinterpret_cast<const VkDeviceAddressBindingCallbackDataEXT*>(next);
break;
}
}
if (binding == nullptr)
return VK_FALSE;
int object_type = VK_OBJECT_TYPE_UNKNOWN;
u64 object_handle = 0;
if (pCallbackData->objectCount > 0 && pCallbackData->pObjects != nullptr)
{
object_type = static_cast<int>(pCallbackData->pObjects[0].objectType);
object_handle = pCallbackData->pObjects[0].objectHandle;
}
context->RecordAddressBinding(
static_cast<u64>(binding->baseAddress), static_cast<u64>(binding->size),
static_cast<u32>(binding->bindingType), static_cast<u32>(binding->flags), object_type,
object_handle);
return VK_FALSE;
}
} // namespace
bool VulkanContext::EnableAddressBindingReport()
{
if (!m_address_binding_report_enabled)
return false;
if (m_address_binding_messenger != VK_NULL_HANDLE)
return true;
if (!vkCreateDebugUtilsMessengerEXT || !vkDestroyDebugUtilsMessengerEXT)
return false;
{
std::lock_guard<std::mutex> lock(m_address_binding_mutex);
m_address_binding_events.assign(ADDRESS_BINDING_LOG_CAPACITY, AddressBindingEvent{});
m_address_binding_write = 0;
}
VkDebugUtilsMessengerCreateInfoEXT messenger_info = {
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
messenger_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
messenger_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT;
messenger_info.pfnUserCallback = AddressBindingCallback;
messenger_info.pUserData = this;
const VkResult res = vkCreateDebugUtilsMessengerEXT(m_instance, &messenger_info, nullptr,
&m_address_binding_messenger);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateDebugUtilsMessengerEXT (address binding) failed: ");
m_address_binding_messenger = VK_NULL_HANDLE;
return false;
}
INFO_LOG_FMT(VIDEO, "Vulkan: VK_EXT_device_address_binding_report capture active.");
return true;
}
void VulkanContext::DisableAddressBindingReport()
{
if (m_address_binding_messenger != VK_NULL_HANDLE)
{
vkDestroyDebugUtilsMessengerEXT(m_instance, m_address_binding_messenger, nullptr);
m_address_binding_messenger = VK_NULL_HANDLE;
}
}
void VulkanContext::RecordAddressBinding(u64 base, u64 size, u32 binding_type, u32 flags,
int object_type, u64 object_handle)
{
const u64 seq = m_address_binding_seq.fetch_add(1, std::memory_order_relaxed) + 1;
std::lock_guard<std::mutex> lock(m_address_binding_mutex);
if (m_address_binding_events.empty())
return;
AddressBindingEvent& slot = m_address_binding_events[m_address_binding_write];
slot.base = base;
slot.size = size;
slot.seq = seq;
slot.object_handle = object_handle;
slot.binding_type = binding_type;
slot.flags = flags;
slot.object_type = object_type;
m_address_binding_write = (m_address_binding_write + 1) % m_address_binding_events.size();
}
void VulkanContext::LogAddressBindingsForFault(u64 fault_address) const
{
// Only meaningful when the capture messenger actually ran (Host GPU logging enabled).
if (!m_address_binding_report_enabled || m_address_binding_messenger == VK_NULL_HANDLE)
return;
std::lock_guard<std::mutex> lock(m_address_binding_mutex);
std::vector<const AddressBindingEvent*> matches;
for (const auto& event : m_address_binding_events)
{
if (event.seq == 0 || event.size == 0)
continue;
if (fault_address >= event.base && fault_address < event.base + event.size)
matches.push_back(&event);
}
if (matches.empty())
{
ERROR_LOG_FMT(VIDEO,
" No recorded GPU address binding covers {:#018x}. The faulting resource "
"belonged to a previous device/session or to the runtime/compositor.",
static_cast<unsigned long long>(fault_address));
return;
}
std::sort(matches.begin(), matches.end(),
[](const AddressBindingEvent* a, const AddressBindingEvent* b) {
return a->seq < b->seq;
});
for (const auto* event : matches)
{
ERROR_LOG_FMT(
VIDEO,
" GPU address binding seq={} {} range=[{:#018x}, {:#018x}) object_type={} handle={:#018x}{}.",
event->seq,
event->binding_type == VK_DEVICE_ADDRESS_BINDING_TYPE_UNBIND_EXT ? "UNBIND" : "BIND",
static_cast<unsigned long long>(event->base),
static_cast<unsigned long long>(event->base + event->size), event->object_type,
static_cast<unsigned long long>(event->object_handle),
(event->flags & VK_DEVICE_ADDRESS_BINDING_INTERNAL_OBJECT_BIT_EXT) ? " internal" : "");
}
}
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