/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2017 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 #include #include #include #include "third_party/renderdoc/renderdoc_app.h" #include "third_party/volk/volk.h" #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/profiling.h" #include "xenia/ui/vulkan/vulkan.h" #include "xenia/ui/vulkan/vulkan_immediate_drawer.h" #include "xenia/ui/vulkan/vulkan_util.h" #include "xenia/ui/window.h" #if XE_PLATFORM_LINUX #include "xenia/ui/window_gtk.h" #endif #define VK_API_VERSION VK_API_VERSION_1_1 namespace xe { namespace ui { namespace vulkan { VulkanInstance::VulkanInstance() { if (FLAGS_vulkan_validation) { DeclareRequiredLayer("VK_LAYER_LUNARG_standard_validation", Version::Make(0, 0, 0), true); // DeclareRequiredLayer("VK_LAYER_GOOGLE_unique_objects", Version::Make(0, // 0, 0), true); /* DeclareRequiredLayer("VK_LAYER_GOOGLE_threading", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_core_validation", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_object_tracker", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_draw_state", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_parameter_validation", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_swapchain", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_device_limits", Version::Make(0, 0, 0), true); DeclareRequiredLayer("VK_LAYER_LUNARG_image", Version::Make(0, 0, 0), true); */ DeclareRequiredExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, Version::Make(0, 0, 0), true); } DeclareRequiredExtension(VK_EXT_DEBUG_MARKER_EXTENSION_NAME, Version::Make(0, 0, 0), true); } VulkanInstance::~VulkanInstance() { DestroyInstance(); } bool VulkanInstance::Initialize() { auto version = Version::Parse(VK_API_VERSION); XELOGVK("Initializing Vulkan %s...", version.pretty_string.c_str()); if (volkInitialize() != VK_SUCCESS) { XELOGE("volkInitialize() failed!"); return false; } // Get all of the global layers and extensions provided by the system. if (!QueryGlobals()) { XELOGE("Failed to query instance globals"); return false; } // Create the vulkan instance used by the application with our required // extensions and layers. if (!CreateInstance()) { XELOGE("Failed to create instance"); return false; } // Query available devices so that we can pick one. if (!QueryDevices()) { XELOGE("Failed to query devices"); return false; } // Hook into renderdoc, if it's available. EnableRenderDoc(); XELOGVK("Instance initialized successfully!"); return true; } bool VulkanInstance::EnableRenderDoc() { // RenderDoc injects itself into our process, so we should be able to get it. pRENDERDOC_GetAPI get_api = nullptr; #if XE_PLATFORM_WIN32 auto module_handle = GetModuleHandle(L"renderdoc.dll"); if (!module_handle) { XELOGI("RenderDoc support requested but it is not attached"); return false; } get_api = reinterpret_cast( GetProcAddress(module_handle, "RENDERDOC_GetAPI")); #else // TODO(benvanik): dlsym/etc - abstracted in base/. #endif // XE_PLATFORM_32 if (!get_api) { XELOGI("RenderDoc support requested but it is not attached"); return false; } // Request all API function pointers. if (!get_api(eRENDERDOC_API_Version_1_0_1, reinterpret_cast(&renderdoc_api_))) { XELOGE("RenderDoc found but was unable to get API - version mismatch?"); return false; } auto api = reinterpret_cast(renderdoc_api_); // Query version. int major; int minor; int patch; api->GetAPIVersion(&major, &minor, &patch); XELOGI("RenderDoc attached; %d.%d.%d", major, minor, patch); is_renderdoc_attached_ = true; return true; } bool VulkanInstance::QueryGlobals() { // Scan global layers and accumulate properties. // We do this in a loop so that we can allocate the required amount of // memory and handle race conditions while querying. uint32_t count = 0; std::vector global_layer_properties; VkResult err; do { err = vkEnumerateInstanceLayerProperties(&count, nullptr); CheckResult(err, "vkEnumerateInstanceLayerProperties"); global_layer_properties.resize(count); err = vkEnumerateInstanceLayerProperties(&count, global_layer_properties.data()); } while (err == VK_INCOMPLETE); CheckResult(err, "vkEnumerateInstanceLayerProperties"); global_layers_.resize(count); for (size_t i = 0; i < global_layers_.size(); ++i) { auto& global_layer = global_layers_[i]; global_layer.properties = global_layer_properties[i]; // Get all extensions available for the layer. do { err = vkEnumerateInstanceExtensionProperties( global_layer.properties.layerName, &count, nullptr); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); global_layer.extensions.resize(count); err = vkEnumerateInstanceExtensionProperties( global_layer.properties.layerName, &count, global_layer.extensions.data()); } while (err == VK_INCOMPLETE); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); } XELOGVK("Found %d global layers:", global_layers_.size()); for (size_t i = 0; i < global_layers_.size(); ++i) { auto& global_layer = global_layers_[i]; auto spec_version = Version::Parse(global_layer.properties.specVersion); auto impl_version = Version::Parse(global_layer.properties.implementationVersion); XELOGVK("- %s (spec: %s, impl: %s)", global_layer.properties.layerName, spec_version.pretty_string.c_str(), impl_version.pretty_string.c_str()); XELOGVK(" %s", global_layer.properties.description); if (!global_layer.extensions.empty()) { XELOGVK(" %d extensions:", global_layer.extensions.size()); DumpExtensions(global_layer.extensions, " "); } } // Scan global extensions. do { err = vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); global_extensions_.resize(count); err = vkEnumerateInstanceExtensionProperties(nullptr, &count, global_extensions_.data()); } while (err == VK_INCOMPLETE); CheckResult(err, "vkEnumerateInstanceExtensionProperties"); XELOGVK("Found %d global extensions:", global_extensions_.size()); DumpExtensions(global_extensions_, ""); return true; } bool VulkanInstance::CreateInstance() { XELOGVK("Verifying layers and extensions..."); // Gather list of enabled layer names. auto layers_result = CheckRequirements(required_layers_, global_layers_); auto& enabled_layers = layers_result.second; // Gather list of enabled extension names. auto extensions_result = CheckRequirements(required_extensions_, global_extensions_); auto& enabled_extensions = extensions_result.second; // We wait until both extensions and layers are checked before failing out so // that the user gets a complete list of what they have/don't. if (!extensions_result.first || !layers_result.first) { XELOGE("Layer and extension verification failed; aborting initialization"); return false; } XELOGVK("Initializing application instance..."); // TODO(benvanik): use GetEntryInfo? 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 = "xenia"; application_info.engineVersion = 1; application_info.apiVersion = VK_API_VERSION; VkInstanceCreateInfo instance_info; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pNext = nullptr; instance_info.flags = 0; instance_info.pApplicationInfo = &application_info; instance_info.enabledLayerCount = static_cast(enabled_layers.size()); instance_info.ppEnabledLayerNames = enabled_layers.data(); instance_info.enabledExtensionCount = static_cast(enabled_extensions.size()); instance_info.ppEnabledExtensionNames = enabled_extensions.data(); auto err = vkCreateInstance(&instance_info, nullptr, &handle); if (err != VK_SUCCESS) { XELOGE("vkCreateInstance returned %s", to_string(err)); } switch (err) { case VK_SUCCESS: // Ok! break; case VK_ERROR_INITIALIZATION_FAILED: XELOGE("Instance initialization failed; generic"); return false; case VK_ERROR_INCOMPATIBLE_DRIVER: XELOGE( "Instance initialization failed; cannot find a compatible Vulkan " "installable client driver (ICD)"); return false; case VK_ERROR_EXTENSION_NOT_PRESENT: XELOGE("Instance initialization failed; requested extension not present"); return false; case VK_ERROR_LAYER_NOT_PRESENT: XELOGE("Instance initialization failed; requested layer not present"); return false; default: XELOGE("Instance initialization failed; unknown: %s", to_string(err)); return false; } // Load Vulkan entrypoints and extensions. volkLoadInstance(handle); // Enable debug validation, if needed. EnableDebugValidation(); return true; } void VulkanInstance::DestroyInstance() { if (!handle) { return; } DisableDebugValidation(); vkDestroyInstance(handle, nullptr); handle = nullptr; } VkBool32 VKAPI_PTR DebugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { if (strcmp(pLayerPrefix, "Validation") == 0) { const char* blacklist[] = { "bound but it was never updated. You may want to either update it or " "not bind it.", "is being used in draw but has not been updated.", }; for (uint32_t i = 0; i < xe::countof(blacklist); ++i) { if (strstr(pMessage, blacklist[i]) != nullptr) { return false; } } } auto instance = reinterpret_cast(pUserData); const char* message_type = "UNKNOWN"; if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { message_type = "ERROR"; } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { message_type = "WARN"; } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { message_type = "PERF WARN"; } else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { message_type = "INFO"; } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { message_type = "DEBUG"; } XELOGVK("[%s/%s:%d] %s", pLayerPrefix, message_type, messageCode, pMessage); return false; } void VulkanInstance::EnableDebugValidation() { if (dbg_report_callback_) { DisableDebugValidation(); } auto vk_create_debug_report_callback_ext = reinterpret_cast( vkGetInstanceProcAddr(handle, "vkCreateDebugReportCallbackEXT")); if (!vk_create_debug_report_callback_ext) { XELOGVK("Debug validation layer not installed; ignoring"); return; } VkDebugReportCallbackCreateInfoEXT create_info; create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; create_info.pNext = nullptr; // TODO(benvanik): flags to set these. create_info.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT; create_info.pfnCallback = &DebugMessageCallback; create_info.pUserData = this; auto status = vk_create_debug_report_callback_ext( handle, &create_info, nullptr, &dbg_report_callback_); if (status == VK_SUCCESS) { XELOGVK("Debug validation layer enabled"); } else { XELOGVK("Debug validation layer failed to install; error %s", to_string(status)); } } void VulkanInstance::DisableDebugValidation() { if (!dbg_report_callback_) { return; } auto vk_destroy_debug_report_callback_ext = reinterpret_cast( vkGetInstanceProcAddr(handle, "vkDestroyDebugReportCallbackEXT")); if (!vk_destroy_debug_report_callback_ext) { return; } vk_destroy_debug_report_callback_ext(handle, dbg_report_callback_, nullptr); dbg_report_callback_ = nullptr; } bool VulkanInstance::QueryDevices() { // Get handles to all devices. uint32_t count = 0; std::vector device_handles; auto err = vkEnumeratePhysicalDevices(handle, &count, nullptr); CheckResult(err, "vkEnumeratePhysicalDevices"); device_handles.resize(count); err = vkEnumeratePhysicalDevices(handle, &count, device_handles.data()); CheckResult(err, "vkEnumeratePhysicalDevices"); // Query device info. for (size_t i = 0; i < device_handles.size(); ++i) { auto device_handle = device_handles[i]; DeviceInfo device_info; device_info.handle = device_handle; // Query general attributes. vkGetPhysicalDeviceProperties(device_handle, &device_info.properties); vkGetPhysicalDeviceFeatures(device_handle, &device_info.features); vkGetPhysicalDeviceMemoryProperties(device_handle, &device_info.memory_properties); // Gather queue family properties. vkGetPhysicalDeviceQueueFamilyProperties(device_handle, &count, nullptr); device_info.queue_family_properties.resize(count); vkGetPhysicalDeviceQueueFamilyProperties( device_handle, &count, device_info.queue_family_properties.data()); // Gather layers. std::vector layer_properties; err = vkEnumerateDeviceLayerProperties(device_handle, &count, nullptr); CheckResult(err, "vkEnumerateDeviceLayerProperties"); layer_properties.resize(count); err = vkEnumerateDeviceLayerProperties(device_handle, &count, layer_properties.data()); CheckResult(err, "vkEnumerateDeviceLayerProperties"); for (size_t j = 0; j < layer_properties.size(); ++j) { LayerInfo layer_info; layer_info.properties = layer_properties[j]; err = vkEnumerateDeviceExtensionProperties( device_handle, layer_info.properties.layerName, &count, nullptr); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); layer_info.extensions.resize(count); err = vkEnumerateDeviceExtensionProperties( device_handle, layer_info.properties.layerName, &count, layer_info.extensions.data()); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); device_info.layers.push_back(std::move(layer_info)); } // Gather extensions. err = vkEnumerateDeviceExtensionProperties(device_handle, nullptr, &count, nullptr); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); device_info.extensions.resize(count); err = vkEnumerateDeviceExtensionProperties(device_handle, nullptr, &count, device_info.extensions.data()); CheckResult(err, "vkEnumerateDeviceExtensionProperties"); available_devices_.push_back(std::move(device_info)); } XELOGVK("Found %d physical devices:", available_devices_.size()); for (size_t i = 0; i < available_devices_.size(); ++i) { auto& device_info = available_devices_[i]; XELOGVK("- Device %d:", i); DumpDeviceInfo(device_info); } return true; } void VulkanInstance::DumpLayers(const std::vector& layers, const char* indent) { for (size_t i = 0; i < layers.size(); ++i) { auto& layer = layers[i]; auto spec_version = Version::Parse(layer.properties.specVersion); auto impl_version = Version::Parse(layer.properties.implementationVersion); XELOGVK("%s- %s (spec: %s, impl: %s)", indent, layer.properties.layerName, spec_version.pretty_string.c_str(), impl_version.pretty_string.c_str()); XELOGVK("%s %s", indent, layer.properties.description); if (!layer.extensions.empty()) { XELOGVK("%s %d extensions:", indent, layer.extensions.size()); DumpExtensions(layer.extensions, std::strlen(indent) ? " " : " "); } } } void VulkanInstance::DumpExtensions( const std::vector& extensions, const char* indent) { for (size_t i = 0; i < extensions.size(); ++i) { auto& extension = extensions[i]; auto version = Version::Parse(extension.specVersion); XELOGVK("%s- %s (%s)", indent, extension.extensionName, version.pretty_string.c_str()); } } void VulkanInstance::DumpDeviceInfo(const DeviceInfo& device_info) { auto& properties = device_info.properties; auto api_version = Version::Parse(properties.apiVersion); auto driver_version = Version::Parse(properties.driverVersion); XELOGVK(" apiVersion = %s", api_version.pretty_string.c_str()); XELOGVK(" driverVersion = %s", driver_version.pretty_string.c_str()); XELOGVK(" vendorId = 0x%04x", properties.vendorID); XELOGVK(" deviceId = 0x%04x", properties.deviceID); XELOGVK(" deviceType = %s", to_string(properties.deviceType)); XELOGVK(" deviceName = %s", properties.deviceName); auto& memory_props = device_info.memory_properties; XELOGVK(" Memory Heaps:"); for (size_t j = 0; j < memory_props.memoryHeapCount; ++j) { XELOGVK(" - Heap %u: %" PRIu64 " bytes", j, memory_props.memoryHeaps[j].size); for (size_t k = 0; k < memory_props.memoryTypeCount; ++k) { if (memory_props.memoryTypes[k].heapIndex == j) { XELOGVK(" - Type %u:", k); auto type_flags = memory_props.memoryTypes[k].propertyFlags; if (!type_flags) { XELOGVK(" VK_MEMORY_PROPERTY_DEVICE_ONLY"); } if (type_flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { XELOGVK(" VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT"); } if (type_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { XELOGVK(" VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT"); } if (type_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) { XELOGVK(" VK_MEMORY_PROPERTY_HOST_COHERENT_BIT"); } if (type_flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) { XELOGVK(" VK_MEMORY_PROPERTY_HOST_CACHED_BIT"); } if (type_flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { XELOGVK(" VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT"); } } } } XELOGVK(" Queue Families:"); for (size_t j = 0; j < device_info.queue_family_properties.size(); ++j) { auto& queue_props = device_info.queue_family_properties[j]; XELOGVK(" - Queue %d:", j); XELOGVK( " queueFlags = %s%s%s%s", (queue_props.queueFlags & VK_QUEUE_GRAPHICS_BIT) ? "graphics, " : "", (queue_props.queueFlags & VK_QUEUE_COMPUTE_BIT) ? "compute, " : "", (queue_props.queueFlags & VK_QUEUE_TRANSFER_BIT) ? "transfer, " : "", (queue_props.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) ? "sparse, " : ""); XELOGVK(" queueCount = %u", queue_props.queueCount); XELOGVK(" timestampValidBits = %u", queue_props.timestampValidBits); } XELOGVK(" Layers:"); DumpLayers(device_info.layers, " "); XELOGVK(" Extensions:"); DumpExtensions(device_info.extensions, " "); } } // namespace vulkan } // namespace ui } // namespace xe