From 87773a417b96417a14bab695422f70e80697f4e4 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 17 Dec 2024 05:04:19 -0800 Subject: [PATCH] mac: Choose whether system Vulkan is needed at runtime. (#1780) --- CMakeLists.txt | 13 ++- REUSE.toml | 1 + externals/MoltenVK/CMakeLists.txt | 16 ++- externals/MoltenVK/MoltenVK_icd.json | 8 ++ src/video_core/renderer_vulkan/vk_common.h | 4 - .../renderer_vulkan/vk_platform.cpp | 105 +++++++++++------- 6 files changed, 95 insertions(+), 52 deletions(-) create mode 100644 externals/MoltenVK/MoltenVK_icd.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 78d8421a..8f0397e8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -892,11 +892,16 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") endif() if (APPLE) - option(USE_SYSTEM_VULKAN_LOADER "Enables using the system Vulkan loader instead of directly linking with MoltenVK. Useful for loading validation layers." OFF) - if (USE_SYSTEM_VULKAN_LOADER) - target_compile_definitions(shadps4 PRIVATE USE_SYSTEM_VULKAN_LOADER=1) + if (ENABLE_QT_GUI) + # Include MoltenVK in the app bundle, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. + target_sources(shadps4 PRIVATE externals/MoltenVK/MoltenVK_icd.json) + set_source_files_properties(externals/MoltenVK/MoltenVK_icd.json + PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d) + add_custom_command(TARGET shadps4 POST_BUILD + COMMAND cmake -E copy $ $/Contents/Frameworks/libMoltenVK.dylib) + set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") else() - # Link MoltenVK for Vulkan support + # For non-bundled SDL build, just do a normal library link. target_link_libraries(shadps4 PRIVATE MoltenVK) endif() diff --git a/REUSE.toml b/REUSE.toml index 747679c8..cba63adf 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -15,6 +15,7 @@ path = [ "documents/changelog.md", "documents/Quickstart/2.png", "documents/Screenshots/*", + "externals/MoltenVK/MoltenVK_icd.json", "scripts/ps4_names.txt", "src/images/about_icon.png", "src/images/controller_icon.png", diff --git a/externals/MoltenVK/CMakeLists.txt b/externals/MoltenVK/CMakeLists.txt index 00e3231e..908c2847 100644 --- a/externals/MoltenVK/CMakeLists.txt +++ b/externals/MoltenVK/CMakeLists.txt @@ -1,17 +1,29 @@ # SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -# Prepare version information +# Prepare MoltenVK Git revision find_package(Git) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD OUTPUT_VARIABLE MVK_GIT_REV + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) endif() -set(MVK_VERSION "1.2.12") set(MVK_GENERATED_INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/Generated) file(WRITE ${MVK_GENERATED_INCLUDES}/mvkGitRevDerived.h "static const char* mvkRevString = \"${MVK_GIT_REV}\";") +message(STATUS "MoltenVK revision: ${MVK_GIT_REV}") + +# Prepare MoltenVK version +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/MoltenVK/API/mvk_private_api.h MVK_PRIVATE_API) +string(REGEX MATCH "#define MVK_VERSION_MAJOR [0-9]+" MVK_VERSION_MAJOR_LINE "${MVK_PRIVATE_API}") +string(REGEX MATCH "[0-9]+" MVK_VERSION_MAJOR "${MVK_VERSION_MAJOR_LINE}") +string(REGEX MATCH "#define MVK_VERSION_MINOR [0-9]+" MVK_VERSION_MINOR_LINE "${MVK_PRIVATE_API}") +string(REGEX MATCH "[0-9]+" MVK_VERSION_MINOR "${MVK_VERSION_MINOR_LINE}") +string(REGEX MATCH "#define MVK_VERSION_PATCH [0-9]+" MVK_VERSION_PATCH_LINE "${MVK_PRIVATE_API}") +string(REGEX MATCH "[0-9]+" MVK_VERSION_PATCH "${MVK_VERSION_PATCH_LINE}") +set(MVK_VERSION "${MVK_VERSION_MAJOR}.${MVK_VERSION_MINOR}.${MVK_VERSION_PATCH}") +message(STATUS "MoltenVK version: ${MVK_VERSION}") # Find required system libraries find_library(APPKIT_LIBRARY AppKit REQUIRED) diff --git a/externals/MoltenVK/MoltenVK_icd.json b/externals/MoltenVK/MoltenVK_icd.json new file mode 100644 index 00000000..2c331926 --- /dev/null +++ b/externals/MoltenVK/MoltenVK_icd.json @@ -0,0 +1,8 @@ +{ + "file_format_version": "1.0.0", + "ICD": { + "library_path": "../../../Frameworks/libMoltenVK.dylib", + "api_version": "1.2.0", + "is_portability_driver": true + } +} diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h index 9178aeb6..5fe199e0 100644 --- a/src/video_core/renderer_vulkan/vk_common.h +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -3,10 +3,6 @@ #pragma once -#if defined(__APPLE__) && !USE_SYSTEM_VULKAN_LOADER -#define VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL 0 -#endif - // Include vulkan-hpp header #define VK_ENABLE_BETA_EXTENSIONS #define VK_NO_PROTOTYPES diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index f5e51361..dbdabe0d 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -14,6 +14,7 @@ #endif #include +#include #include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" @@ -21,15 +22,6 @@ #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_platform.h" -#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL -static vk::detail::DynamicLoader dl; -#else -extern "C" { -VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, - const char* pName); -} -#endif - namespace Vulkan { static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; @@ -199,15 +191,57 @@ std::vector GetInstanceExtensions(Frontend::WindowSystemType window return extensions; } +std::vector GetInstanceLayers(bool enable_validation, bool enable_crash_diagnostic) { + const auto [properties_result, properties] = vk::enumerateInstanceLayerProperties(); + if (properties_result != vk::Result::eSuccess || properties.empty()) { + LOG_ERROR(Render_Vulkan, "Failed to query layer properties: {}", + vk::to_string(properties_result)); + return {}; + } + + std::vector layers; + layers.reserve(2); + + if (enable_validation) { + layers.push_back(VALIDATION_LAYER_NAME); + } + if (enable_crash_diagnostic) { + layers.push_back(CRASH_DIAGNOSTIC_LAYER_NAME); + } + + // Sanitize layer list + std::erase_if(layers, [&](const char* layer) -> bool { + const auto it = std::ranges::find_if(properties, [layer](const auto& prop) { + return std::strcmp(layer, prop.layerName) == 0; + }); + if (it == properties.end()) { + LOG_ERROR(Render_Vulkan, "Requested layer {} is not available", layer); + return true; + } + return false; + }); + + return layers; +} + vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool enable_validation, bool enable_crash_diagnostic) { LOG_INFO(Render_Vulkan, "Creating vulkan instance"); -#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL - auto vkGetInstanceProcAddr = - dl.getProcAddress("vkGetInstanceProcAddr"); +#ifdef __APPLE__ + // If the Vulkan loader exists in /usr/local/lib, give it priority. The Vulkan SDK + // installs it here by default but it is not in the default library search path. + // The loader has a clause to check for it, but at a lower priority than the bundled + // libMoltenVK.dylib, so we need to handle it ourselves to give it priority. + static const std::string usr_local_path = "/usr/local/lib/libvulkan.dylib"; + static vk::detail::DynamicLoader dl = std::filesystem::exists(usr_local_path) + ? vk::detail::DynamicLoader(usr_local_path) + : vk::detail::DynamicLoader(); +#else + static vk::detail::DynamicLoader dl; #endif - VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); + VULKAN_HPP_DEFAULT_DISPATCHER.init( + dl.getProcAddress("vkGetInstanceProcAddr")); const auto [available_version_result, available_version] = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion @@ -230,38 +264,25 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e .apiVersion = available_version, }; - u32 num_layers = 0; - std::array layers; + const auto layers = GetInstanceLayers(enable_validation, enable_crash_diagnostic); - vk::Bool32 enable_force_barriers = vk::False; - const char* log_path{}; + const std::string extensions_string = fmt::format("{}", fmt::join(extensions, ", ")); + const std::string layers_string = fmt::format("{}", fmt::join(layers, ", ")); + LOG_INFO(Render_Vulkan, "Enabled instance extensions: {}", extensions_string); + LOG_INFO(Render_Vulkan, "Enabled instance layers: {}", layers_string); -#if VULKAN_HPP_ENABLE_DYNAMIC_LOADER_TOOL - if (enable_validation) { - layers[num_layers++] = VALIDATION_LAYER_NAME; - } + // Validation settings + vk::Bool32 enable_sync = Config::vkValidationSyncEnabled() ? vk::True : vk::False; + vk::Bool32 enable_gpuav = Config::vkValidationSyncEnabled() ? vk::True : vk::False; + const char* gpuav_mode = + Config::vkValidationGpuEnabled() ? "GPU_BASED_GPU_ASSISTED" : "GPU_BASED_NONE"; - if (enable_crash_diagnostic) { - layers[num_layers++] = CRASH_DIAGNOSTIC_LAYER_NAME; - static const auto crash_diagnostic_path = - Common::FS::GetUserPathString(Common::FS::PathType::LogDir); - log_path = crash_diagnostic_path.c_str(); - enable_force_barriers = vk::True; - } -#else - if (enable_validation || enable_crash_diagnostic) { - LOG_WARNING(Render_Vulkan, - "Skipping loading Vulkan layers as dynamic loading is not enabled."); - } -#endif + // Crash diagnostics settings + static const auto crash_diagnostic_path = + Common::FS::GetUserPathString(Common::FS::PathType::LogDir); + const char* log_path = crash_diagnostic_path.c_str(); + vk::Bool32 enable_force_barriers = vk::True; - vk::Bool32 enable_sync = - enable_validation && Config::vkValidationSyncEnabled() ? vk::True : vk::False; - vk::Bool32 enable_gpuav = - enable_validation && Config::vkValidationSyncEnabled() ? vk::True : vk::False; - const char* gpuav_mode = enable_validation && Config::vkValidationGpuEnabled() - ? "GPU_BASED_GPU_ASSISTED" - : "GPU_BASED_NONE"; const std::array layer_setings = { vk::LayerSettingEXT{ .pLayerName = VALIDATION_LAYER_NAME, @@ -331,7 +352,7 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e vk::StructureChain instance_ci_chain = { vk::InstanceCreateInfo{ .pApplicationInfo = &application_info, - .enabledLayerCount = num_layers, + .enabledLayerCount = static_cast(layers.size()), .ppEnabledLayerNames = layers.data(), .enabledExtensionCount = static_cast(extensions.size()), .ppEnabledExtensionNames = extensions.data(),