From 670571421103fadcf445cbfa3c41e19841b19806 Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sat, 9 May 2026 17:03:15 +0300 Subject: [PATCH] Vulkan: add support for custom drivers on android --- .gitmodules | 3 + CMakeLists.txt | 6 + dependencies/libadrenotools | 1 + src/Cafe/CMakeLists.txt | 3 + src/Cafe/GameProfile/GameProfile.cpp | 19 +++ src/Cafe/GameProfile/GameProfile.h | 22 +++ .../HW/Latte/Renderer/Vulkan/VulkanAPI.cpp | 136 +++++++++++++++++- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 5 + .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 +- src/config/ActiveSettings.cpp | 11 ++ src/config/ActiveSettings.h | 18 +++ src/config/CemuConfig.cpp | 9 ++ src/config/CemuConfig.h | 14 ++ 13 files changed, 242 insertions(+), 9 deletions(-) create mode 160000 dependencies/libadrenotools diff --git a/.gitmodules b/.gitmodules index 82e53209..8da1208a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "dependencies/xbyak_aarch64"] path = dependencies/xbyak_aarch64 url = https://github.com/fujitsu/xbyak_aarch64 +[submodule "dependencies/libadrenotools"] + path = dependencies/libadrenotools + url = https://github.com/bylaws/libadrenotools diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d26311..ca6bff81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,4 +286,10 @@ if (NOT ZArchive_FOUND) add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) endif() +if(ANDROID) + if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)") + add_subdirectory("dependencies/libadrenotools" EXCLUDE_FROM_ALL) + endif() +endif() + add_subdirectory(src) diff --git a/dependencies/libadrenotools b/dependencies/libadrenotools new file mode 160000 index 00000000..8fae8ce2 --- /dev/null +++ b/dependencies/libadrenotools @@ -0,0 +1 @@ +Subproject commit 8fae8ce254dfc1344527e05301e43f37dea2df80 diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 91c16cba..758dfe51 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -623,6 +623,9 @@ if(ANDROID) Filesystem/fscDeviceAndroidSAF.cpp Filesystem/fscDeviceAndroidSAF.h ) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") + target_link_libraries(CemuCafe PRIVATE adrenotools) + endif() endif() if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index 5c15d7c3..3aef89bf 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -275,6 +275,17 @@ bool GameProfile::Load(uint64_t title_id) } } +#if BOOST_PLAT_ANDROID + else if (boost::iequals(iniParser.GetCurrentSectionName(), "AndroidDriver")) + { + gameProfile_loadEnumOption(iniParser, "mode", m_driverSetting.mode); + + if (m_driverSetting.mode == DriverSettingMode::Custom) + { + m_driverSetting.customPath = iniParser.FindOption("customPath"); + } + } +#endif } return true; } @@ -329,6 +340,14 @@ void GameProfile::Save(uint64_t title_id) fs->writeLine(""); +#if BOOST_PLAT_ANDROID + fs->writeLine("[AndroidDriver]"); + fs->writeLine(fmt::format("{} = {}", "mode", m_driverSetting.mode).c_str()); + if (m_driverSetting.mode == DriverSettingMode::Custom && m_driverSetting.customPath.has_value()) + fs->writeLine(fmt::format("{} = {}", "customPath", m_driverSetting.customPath.value()).c_str()); + fs->writeLine(""); +#endif + #undef WRITE_OPTIONAL_ENTRY #undef WRITE_ENTRY #undef WRITE_ENTRY_NUMBERED diff --git a/src/Cafe/GameProfile/GameProfile.h b/src/Cafe/GameProfile/GameProfile.h index 06c2a13b..2742e339 100644 --- a/src/Cafe/GameProfile/GameProfile.h +++ b/src/Cafe/GameProfile/GameProfile.h @@ -43,6 +43,28 @@ public: [[nodiscard]] const std::array< std::optional, 8>& GetControllerProfile() const { return m_controllerProfile; } +#if BOOST_PLAT_ANDROID + struct DriverSetting + { + DriverSettingMode mode = DriverSettingMode::Global; + std::optional customPath; + }; + + [[nodiscard]] DriverSetting GetDriverSetting() const + { + return m_driverSetting; + } + + void SetDriverSetting(DriverSetting driverSetting) + { + m_driverSetting = driverSetting; + } + + private: + DriverSetting m_driverSetting; + +#endif + private: uint64_t m_title_id = 0; bool m_is_loaded = false; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp index b7dc5001..cbe71ad6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp @@ -144,10 +144,114 @@ bool InitializeDeviceVulkan(VkDevice device) #else +void* g_vulkan_so = nullptr; + +#if BOOST_PLAT_ANDROID +bool SupportsLoadingCustomDriver() +{ +#ifdef __aarch64__ + std::error_code ec; + return fs::exists("/dev/kgsl-3d0", ec); +#else + return false; +#endif +} + +#ifdef __aarch64__ + +constexpr auto CUSTOM_DRIVER_LIB_NAME = "custom_vulkan.so"; + +#include +#include +#include +#include "config/ActiveSettings.h" +#include "Cafe/GameProfile/GameProfile.h" + +std::string get_custom_driver_lib_name(const fs::path& driver_path) +{ + static constexpr auto LIB_NAME_MEMBER = "libraryName"; + std::ifstream in(driver_path / "meta.json"); + if (!in.is_open()) + return {}; + rapidjson::IStreamWrapper str(in); + rapidjson::Document doc; + doc.ParseStream(str); + + if (!doc.HasMember(LIB_NAME_MEMBER) || !doc[LIB_NAME_MEMBER].IsString()) + return {}; + + std::string lib_name = doc[LIB_NAME_MEMBER].GetString(); + + std::error_code ec; + if (!fs::exists(driver_path / lib_name, ec)) + return {}; + + return lib_name; +} + +std::optional get_custom_driver_path() +{ + const auto& driverSetting = g_current_game_profile->GetDriverSetting(); + if (driverSetting.mode == DriverSettingMode::System) + { + return {}; + } + + std::error_code ec; + if (driverSetting.mode == DriverSettingMode::Custom && + driverSetting.customPath.has_value() && + fs::exists(driverSetting.customPath.value(), ec)) + { + return driverSetting.customPath; + } + + return GetConfig().custom_driver_path; +} + +void* load_custom_driver() +{ + std::optional driver_path = get_custom_driver_path(); + + if (!driver_path.has_value() || driver_path->empty()) + return nullptr; + std::string driver_name = get_custom_driver_lib_name(driver_path.value()); + if (driver_name.empty()) + return nullptr; + + std::error_code ec; + fs::copy(fs::path(driver_path.value()) / driver_name, ActiveSettings::GetInternalPath(CUSTOM_DRIVER_LIB_NAME), fs::copy_options::overwrite_existing, ec); + + void* vulkan_so = adrenotools_open_libvulkan( + RTLD_NOW | RTLD_LOCAL, + ADRENOTOOLS_DRIVER_CUSTOM, + nullptr, + (ActiveSettings::GetNativeLibPath().string() + "/").c_str(), + (ActiveSettings::GetInternalPath().string() + "/").c_str(), + CUSTOM_DRIVER_LIB_NAME, + nullptr, + nullptr); + if (!vulkan_so) + { + cemuLog_log(LogType::Force, "Failed to load custom driver"); + return nullptr; + } + cemuLog_log(LogType::Force, "Loaded custom driver"); + return vulkan_so; +} +#endif // __aarch64__ + +#endif // BOOST_PLAT_ANDROID + void* dlopen_vulkan_loader() { #if BOOST_OS_LINUX || BOOST_OS_BSD - void* vulkan_so = dlopen("libvulkan.so", RTLD_NOW); + static void* vulkan_so = nullptr; +#if BOOST_PLAT_ANDROID && defined(__aarch64__) + vulkan_so = load_custom_driver(); + if (vulkan_so) + return vulkan_so; +#endif + vulkan_so = dlopen("libvulkan.so", RTLD_NOW); if(!vulkan_so) vulkan_so = dlopen("libvulkan.so.1", RTLD_NOW); #elif BOOST_OS_MACOS @@ -158,17 +262,19 @@ void* dlopen_vulkan_loader() bool InitializeGlobalVulkan() { - void* vulkan_so = dlopen_vulkan_loader(); + g_vulkan_so = dlopen_vulkan_loader(); - if(g_vulkan_available) + if (g_vulkan_available) return true; - if (!vulkan_so) + if (!g_vulkan_so) { cemuLog_log(LogType::Force, "Vulkan loader not available."); return false; } + void* vulkan_so = g_vulkan_so; + #define VKFUNC_INIT #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" @@ -177,14 +283,30 @@ bool InitializeGlobalVulkan() cemuLog_log(LogType::Force, "vkEnumerateInstanceVersion not available. Outdated graphics driver or Vulkan runtime?"); return false; } - + g_vulkan_available = true; return true; } +void CleanupGlobalVulkan() +{ + if (g_vulkan_so) + { + dlclose(g_vulkan_so); + g_vulkan_so = nullptr; + } + + g_vulkan_available = false; + +#if BOOST_PLAT_ANDROID && defined(__aarch64__) + std::error_code ec; + fs::remove(ActiveSettings::GetInternalPath(CUSTOM_DRIVER_LIB_NAME), ec); +#endif +} + bool InitializeInstanceVulkan(VkInstance instance) { - void* vulkan_so = dlopen_vulkan_loader(); + void* vulkan_so = g_vulkan_so; if (!vulkan_so) return false; @@ -196,7 +318,7 @@ bool InitializeInstanceVulkan(VkInstance instance) bool InitializeDeviceVulkan(VkDevice device) { - void* vulkan_so = dlopen_vulkan_loader(); + void* vulkan_so = g_vulkan_so; if (!vulkan_so) return false; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index cde4162e..ee85f720 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -10,6 +10,11 @@ bool InitializeGlobalVulkan(); bool InitializeInstanceVulkan(VkInstance instance); bool InitializeDeviceVulkan(VkDevice device); + +#if BOOST_PLAT_ANDROID +bool SupportsLoadingCustomDriver(); +#endif + extern bool g_vulkan_available; #endif diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a38a83ab..75e87776 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2244,10 +2244,10 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor const VkPipelineStageFlags semWaitStageMask[2] = { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT }; VkSemaphore waitSemArray[2]; submitInfo.waitSemaphoreCount = 0; - if (m_numSubmittedCmdBuffers > 0) - waitSemArray[submitInfo.waitSemaphoreCount++] = prevSem; // wait on semaphore from previous submit if (waitSemaphore != VK_NULL_HANDLE) waitSemArray[submitInfo.waitSemaphoreCount++] = waitSemaphore; + if (m_numSubmittedCmdBuffers > 0) + waitSemArray[submitInfo.waitSemaphoreCount++] = prevSem; // wait on semaphore from previous submit submitInfo.pWaitDstStageMask = semWaitStageMask; submitInfo.pWaitSemaphores = waitSemArray; diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 08def5a3..8173d4f6 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -301,3 +301,14 @@ fs::path ActiveSettings::GetDefaultMLCPath() return GetUserDataPath("mlc01"); } +#if BOOST_PLAT_ANDROID +void ActiveSettings::SetNativeLibPath(const fs::path& nativeLibPath) +{ + s_native_lib_path = nativeLibPath; +} + +void ActiveSettings::SetInternalDir(const fs::path& internalDirPath) +{ + s_internal_dir_path = internalDirPath; +} +#endif diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index 19cce79b..5a6f3e1e 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -56,6 +56,15 @@ public: [[nodiscard]] static fs::path GetMlcPath(); +#if BOOST_PLAT_ANDROID + template + [[nodiscard]] static fs::path GetNativeLibPath(TArgs&&... args){ return GetPath(s_native_lib_path, std::forward(args)...); }; + + template + [[nodiscard]] static fs::path GetInternalPath(TArgs&&... args){ return GetPath(s_internal_dir_path, std::forward(args)...); }; +#endif + + template [[nodiscard]] static fs::path GetMlcPath(TArgs&&... args){ return GetPath(GetMlcPath(), std::forward(args)...); }; static bool IsCustomMlcPath(); @@ -73,8 +82,17 @@ private: inline static fs::path s_data_path; inline static fs::path s_executable_filename; // cemu.exe inline static fs::path s_mlc_path; +#if BOOST_PLAT_ANDROID + inline static fs::path s_native_lib_path; + inline static fs::path s_internal_dir_path; +#endif public: + +#if BOOST_PLAT_ANDROID + static void SetNativeLibPath(const fs::path& nativeLibPath); + static void SetInternalDir(const fs::path& internalDirPath); +#endif // can be called before Init [[nodiscard]] static bool IsPortableMode(); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 139c290c..17d45b97 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -290,6 +290,11 @@ XMLConfigParser CemuConfig::Load(XMLConfigParser& parser) emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad); + +#if BOOST_PLAT_ANDROID + custom_driver_path = parser.get("custom_driver_path", ""); +#endif + return parser; } @@ -454,6 +459,10 @@ XMLConfigParser CemuConfig::Save(XMLConfigParser& parser) usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue()); +#if BOOST_PLAT_ANDROID + config.set("custom_driver_path", custom_driver_path.GetValue()); +#endif + return config; } diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 48c396f2..f23f4214 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -213,6 +213,16 @@ enum class CrashDump ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif +#if BOOST_PLAT_ANDROID +enum class DriverSettingMode +{ + Global, + System, + Custom, +}; +ENABLE_ENUM_ITERATORS(DriverSettingMode, DriverSettingMode::Global, DriverSettingMode::Custom); +#endif + template <> struct fmt::formatter : formatter { template @@ -419,6 +429,10 @@ struct CemuConfig #undef DISABLE_SCREENSAVER_DEFAULT ConfigValue play_boot_sound{false}; +#if BOOST_PLAT_ANDROID + ConfigValue custom_driver_path{}; +#endif + std::vector game_paths; std::mutex game_cache_entries_mutex; std::vector game_cache_entries;