[Vulkan] Refactoring and fixes for VulkanProvider and related areas

Enable portability subset physical device enumeration.

Don't use Vulkan 1.1+ logical devices on Vulkan 1.0 instances due to the
VkApplicationInfo::apiVersion specification.

Make sure all extension dependencies are enabled when creating a device.

Prefer exposing feature support over extension support via the device
interface to avoid causing confusion with regard to promoted extensions
(especially those that required some features as extensions, but had those
features made optional when they were promoted).

Allow creating presentation-only devices, not demanding any optional
features beyond the basic Vulkan 1.0, for use cases such as internal tools
or CPU rendering.

Require the independentBlend feature for GPU emulation as working around is
complicated, while support is almost ubiquitous.

Move the graphics system initialization fatal error message to xenia_main
after attempting to initialize all implementations, for automatic fallback
to other implementations in the future.

Log Vulkan driver info.

Improve Vulkan debug message logging, enabled by default.

Refactor code, with simplified logic for enabling extensions and layers.
This commit is contained in:
Triang3l
2025-08-12 23:21:59 +03:00
parent a06be03f1b
commit b5432ab83f
70 changed files with 3238 additions and 2757 deletions

View File

@@ -0,0 +1,667 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/vulkan/vulkan_instance.h"
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/ui/vulkan/vulkan_presenter.h"
#if XE_PLATFORM_LINUX
#include <dlfcn.h>
#elif XE_PLATFORM_WIN32
#include "xenia/base/platform_win.h"
#endif
DEFINE_bool(
vulkan_log_debug_messages, true,
"Write Vulkan VK_EXT_debug_utils messages to the Xenia log, as opposed to "
"the OS debug output.",
"Vulkan");
namespace xe {
namespace ui {
namespace vulkan {
std::unique_ptr<VulkanInstance> VulkanInstance::Create(
const bool with_surface, const bool try_enable_validation) {
std::unique_ptr<VulkanInstance> vulkan_instance(new VulkanInstance());
// Load the RenderDoc API if connected.
vulkan_instance->renderdoc_api_ = RenderDocAPI::CreateIfConnected();
// Load the loader library.
Functions& ifn = vulkan_instance->functions_;
bool functions_loaded = true;
#if XE_PLATFORM_LINUX
#if XE_PLATFORM_ANDROID
const char* const loader_library_name = "libvulkan.so";
#else
const char* const loader_library_name = "libvulkan.so.1";
#endif
// http://developer.download.nvidia.com/mobile/shield/assets/Vulkan/UsingtheVulkanAPI.pdf
vulkan_instance->loader_ = dlopen(loader_library_name, RTLD_NOW | RTLD_LOCAL);
if (!vulkan_instance->loader_) {
XELOGE("Failed to load {}", loader_library_name);
return false;
}
#define XE_VULKAN_LOAD_LOADER_FUNCTION(name) \
functions_loaded &= \
(ifn.name = PFN_##name(dlsym(vulkan_instance->loader_, #name))) != \
nullptr;
#elif XE_PLATFORM_WIN32
vulkan_instance->loader_ = LoadLibraryW(L"vulkan-1.dll");
if (!vulkan_instance->loader_) {
XELOGE("Failed to load vulkan-1.dll");
return false;
}
#define XE_VULKAN_LOAD_LOADER_FUNCTION(name) \
functions_loaded &= (ifn.name = PFN_##name(GetProcAddress( \
vulkan_instance->loader_, #name))) != nullptr;
#else
#error No Vulkan loader library loading provided for the target platform.
#endif
XE_VULKAN_LOAD_LOADER_FUNCTION(vkGetInstanceProcAddr);
XE_VULKAN_LOAD_LOADER_FUNCTION(vkDestroyInstance);
#undef XE_VULKAN_LOAD_LOADER_FUNCTION
if (!functions_loaded) {
XELOGE("Failed to get Vulkan loader function pointers");
return nullptr;
}
// Load global functions.
functions_loaded &=
(ifn.vkCreateInstance = PFN_vkCreateInstance(
ifn.vkGetInstanceProcAddr(nullptr, "vkCreateInstance"))) != nullptr;
functions_loaded &=
(ifn.vkEnumerateInstanceExtensionProperties =
PFN_vkEnumerateInstanceExtensionProperties(ifn.vkGetInstanceProcAddr(
nullptr, "vkEnumerateInstanceExtensionProperties"))) != nullptr;
functions_loaded &=
(ifn.vkEnumerateInstanceLayerProperties =
PFN_vkEnumerateInstanceLayerProperties(ifn.vkGetInstanceProcAddr(
nullptr, "vkEnumerateInstanceLayerProperties"))) != nullptr;
if (!functions_loaded) {
XELOGE(
"Failed to get Vulkan global function pointers via "
"vkGetInstanceProcAddr");
return nullptr;
}
// Available since Vulkan 1.1. If this is nullptr, it's a Vulkan 1.0 instance.
ifn.vkEnumerateInstanceVersion = PFN_vkEnumerateInstanceVersion(
ifn.vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion"));
// Get the API version.
if (ifn.vkEnumerateInstanceVersion) {
ifn.vkEnumerateInstanceVersion(&vulkan_instance->api_version_);
}
// Enable extensions and layers.
// Name pointers from `requested_extensions` will be used in the enabled
// extensions vector.
std::unordered_map<std::string, bool*> requested_extensions;
if (vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
vulkan_instance->extensions_.ext_1_1_KHR_get_physical_device_properties2 =
true;
} else {
// #60.
requested_extensions.emplace(
"VK_KHR_get_physical_device_properties2",
&vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2);
}
// #129.
requested_extensions.emplace(
"VK_EXT_debug_utils", &vulkan_instance->extensions_.ext_EXT_debug_utils);
// #395.
requested_extensions.emplace(
"VK_KHR_portability_enumeration",
&vulkan_instance->extensions_.ext_KHR_portability_enumeration);
if (with_surface) {
// #1.
requested_extensions.emplace("VK_KHR_surface",
&vulkan_instance->extensions_.ext_KHR_surface);
#ifdef VK_USE_PLATFORM_XCB_KHR
// #6.
requested_extensions.emplace(
"VK_KHR_xcb_surface",
&vulkan_instance->extensions_.ext_KHR_xcb_surface);
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
// #9.
requested_extensions.emplace(
"VK_KHR_android_surface",
&vulkan_instance->extensions_.ext_KHR_android_surface);
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
// #10.
requested_extensions.emplace(
"VK_KHR_win32_surface",
&vulkan_instance->extensions_.ext_KHR_win32_surface);
#endif
}
std::vector<const char*> enabled_extensions;
std::vector<VkExtensionProperties> supported_implementation_extensions;
while (true) {
uint32_t supported_implementation_extension_count = 0;
const VkResult get_supported_implementation_extension_count_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_implementation_extension_count, nullptr);
if (get_supported_implementation_extension_count_result != VK_SUCCESS &&
get_supported_implementation_extension_count_result != VK_INCOMPLETE) {
XELOGW("Failed to get the Vulkan instance extension count");
return nullptr;
}
if (supported_implementation_extension_count) {
supported_implementation_extensions.resize(
supported_implementation_extension_count);
const VkResult get_supported_implementation_extensions_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_implementation_extension_count,
supported_implementation_extensions.data());
if (get_supported_implementation_extensions_result == VK_INCOMPLETE) {
continue;
}
if (get_supported_implementation_extensions_result != VK_SUCCESS) {
XELOGW("Failed to get the Vulkan instance extensions");
return nullptr;
}
}
supported_implementation_extensions.resize(
supported_implementation_extension_count);
break;
}
for (const VkExtensionProperties& supported_extension :
supported_implementation_extensions) {
const auto requested_extension_it =
requested_extensions.find(supported_extension.extensionName);
if (requested_extension_it == requested_extensions.cend()) {
continue;
}
assert_not_null(requested_extension_it->second);
if (!*requested_extension_it->second) {
enabled_extensions.emplace_back(requested_extension_it->first.c_str());
*requested_extension_it->second = true;
}
}
// If enabled layers are not present, will disable all extensions provided by
// the layers by truncating the enabled extension vector to this size.
const size_t enabled_implementation_extension_count =
enabled_extensions.size();
std::vector<bool*> enabled_layer_extension_enablement_bools;
// Name pointers from `requested_layers` will be used in the enabled layer
// vector.
std::unordered_map<std::string, bool*> requested_layers;
bool layer_khronos_validation = false;
if (try_enable_validation) {
requested_layers.emplace("VK_LAYER_KHRONOS_validation",
&layer_khronos_validation);
}
std::vector<const char*> enabled_layers;
if (!requested_layers.empty()) {
std::vector<VkLayerProperties> available_layers;
// "The list of available layers may change at any time due to actions
// outside of the Vulkan implementation"
while (true) {
available_layers.clear();
uint32_t available_layer_count = 0;
const VkResult get_available_layer_count_result =
ifn.vkEnumerateInstanceLayerProperties(&available_layer_count,
nullptr);
if (get_available_layer_count_result != VK_SUCCESS &&
get_available_layer_count_result != VK_INCOMPLETE) {
break;
}
if (available_layer_count) {
available_layers.resize(available_layer_count);
const VkResult get_available_layers_result =
ifn.vkEnumerateInstanceLayerProperties(&available_layer_count,
available_layers.data());
if (get_available_layers_result == VK_INCOMPLETE) {
// New layers were added.
continue;
}
if (get_available_layers_result != VK_SUCCESS) {
available_layers.clear();
break;
}
// In case the second enumeration returned fewer layers.
available_layers.resize(available_layer_count);
}
break;
}
if (!available_layers.empty()) {
std::vector<VkExtensionProperties> supported_layer_extensions;
for (const VkLayerProperties& available_layer : available_layers) {
auto requested_layer_it =
requested_layers.find(available_layer.layerName);
if (requested_layer_it == requested_layers.cend()) {
continue;
}
bool got_layer_extensions = true;
// "Because the list of available layers may change externally between
// calls to vkEnumerateInstanceExtensionProperties, two calls may
// retrieve different results if a pLayerName is available in one call
// but not in another."
while (true) {
uint32_t supported_layer_extension_count = 0;
const VkResult get_supported_layer_extension_count_result =
ifn.vkEnumerateInstanceExtensionProperties(
nullptr, &supported_layer_extension_count, nullptr);
if (get_supported_layer_extension_count_result != VK_SUCCESS &&
get_supported_layer_extension_count_result != VK_INCOMPLETE) {
got_layer_extensions = false;
break;
}
if (supported_layer_extension_count) {
supported_layer_extensions.resize(supported_layer_extension_count);
const VkResult get_supported_layer_extensions_result =
ifn.vkEnumerateInstanceExtensionProperties(
available_layer.layerName, &supported_layer_extension_count,
supported_layer_extensions.data());
if (get_supported_layer_extensions_result == VK_INCOMPLETE) {
continue;
}
if (get_supported_layer_extensions_result != VK_SUCCESS) {
got_layer_extensions = false;
break;
}
}
supported_layer_extensions.resize(supported_layer_extension_count);
break;
}
if (!got_layer_extensions) {
// The layer was possibly removed.
continue;
}
for (const VkExtensionProperties& supported_extension :
supported_layer_extensions) {
const auto requested_extension_it =
requested_extensions.find(supported_extension.extensionName);
if (requested_extension_it == requested_extensions.cend()) {
continue;
}
assert_not_null(requested_extension_it->second);
// Don't add the extension to the enabled vector multiple times if
// provided by the implementation itself or by another layer.
if (!*requested_extension_it->second) {
enabled_extensions.emplace_back(
requested_extension_it->first.c_str());
enabled_layer_extension_enablement_bools.push_back(
requested_layer_it->second);
*requested_extension_it->second = true;
}
}
assert_not_null(requested_layer_it->second);
if (!*requested_layer_it->second) {
enabled_layers.emplace_back(requested_layer_it->first.c_str());
*requested_layer_it->second = true;
}
}
}
}
// Create the instance.
VkApplicationInfo application_info;
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pNext = nullptr;
application_info.pApplicationName = "Xenia";
application_info.applicationVersion = 1;
application_info.pEngineName = nullptr;
application_info.engineVersion = 0;
// "The patch version number specified in apiVersion is ignored when creating
// an instance object."
// "Vulkan 1.0 implementations were required to return
// VK_ERROR_INCOMPATIBLE_DRIVER if apiVersion was larger than 1.0."
application_info.apiVersion =
vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)
? VulkanDevice::kHighestUsedApiMinorVersion
: VK_MAKE_API_VERSION(0, 1, 0, 0);
VkInstanceCreateInfo instance_create_info;
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pNext = nullptr;
instance_create_info.flags = 0;
// VK_KHR_get_physical_device_properties2 is needed to get the portability
// subset features.
if (vulkan_instance->extensions_.ext_KHR_portability_enumeration &&
vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2) {
instance_create_info.flags |=
VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
instance_create_info.pApplicationInfo = &application_info;
instance_create_info.enabledLayerCount = uint32_t(enabled_layers.size());
instance_create_info.ppEnabledLayerNames = enabled_layers.data();
instance_create_info.enabledExtensionCount =
uint32_t(enabled_extensions.size());
instance_create_info.ppEnabledExtensionNames = enabled_extensions.data();
VkResult instance_create_result = ifn.vkCreateInstance(
&instance_create_info, nullptr, &vulkan_instance->instance_);
if (instance_create_result == VK_ERROR_LAYER_NOT_PRESENT ||
instance_create_result == VK_ERROR_EXTENSION_NOT_PRESENT) {
// A layer was possibly removed. Try without layers.
for (bool* const extension_enablement :
enabled_layer_extension_enablement_bools) {
*extension_enablement = false;
}
for (const std::pair<std::string, bool*>& requested_layer :
requested_layers) {
*requested_layer.second = false;
}
instance_create_info.enabledLayerCount = 0;
instance_create_info.enabledExtensionCount =
uint32_t(enabled_implementation_extension_count);
instance_create_result = ifn.vkCreateInstance(
&instance_create_info, nullptr, &vulkan_instance->instance_);
}
if (instance_create_result != VK_SUCCESS) {
XELOGE("Failed to create a Vulkan instance: {}",
vk::to_string(vk::Result(instance_create_result)));
return nullptr;
}
// Load instance functions.
#define XE_UI_VULKAN_FUNCTION(name) \
functions_loaded &= (ifn.name = PFN_##name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #name))) != nullptr;
// Vulkan 1.0.
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
// Extensions promoted to a Vulkan version supported by the instance.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(ifn.core_name = PFN_##core_name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #core_name))) != nullptr;
if (vulkan_instance->api_version_ >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
#include "xenia/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
// Non-promoted extensions, and extensions promoted to a Vulkan version not
// supported by the instance.
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
functions_loaded &= \
(ifn.core_name = PFN_##core_name(ifn.vkGetInstanceProcAddr( \
vulkan_instance->instance_, #extension_name))) != nullptr;
if (vulkan_instance->api_version_ < VK_MAKE_API_VERSION(0, 1, 1, 0)) {
if (vulkan_instance->extensions_
.ext_1_1_KHR_get_physical_device_properties2) {
#include "xenia/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc"
}
}
#ifdef VK_USE_PLATFORM_XCB_KHR
if (vulkan_instance->extensions_.ext_KHR_xcb_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
}
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
if (vulkan_instance->extensions_.ext_KHR_android_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
}
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
if (vulkan_instance->extensions_.ext_KHR_win32_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
}
#endif
if (vulkan_instance->extensions_.ext_KHR_surface) {
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
}
if (vulkan_instance->extensions_.ext_EXT_debug_utils) {
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
}
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
if (!functions_loaded) {
XELOGE("Failed to get all Vulkan instance function pointers");
return nullptr;
}
// Check whether a surface can be created.
if (with_surface && !VulkanPresenter::GetSurfaceTypesSupportedByInstance(
vulkan_instance->extensions_)) {
XELOGE("The Vulkan instance doesn't support surface types used by Xenia");
return nullptr;
}
// Log instance properties.
XELOGI("Vulkan instance API version {}.{}.{}. Enabled layers and extensions:",
VK_VERSION_MAJOR(vulkan_instance->api_version_),
VK_VERSION_MINOR(vulkan_instance->api_version_),
VK_VERSION_PATCH(vulkan_instance->api_version_));
for (uint32_t enabled_layer_index = 0;
enabled_layer_index < instance_create_info.enabledLayerCount;
++enabled_layer_index) {
XELOGI("* {}",
instance_create_info.ppEnabledLayerNames[enabled_layer_index]);
}
for (uint32_t enabled_extension_index = 0;
enabled_extension_index < instance_create_info.enabledExtensionCount;
++enabled_extension_index) {
XELOGI(
"* {}",
instance_create_info.ppEnabledExtensionNames[enabled_extension_index]);
}
// Create the debug messenger if requested and available.
if (vulkan_instance->extensions_.ext_EXT_debug_utils &&
cvars::vulkan_log_debug_messages) {
VkDebugUtilsMessengerCreateInfoEXT debug_utils_messenger_create_info = {
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
if (xe::logging::ShouldLog(xe::LogLevel::Debug)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Info)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Warning)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
}
if (xe::logging::ShouldLog(xe::LogLevel::Error)) {
debug_utils_messenger_create_info.messageSeverity |=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
}
// VUID-VkDebugUtilsMessengerCreateInfoEXT-messageSeverity-requiredbitmask:
// "messageSeverity must not be 0"
if (debug_utils_messenger_create_info.messageSeverity) {
debug_utils_messenger_create_info.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debug_utils_messenger_create_info.pfnUserCallback =
DebugUtilsMessengerCallback;
debug_utils_messenger_create_info.pUserData = vulkan_instance.get();
const VkResult debug_utils_messenger_create_result =
ifn.vkCreateDebugUtilsMessengerEXT(
vulkan_instance->instance_, &debug_utils_messenger_create_info,
nullptr, &vulkan_instance->debug_utils_messenger_);
if (debug_utils_messenger_create_result != VK_SUCCESS) {
XELOGW("Failed to create the Vulkan debug utils messenger: {}",
vk::to_string(vk::Result(debug_utils_messenger_create_result)));
}
}
}
return vulkan_instance;
}
VulkanInstance::~VulkanInstance() {
if (instance_) {
if (debug_utils_messenger_ != VK_NULL_HANDLE) {
functions_.vkDestroyDebugUtilsMessengerEXT(
instance_, debug_utils_messenger_, nullptr);
}
functions_.vkDestroyInstance(instance_, nullptr);
}
#if XE_PLATFORM_LINUX
if (loader_) {
dlclose(loader_);
}
#elif XE_PLATFORM_WIN32
if (loader_) {
FreeLibrary(loader_);
}
#endif
}
void VulkanInstance::EnumeratePhysicalDevices(
std::vector<VkPhysicalDevice>& physical_devices_out) const {
physical_devices_out.clear();
while (true) {
uint32_t physical_device_count = 0;
const VkResult get_physical_device_count_result =
functions_.vkEnumeratePhysicalDevices(instance_, &physical_device_count,
nullptr);
if ((get_physical_device_count_result != VK_SUCCESS &&
get_physical_device_count_result != VK_INCOMPLETE) ||
!physical_device_count) {
return;
}
physical_devices_out.resize(physical_device_count);
const VkResult get_physical_devices_result =
functions_.vkEnumeratePhysicalDevices(instance_, &physical_device_count,
physical_devices_out.data());
if (get_physical_devices_result == VK_INCOMPLETE) {
continue;
}
physical_devices_out.resize(
get_physical_devices_result == VK_SUCCESS ? physical_device_count : 0);
return;
}
}
VkBool32 VulkanInstance::DebugUtilsMessengerCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_types,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
[[maybe_unused]] void* user_data) {
xe::LogLevel log_level;
char log_prefix_char;
if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
log_level = xe::LogLevel::Error;
log_prefix_char = xe::logging::kPrefixCharError;
} else if (message_severity >=
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
log_level = xe::LogLevel::Warning;
log_prefix_char = xe::logging::kPrefixCharWarning;
} else if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
log_level = xe::LogLevel::Info;
log_prefix_char = xe::logging::kPrefixCharInfo;
} else {
log_level = xe::LogLevel::Debug;
log_prefix_char = xe::logging::kPrefixCharDebug;
}
std::ostringstream log_str;
log_str << "Vulkan "
<< vk::to_string(
vk::DebugUtilsMessageSeverityFlagBitsEXT(message_severity))
<< " ("
<< vk::to_string(vk::DebugUtilsMessageTypeFlagsEXT(message_types))
<< ", ID " << callback_data->messageIdNumber;
if (callback_data->pMessageIdName) {
log_str << ": " << callback_data->pMessageIdName;
}
log_str << ')';
if (callback_data->pMessage) {
log_str << ": " << callback_data->pMessage;
}
bool annotations_begun = false;
const auto begin_annotation = [&log_str, &annotations_begun]() {
log_str << (annotations_begun ? ", " : " (");
annotations_begun = true;
};
for (uint32_t queue_label_index = 0;
queue_label_index < callback_data->queueLabelCount;
++queue_label_index) {
begin_annotation();
log_str << "queue label " << queue_label_index << ": "
<< callback_data->pQueueLabels[queue_label_index].pLabelName;
}
for (uint32_t cmd_buf_label_index = 0;
cmd_buf_label_index < callback_data->cmdBufLabelCount;
++cmd_buf_label_index) {
begin_annotation();
log_str << "command buffer label " << cmd_buf_label_index << ": "
<< callback_data->pCmdBufLabels[cmd_buf_label_index].pLabelName;
}
for (uint32_t object_index = 0; object_index < callback_data->objectCount;
++object_index) {
begin_annotation();
const VkDebugUtilsObjectNameInfoEXT& object_info =
callback_data->pObjects[object_index];
// Lowercase hexadecimal digits in the handle to match the default Vulkan
// debug utils messenger.
log_str << "object " << object_index << ": "
<< vk::to_string(vk::ObjectType(object_info.objectType)) << " 0x"
<< std::hex << object_info.objectHandle << std::dec;
if (object_info.pObjectName) {
log_str << " '" << object_info.pObjectName << '\'';
}
}
if (annotations_begun) {
log_str << ')';
}
xe::logging::AppendLogLine(log_level, log_prefix_char, log_str.str());
return VK_FALSE;
}
} // namespace vulkan
} // namespace ui
} // namespace xe