vk_platform.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 // Include the vulkan platform specific header 6 #if defined(ANDROID) 7 #define VK_USE_PLATFORM_ANDROID_KHR 8 #elif defined(WIN32) 9 #define VK_USE_PLATFORM_WIN32_KHR 10 #elif defined(__APPLE__) 11 #define VK_USE_PLATFORM_METAL_EXT 12 #else 13 #define VK_USE_PLATFORM_WAYLAND_KHR 14 #define VK_USE_PLATFORM_XLIB_KHR 15 #endif 16 17 #include <memory> 18 #include <vector> 19 #include <boost/container/static_vector.hpp> 20 #include <fmt/format.h> 21 22 #include "common/assert.h" 23 #include "common/logging/log.h" 24 #include "common/settings.h" 25 #include "core/frontend/emu_window.h" 26 #include "video_core/renderer_vulkan/vk_platform.h" 27 28 namespace Vulkan { 29 30 namespace { 31 static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( 32 VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, 33 const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { 34 35 switch (callback_data->messageIdNumber) { 36 case 0x609a13b: // Vertex attribute at location not consumed by shader 37 return VK_FALSE; 38 default: 39 break; 40 } 41 42 Common::Log::Level level{}; 43 switch (severity) { 44 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: 45 level = Common::Log::Level::Error; 46 break; 47 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: 48 level = Common::Log::Level::Info; 49 break; 50 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: 51 case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: 52 level = Common::Log::Level::Debug; 53 break; 54 default: 55 level = Common::Log::Level::Info; 56 } 57 58 LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}", 59 callback_data->pMessageIdName ? callback_data->pMessageIdName : "<null>", 60 callback_data->pMessage ? callback_data->pMessage : "<null>"); 61 62 return VK_FALSE; 63 } 64 65 static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, 66 VkDebugReportObjectTypeEXT objectType, 67 uint64_t object, std::size_t location, 68 int32_t messageCode, 69 const char* pLayerPrefix, 70 const char* pMessage, void* pUserData) { 71 72 const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags); 73 Common::Log::Level level{}; 74 switch (severity) { 75 case VK_DEBUG_REPORT_ERROR_BIT_EXT: 76 level = Common::Log::Level::Error; 77 break; 78 case VK_DEBUG_REPORT_INFORMATION_BIT_EXT: 79 level = Common::Log::Level::Warning; 80 break; 81 case VK_DEBUG_REPORT_DEBUG_BIT_EXT: 82 case VK_DEBUG_REPORT_WARNING_BIT_EXT: 83 case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT: 84 level = Common::Log::Level::Debug; 85 break; 86 default: 87 level = Common::Log::Level::Info; 88 } 89 90 const vk::DebugReportObjectTypeEXT type = static_cast<vk::DebugReportObjectTypeEXT>(objectType); 91 LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, 92 "type = {}, object = {} | MessageCode = {:#x}, LayerPrefix = {} | {}", 93 vk::to_string(type), object, messageCode, pLayerPrefix, pMessage); 94 95 return VK_FALSE; 96 } 97 } // Anonymous namespace 98 99 std::shared_ptr<Common::DynamicLibrary> OpenLibrary( 100 [[maybe_unused]] Frontend::GraphicsContext* context) { 101 #ifdef ANDROID 102 // Android may override the Vulkan driver from the frontend. 103 if (auto library = context->GetDriverLibrary(); library) { 104 return library; 105 } 106 #endif 107 auto library = std::make_shared<Common::DynamicLibrary>(); 108 #ifdef __APPLE__ 109 const std::string filename = Common::DynamicLibrary::GetLibraryName("vulkan"); 110 if (!library->Load(filename)) { 111 // Fall back to directly loading bundled MoltenVK library. 112 const std::string mvk_filename = Common::DynamicLibrary::GetLibraryName("MoltenVK"); 113 void(library->Load(mvk_filename)); 114 } 115 #else 116 std::string filename = Common::DynamicLibrary::GetLibraryName("vulkan", 1); 117 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename); 118 if (!library->Load(filename)) { 119 // Android devices may not have libvulkan.so.1, only libvulkan.so. 120 filename = Common::DynamicLibrary::GetLibraryName("vulkan"); 121 LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename); 122 void(library->Load(filename)); 123 } 124 #endif 125 return library; 126 } 127 128 vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::EmuWindow& emu_window) { 129 const auto& window_info = emu_window.GetWindowInfo(); 130 vk::SurfaceKHR surface{}; 131 132 #if defined(VK_USE_PLATFORM_WIN32_KHR) 133 if (window_info.type == Frontend::WindowSystemType::Windows) { 134 const vk::Win32SurfaceCreateInfoKHR win32_ci = { 135 .hinstance = nullptr, 136 .hwnd = static_cast<HWND>(window_info.render_surface), 137 }; 138 139 if (instance.createWin32SurfaceKHR(&win32_ci, nullptr, &surface) != vk::Result::eSuccess) { 140 LOG_CRITICAL(Render_Vulkan, "Failed to initialize Win32 surface"); 141 UNREACHABLE(); 142 } 143 } 144 #elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) 145 if (window_info.type == Frontend::WindowSystemType::X11) { 146 const vk::XlibSurfaceCreateInfoKHR xlib_ci = { 147 .dpy = static_cast<Display*>(window_info.display_connection), 148 .window = reinterpret_cast<Window>(window_info.render_surface), 149 }; 150 151 if (instance.createXlibSurfaceKHR(&xlib_ci, nullptr, &surface) != vk::Result::eSuccess) { 152 LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface"); 153 UNREACHABLE(); 154 } 155 } else if (window_info.type == Frontend::WindowSystemType::Wayland) { 156 const vk::WaylandSurfaceCreateInfoKHR wayland_ci = { 157 .display = static_cast<wl_display*>(window_info.display_connection), 158 .surface = static_cast<wl_surface*>(window_info.render_surface), 159 }; 160 161 if (instance.createWaylandSurfaceKHR(&wayland_ci, nullptr, &surface) != 162 vk::Result::eSuccess) { 163 LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface"); 164 UNREACHABLE(); 165 } 166 } 167 #elif defined(VK_USE_PLATFORM_METAL_EXT) 168 if (window_info.type == Frontend::WindowSystemType::MacOS) { 169 const vk::MetalSurfaceCreateInfoEXT macos_ci = { 170 .pLayer = static_cast<const CAMetalLayer*>(window_info.render_surface), 171 }; 172 173 if (instance.createMetalSurfaceEXT(&macos_ci, nullptr, &surface) != vk::Result::eSuccess) { 174 LOG_CRITICAL(Render_Vulkan, "Failed to initialize MacOS surface"); 175 UNREACHABLE(); 176 } 177 } 178 #elif defined(VK_USE_PLATFORM_ANDROID_KHR) 179 if (window_info.type == Frontend::WindowSystemType::Android) { 180 vk::AndroidSurfaceCreateInfoKHR android_ci = { 181 .window = reinterpret_cast<ANativeWindow*>(window_info.render_surface), 182 }; 183 184 if (instance.createAndroidSurfaceKHR(&android_ci, nullptr, &surface) != 185 vk::Result::eSuccess) { 186 LOG_CRITICAL(Render_Vulkan, "Failed to initialize Android surface"); 187 UNREACHABLE(); 188 } 189 } 190 #endif 191 192 if (!surface) { 193 LOG_CRITICAL(Render_Vulkan, "Presentation not supported on this platform"); 194 UNREACHABLE(); 195 } 196 197 return surface; 198 } 199 200 std::vector<const char*> GetInstanceExtensions(Frontend::WindowSystemType window_type, 201 bool enable_debug_utils) { 202 const auto properties = vk::enumerateInstanceExtensionProperties(); 203 if (properties.empty()) { 204 LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); 205 return {}; 206 } 207 208 // Add the windowing system specific extension 209 std::vector<const char*> extensions; 210 extensions.reserve(7); 211 212 #if defined(__APPLE__) 213 extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); 214 // For configuring MoltenVK. 215 extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); 216 #endif 217 218 switch (window_type) { 219 case Frontend::WindowSystemType::Headless: 220 break; 221 #if defined(VK_USE_PLATFORM_WIN32_KHR) 222 case Frontend::WindowSystemType::Windows: 223 extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); 224 break; 225 #elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) 226 case Frontend::WindowSystemType::X11: 227 extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); 228 break; 229 case Frontend::WindowSystemType::Wayland: 230 extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); 231 break; 232 #elif defined(VK_USE_PLATFORM_METAL_EXT) 233 case Frontend::WindowSystemType::MacOS: 234 extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); 235 break; 236 #elif defined(VK_USE_PLATFORM_ANDROID_KHR) 237 case Frontend::WindowSystemType::Android: 238 extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); 239 break; 240 #endif 241 default: 242 LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); 243 break; 244 } 245 246 if (window_type != Frontend::WindowSystemType::Headless) { 247 extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); 248 } 249 250 if (enable_debug_utils) { 251 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 252 extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); 253 } 254 255 // Sanitize extension list 256 std::erase_if(extensions, [&](const char* extension) -> bool { 257 const auto it = 258 std::find_if(properties.begin(), properties.end(), [extension](const auto& prop) { 259 return std::strcmp(extension, prop.extensionName) == 0; 260 }); 261 262 if (it == properties.end()) { 263 LOG_INFO(Render_Vulkan, "Candidate instance extension {} is not available", extension); 264 return true; 265 } 266 return false; 267 }); 268 269 return extensions; 270 } 271 272 vk::InstanceCreateFlags GetInstanceFlags() { 273 #if defined(__APPLE__) 274 return vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; 275 #else 276 return static_cast<vk::InstanceCreateFlags>(0); 277 #endif 278 } 279 280 vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, 281 Frontend::WindowSystemType window_type, bool enable_validation, 282 bool dump_command_buffers) { 283 if (!library.IsLoaded()) { 284 throw std::runtime_error("Failed to load Vulkan driver library"); 285 } 286 287 const auto vkGetInstanceProcAddr = 288 library.GetSymbol<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"); 289 if (!vkGetInstanceProcAddr) { 290 throw std::runtime_error("Failed GetSymbol vkGetInstanceProcAddr"); 291 } 292 VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); 293 294 const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion 295 ? vk::enumerateInstanceVersion() 296 : VK_API_VERSION_1_0; 297 if (available_version < TargetVulkanApiVersion) { 298 throw std::runtime_error(fmt::format( 299 "Vulkan {}.{} is required, but only {}.{} is supported by instance!", 300 VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), 301 VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version))); 302 } 303 304 const auto extensions = GetInstanceExtensions(window_type, enable_validation); 305 306 const vk::ApplicationInfo application_info = { 307 .pApplicationName = "Citra", 308 .applicationVersion = VK_MAKE_VERSION(1, 0, 0), 309 .pEngineName = "Citra Vulkan", 310 .engineVersion = VK_MAKE_VERSION(1, 0, 0), 311 .apiVersion = TargetVulkanApiVersion, 312 }; 313 314 boost::container::static_vector<const char*, 2> layers; 315 if (enable_validation) { 316 layers.push_back("VK_LAYER_KHRONOS_validation"); 317 } 318 if (dump_command_buffers) { 319 layers.push_back("VK_LAYER_LUNARG_api_dump"); 320 } 321 322 vk::InstanceCreateInfo instance_ci = { 323 .flags = GetInstanceFlags(), 324 .pApplicationInfo = &application_info, 325 .enabledLayerCount = static_cast<u32>(layers.size()), 326 .ppEnabledLayerNames = layers.data(), 327 .enabledExtensionCount = static_cast<u32>(extensions.size()), 328 .ppEnabledExtensionNames = extensions.data(), 329 }; 330 331 #ifdef __APPLE__ 332 // Use synchronous queue submits if async presentation is enabled, to avoid threading 333 // indirection. 334 const auto synchronous_queue_submits = Settings::values.async_presentation.GetValue(); 335 // If the device is lost, make an attempt to resume if possible to avoid crashes. 336 constexpr auto resume_lost_device = true; 337 // Maximize concurrency to improve shader compilation performance. 338 constexpr auto maximize_concurrent_compilation = true; 339 340 constexpr auto layer_name = "MoltenVK"; 341 const vk::LayerSettingEXT layer_settings[] = { 342 {layer_name, "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", vk::LayerSettingTypeEXT::eBool32, 1, 343 &synchronous_queue_submits}, 344 {layer_name, "MVK_CONFIG_RESUME_LOST_DEVICE", vk::LayerSettingTypeEXT::eBool32, 1, 345 &resume_lost_device}, 346 {layer_name, "MVK_CONFIG_SHOULD_MAXIMIZE_CONCURRENT_COMPILATION", 347 vk::LayerSettingTypeEXT::eBool32, 1, &maximize_concurrent_compilation}, 348 }; 349 const vk::LayerSettingsCreateInfoEXT layer_settings_ci = { 350 .pNext = nullptr, 351 .settingCount = static_cast<uint32_t>(std::size(layer_settings)), 352 .pSettings = layer_settings, 353 }; 354 355 if (std::find(extensions.begin(), extensions.end(), VK_EXT_LAYER_SETTINGS_EXTENSION_NAME) != 356 extensions.end()) { 357 instance_ci.pNext = &layer_settings_ci; 358 } 359 #endif 360 361 auto instance = vk::createInstanceUnique(instance_ci); 362 363 VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance); 364 365 return instance; 366 } 367 368 vk::UniqueDebugUtilsMessengerEXT CreateDebugMessenger(vk::Instance instance) { 369 const vk::DebugUtilsMessengerCreateInfoEXT msg_ci = { 370 .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | 371 vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | 372 vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | 373 vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose, 374 .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | 375 vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | 376 vk::DebugUtilsMessageTypeFlagBitsEXT::eDeviceAddressBinding | 377 vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, 378 .pfnUserCallback = DebugUtilsCallback, 379 }; 380 return instance.createDebugUtilsMessengerEXTUnique(msg_ci); 381 } 382 383 vk::UniqueDebugReportCallbackEXT CreateDebugReportCallback(vk::Instance instance) { 384 const vk::DebugReportCallbackCreateInfoEXT callback_ci = { 385 .flags = vk::DebugReportFlagBitsEXT::eDebug | vk::DebugReportFlagBitsEXT::eInformation | 386 vk::DebugReportFlagBitsEXT::eError | 387 vk::DebugReportFlagBitsEXT::ePerformanceWarning | 388 vk::DebugReportFlagBitsEXT::eWarning, 389 .pfnCallback = DebugReportCallback, 390 }; 391 return instance.createDebugReportCallbackEXTUnique(callback_ci); 392 } 393 394 DebugCallback CreateDebugCallback(vk::Instance instance, bool& debug_utils_supported) { 395 if (!Settings::values.renderer_debug) { 396 return {}; 397 } 398 const auto properties = vk::enumerateInstanceExtensionProperties(); 399 if (properties.empty()) { 400 LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); 401 return {}; 402 } 403 const auto it = std::find_if(properties.begin(), properties.end(), [](const auto& prop) { 404 return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; 405 }); 406 // Prefer debug util messenger if available. 407 debug_utils_supported = it != properties.end(); 408 if (debug_utils_supported) { 409 return CreateDebugMessenger(instance); 410 } 411 // Otherwise fallback to debug report callback. 412 return CreateDebugReportCallback(instance); 413 } 414 415 } // namespace Vulkan