diff --git a/.gitmodules b/.gitmodules index e8637dee5..77b577576 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "core/deps/discord-rpc"] path = core/deps/discord-rpc url = https://github.com/flyinghead/discord-rpc +[submodule "core/deps/libadrenotools"] + path = core/deps/libadrenotools + url = https://github.com/bylaws/libadrenotools diff --git a/CMakeLists.txt b/CMakeLists.txt index 9749fbee3..1bf2241af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,9 +105,10 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") string(REPLACE "-" "." MS_VERSION ${MS_VERSION}) string(REGEX REPLACE "\.g[0-9a-f]+" "" MS_VERSION ${MS_VERSION}) string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+" VERSION_3PARTS ${MS_VERSION}) + string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" VERSION_4PARTS ${MS_VERSION}) if(VERSION_3PARTS STREQUAL "") string(APPEND MS_VERSION ".0.0") - else() + elseif(VERSION_4PARTS STREQUAL "") string(APPEND MS_VERSION ".0") endif() endif() @@ -1283,6 +1284,11 @@ if(USE_VULKAN) target_compile_options(VulkanMemoryAllocator INTERFACE $<$,$>:-Wno-nullability-completeness>) target_link_libraries(${PROJECT_NAME} PRIVATE GPUOpen::VulkanMemoryAllocator) + if(ANDROID AND NOT LIBRETRO AND "arm64" IN_LIST ARCHITECTURE) + add_subdirectory(core/deps/libadrenotools) + target_link_libraries(${PROJECT_NAME} PRIVATE adrenotools) + endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE USE_VULKAN HAVE_VULKAN) target_sources(${PROJECT_NAME} PRIVATE core/rend/vulkan/oit/oit_buffer.h @@ -1295,6 +1301,7 @@ if(USE_VULKAN) core/rend/vulkan/oit/oit_renderpass.h core/rend/vulkan/oit/oit_shaders.cpp core/rend/vulkan/oit/oit_shaders.h + core/rend/vulkan/adreno.cpp core/rend/vulkan/buffer.cpp core/rend/vulkan/buffer.h core/rend/vulkan/commandpool.cpp diff --git a/core/archive/ZipArchive.cpp b/core/archive/ZipArchive.cpp index cd279191c..ef10710f2 100644 --- a/core/archive/ZipArchive.cpp +++ b/core/archive/ZipArchive.cpp @@ -47,7 +47,7 @@ ArchiveFile* ZipArchive::OpenFile(const char* name) return nullptr; zip_stat_t stat; zip_stat(zip, name, 0, &stat); - return new ZipArchiveFile(zip_file, stat.size); + return new ZipArchiveFile(zip_file, stat.size, stat.name); } static zip_file *zip_fopen_by_crc(zip_t *za, u32 crc, int flags, zip_uint64_t& index) @@ -77,7 +77,7 @@ ArchiveFile* ZipArchive::OpenFileByCrc(u32 crc) zip_stat_t stat; zip_stat_index(zip, index, 0, &stat); - return new ZipArchiveFile(zip_file, stat.size); + return new ZipArchiveFile(zip_file, stat.size, stat.name); } u32 ZipArchiveFile::Read(void* buffer, u32 length) @@ -104,5 +104,15 @@ ArchiveFile *ZipArchive::OpenFirstFile() return nullptr; zip_stat_t stat; zip_stat_index(zip, 0, 0, &stat); - return new ZipArchiveFile(zipFile, stat.size); + return new ZipArchiveFile(zipFile, stat.size, stat.name); +} + +ArchiveFile *ZipArchive::OpenFileByIndex(size_t index) +{ + zip_file_t *zipFile = zip_fopen_index(zip, index, 0); + if (zipFile == nullptr) + return nullptr; + zip_stat_t stat; + zip_stat_index(zip, index, 0, &stat); + return new ZipArchiveFile(zipFile, stat.size, stat.name); } diff --git a/core/archive/ZipArchive.h b/core/archive/ZipArchive.h index 1369a5c48..8b475ab78 100644 --- a/core/archive/ZipArchive.h +++ b/core/archive/ZipArchive.h @@ -31,11 +31,11 @@ public: ArchiveFile* OpenFile(const char* name) override; ArchiveFile* OpenFileByCrc(u32 crc) override; - bool Open(const void *data, size_t size); - ArchiveFile *OpenFirstFile(); - -protected: bool Open(FILE *file) override; + bool Open(const void *data, size_t size); + + ArchiveFile *OpenFirstFile(); + ArchiveFile *OpenFileByIndex(size_t index); private: zip_t *zip = nullptr; @@ -44,8 +44,8 @@ private: class ZipArchiveFile : public ArchiveFile { public: - ZipArchiveFile(zip_file_t *zip_file, size_t length) - : zip_file(zip_file), _length(length) {} + ZipArchiveFile(zip_file_t *zip_file, size_t length, const char *name) + : zip_file(zip_file), _length(length), name(name) {} ~ZipArchiveFile() override { zip_fclose(zip_file); } @@ -53,8 +53,12 @@ public: size_t length() override { return _length; } + const char *getName() override { + return name; + } private: zip_file_t *zip_file; size_t _length; + const char *name; }; diff --git a/core/archive/archive.h b/core/archive/archive.h index 720613f07..49e925e0c 100644 --- a/core/archive/archive.h +++ b/core/archive/archive.h @@ -28,6 +28,7 @@ public: virtual ~ArchiveFile() = default; virtual u32 Read(void *buffer, u32 length) = 0; virtual size_t length() = 0; + virtual const char *getName() { return nullptr; } }; class Archive diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 24aec35e5..20720d33f 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -108,6 +108,7 @@ Option PerPixelLayers("rend.PerPixelLayers", 32); Option NativeDepthInterpolation("rend.NativeDepthInterpolation", false); Option EmulateFramebuffer("rend.EmulateFramebuffer", false); Option FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true); +Option CustomGpuDriver("rend.CustomGpuDriver", false); #ifdef VIDEO_ROUTING Option VideoRouting("rend.VideoRouting", false); Option VideoRoutingScale("rend.VideoRoutingScale", false); diff --git a/core/cfg/option.h b/core/cfg/option.h index 50f80bf17..05d30b6f2 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -474,6 +474,7 @@ extern Option DupeFrames; extern Option NativeDepthInterpolation; extern Option EmulateFramebuffer; extern Option FixUpscaleBleedingEdge; +extern Option CustomGpuDriver; #ifdef VIDEO_ROUTING extern Option VideoRouting; extern Option VideoRoutingScale; diff --git a/core/deps/libadrenotools b/core/deps/libadrenotools new file mode 160000 index 000000000..5deac9f1a --- /dev/null +++ b/core/deps/libadrenotools @@ -0,0 +1 @@ +Subproject commit 5deac9f1ab2bd833ad664bc3386ac1e8998cecb3 diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 77927f6b5..cd009ab8a 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -51,6 +51,9 @@ #ifdef __ANDROID__ #include "gui_android.h" +#if HOST_CPU == CPU_ARM64 && USE_VULKAN +#include "rend/vulkan/adreno.h" +#endif #endif #ifdef _WIN32 @@ -2762,6 +2765,75 @@ static void gui_display_settings() ImGui::Text("Driver Name: %s", GraphicsContext::Instance()->getDriverName().c_str()); ImGui::Text("Version: %s", GraphicsContext::Instance()->getDriverVersion().c_str()); +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 && USE_VULKAN + if (isVulkan(config::RendererType)) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + if (config::CustomGpuDriver) + { + std::string name, description, vendor, version; + if (getCustomGpuDriverInfo(name, description, vendor, version)) + { + ImGui::Text("Custom Driver:"); + ImGui::Indent(); + ImGui::Text("%s - %s", name.c_str(), description.c_str()); + ImGui::Text("%s - %s", vendor.c_str(), version.c_str()); + ImGui::Unindent(); + } + + if (ImGui::Button("Use Default Driver")) { + config::CustomGpuDriver = false; + ImGui::OpenPopup("Reset Vulkan"); + } + } + else if (ImGui::Button("Upload Custom Driver")) + ImGui::OpenPopup("Select custom GPU driver"); + + static bool driverDirty; + const auto& callback = [](bool cancelled, std::string selection) { + if (!cancelled) { + try { + uploadCustomGpuDriver(selection); + config::CustomGpuDriver = true; + driverDirty = true; + } catch (const FlycastException& e) { + gui_error(e.what()); + config::CustomGpuDriver = false; + } + } + return true; + }; + select_file_popup("Select custom GPU driver", callback, true, "zip"); + + if (driverDirty) { + ImGui::OpenPopup("Reset Vulkan"); + driverDirty = false; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); + if (ImGui::BeginPopupModal("Reset Vulkan", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) + { + ImGui::Text("Do you want to reset Vulkan to use new driver?"); + ImGui::NewLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20 * settings.display.uiScale, ImGui::GetStyle().ItemSpacing.y)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); + if (ImGui::Button("Yes")) + { + mainui_reinit(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) + ImGui::CloseCurrentPopup(); + ImGui::PopStyleVar(2); + + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + + ImGui::PopStyleVar(); + } +#endif ImGui::PopStyleVar(); ImGui::EndTabItem(); } diff --git a/core/rend/vulkan/adreno.cpp b/core/rend/vulkan/adreno.cpp new file mode 100644 index 000000000..928964a48 --- /dev/null +++ b/core/rend/vulkan/adreno.cpp @@ -0,0 +1,209 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "build.h" +#if defined(__ANDROID__) && !defined(LIBRETRO) && HOST_CPU == CPU_ARM64 +#include "adreno.h" +#include +#include "cfg/option.h" +#include +#include "json.hpp" +using namespace nlohmann; +#include "archive/ZipArchive.h" +#include "oslib/directory.h" +#include "stdclass.h" + +std::string getNativeLibraryPath(); +std::string getFilesPath(); + +const std::string DRIVER_PATH = "/gpu_driver/"; +static void *libvulkanHandle; + +static json loadDriverMeta() +{ + std::string fullPath = getFilesPath() + DRIVER_PATH + "meta.json"; + FILE *f = nowide::fopen(fullPath.c_str(), "rt"); + if (f == nullptr) { + WARN_LOG(RENDERER, "Can't open %s", fullPath.c_str()); + return json{}; + } + std::string content(4096, '\0'); + size_t l = fread(content.data(), 1, content.size(), f); + fclose(f); + if (l <= 0) { + WARN_LOG(RENDERER, "Can't read %s", fullPath.c_str()); + return json{}; + } + content.resize(l); + try { + return json::parse(content); + } catch (const json::exception& e) { + WARN_LOG(COMMON, "Corrupted meta.json file: %s", e.what()); + return json{}; + } +} + +static std::string getLibraryName() +{ + json v = loadDriverMeta(); + std::string name; + try { + v.at("libraryName").get_to(name); + } catch (const json::exception& e) { + } + return name; +} + +PFN_vkGetInstanceProcAddr loadVulkanDriver() +{ + // If the user has selected a custom driver, try to load it + if (config::CustomGpuDriver) + { + std::string libName = getLibraryName(); + if (!libName.empty()) + { + std::string driverPath = getFilesPath() + DRIVER_PATH; + std::string tmpLibDir = getFilesPath() + "/tmp/"; + mkdir(tmpLibDir.c_str(), 0755); + //std::string redirectDir = get_writable_data_path(""); + libvulkanHandle = adrenotools_open_libvulkan( + RTLD_NOW | RTLD_LOCAL, + ADRENOTOOLS_DRIVER_CUSTOM /* | ADRENOTOOLS_DRIVER_FILE_REDIRECT */, + tmpLibDir.c_str(), + getNativeLibraryPath().c_str(), + driverPath.c_str(), + libName.c_str(), + nullptr, //redirectDir.c_str(), + nullptr); + if (libvulkanHandle == nullptr) { + char *error = dlerror(); + WARN_LOG(RENDERER, "Failed to load custom Vulkan driver %s%s: %s", driverPath.c_str(), libName.c_str(), error ? error : ""); + } + } + } + if (libvulkanHandle == nullptr) + { + libvulkanHandle = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); + if (libvulkanHandle == nullptr) + { + char *error = dlerror(); + WARN_LOG(RENDERER, "Failed to load system Vulkan driver: %s", error ? error : ""); + return nullptr; + } + } + + return reinterpret_cast(dlsym(libvulkanHandle, "vkGetInstanceProcAddr")); +} + +void unloadVulkanDriver() +{ + if (libvulkanHandle != nullptr) { + dlclose(libvulkanHandle); + libvulkanHandle = nullptr; + } +} + +bool getCustomGpuDriverInfo(std::string& name, std::string& description, std::string& vendor, std::string& version) +{ + json j = loadDriverMeta(); + try { + j.at("name").get_to(name); + } catch (const json::exception& e) { + return false; + } + try { + j.at("description").get_to(description); + } catch (const json::exception& e) { + description = ""; + } + try { + j.at("vendor").get_to(vendor); + } catch (const json::exception& e) { + vendor = ""; + } + try { + j.at("driverVersion").get_to(version); + } catch (const json::exception& e) { + version = ""; + } + + return true; +} + +void uploadCustomGpuDriver(const std::string& zipPath) +{ + FILE *zipf = nowide::fopen(zipPath.c_str(), "rb"); + if (zipf == nullptr) + throw FlycastException("Can't open zip file"); + ZipArchive archive; + if (!archive.Open(zipf)) + throw FlycastException("Invalid zip file"); + std::string fullPath = getFilesPath() + DRIVER_PATH; + flycast::mkdir(fullPath.c_str(), 0755); + // Clean driver directory + DIR *dir = flycast::opendir(fullPath.c_str()); + if (dir != nullptr) + { + while (true) + { + dirent *direntry = flycast::readdir(dir); + if (direntry == nullptr) + break; + std::string name = direntry->d_name; + if (name == "." || name == "..") + continue; + name = fullPath + name; + unlink(name.c_str()); + } + } + // Extract and save files + for (size_t i = 0; ; i++) + { + ArchiveFile *afile = archive.OpenFileByIndex(i); + if (afile == nullptr) + break; + FILE *f = fopen((fullPath + afile->getName()).c_str(), "wb"); + if (f == nullptr) { + delete afile; + throw FlycastException("Can't save files"); + } + u8 buf[8_KB]; + while (true) + { + u32 len = afile->Read(buf, sizeof(buf)); + if (len < 0) + { + fclose(f); + delete afile; + throw FlycastException("Can't read zip"); + } + if (len == 0) + break; + if (fwrite(buf, 1, len, f) != len) + { + fclose(f); + delete afile; + throw FlycastException("Can't save files"); + } + } + fclose(f); + delete afile; + } +} + +#endif // __ANDROID__ && !LIBRETRO && arm64 diff --git a/core/rend/vulkan/adreno.h b/core/rend/vulkan/adreno.h new file mode 100644 index 000000000..261b0af8c --- /dev/null +++ b/core/rend/vulkan/adreno.h @@ -0,0 +1,25 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#pragma once +#include "vulkan.h" + +PFN_vkGetInstanceProcAddr loadVulkanDriver(); +void unloadVulkanDriver(); +bool getCustomGpuDriverInfo(std::string& name, std::string& description, std::string& vendor, std::string& version); +void uploadCustomGpuDriver(const std::string& zipPath); diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index a46d48ca7..48d4b61eb 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -33,6 +33,9 @@ #include "oslib/oslib.h" #include "vulkan_driver.h" #include "rend/transform_matrix.h" +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 +#include "adreno.h" +#endif #if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE @@ -139,8 +142,13 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co try { #if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 + vkGetInstanceProcAddr = loadVulkanDriver(); +#else static vk::DynamicLoader dl; - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); + vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); +#endif if (vkGetInstanceProcAddr == nullptr) { ERROR_LOG(RENDERER, "Vulkan entry point vkGetInstanceProcAddr not found"); return false; @@ -1042,6 +1050,9 @@ void VulkanContext::term() #endif #endif instance.reset(); +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 + unloadVulkanDriver(); +#endif } void VulkanContext::DoSwapAutomation() diff --git a/shell/android-studio/flycast/build.gradle b/shell/android-studio/flycast/build.gradle index 7bd6c0390..5d0e455ba 100644 --- a/shell/android-studio/flycast/build.gradle +++ b/shell/android-studio/flycast/build.gradle @@ -73,6 +73,10 @@ android { excludes += ['META-INF/DEPENDENCIES'] } } + packaging { + // This is necessary for libadrenotools custom driver loading + jniLibs.useLegacyPackaging = true + } } dependencies { diff --git a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java index 140cdc9b6..624714741 100644 --- a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java @@ -415,4 +415,12 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. } private static native void register(BaseGLActivity activity); + + public String getNativeLibDir() { + return getApplicationContext().getApplicationInfo().nativeLibraryDir; + } + + public String getInternalFilesDir() { + return getFilesDir().getAbsolutePath(); + } } diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index 0692c7c9a..bb88ef268 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -653,3 +653,19 @@ extern "C" void abort_message(const char* format, ...) ERROR_LOG(BOOT, "%s", buffer); abort(); } + +std::string getNativeLibraryPath() +{ + JNIEnv *env = jni::env(); + jmethodID getNativeLibDir = env->GetMethodID(env->GetObjectClass(g_activity), "getNativeLibDir", "()Ljava/lang/String;"); + jni::String nativeLibDir(jni::env()->CallObjectMethod(g_activity, getNativeLibDir)); + return nativeLibDir; +} + +std::string getFilesPath() +{ + JNIEnv *env = jni::env(); + jmethodID getInternalFilesDir = env->GetMethodID(env->GetObjectClass(g_activity), "getInternalFilesDir", "()Ljava/lang/String;"); + jni::String filesDir(jni::env()->CallObjectMethod(g_activity, getInternalFilesDir)); + return filesDir; +}