/ src / video_core / renderer_vulkan / vk_platform.cpp
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