vulkan: allow custom GPU driver loading with libadrenotools

Issue #1471
This commit is contained in:
Flyinghead 2024-04-07 12:27:54 +02:00
parent e8340bfa5e
commit a6c4530e22
15 changed files with 384 additions and 11 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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 $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:AppleClang,Clang>>:-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

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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

View File

@ -108,6 +108,7 @@ Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
#ifdef VIDEO_ROUTING
Option<bool, false> VideoRouting("rend.VideoRouting", false);
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);

View File

@ -474,6 +474,7 @@ extern Option<bool> DupeFrames;
extern Option<bool> NativeDepthInterpolation;
extern Option<bool> EmulateFramebuffer;
extern Option<bool> FixUpscaleBleedingEdge;
extern Option<bool> CustomGpuDriver;
#ifdef VIDEO_ROUTING
extern Option<bool, false> VideoRouting;
extern Option<bool, false> VideoRoutingScale;

@ -0,0 +1 @@
Subproject commit 5deac9f1ab2bd833ad664bc3386ac1e8998cecb3

View File

@ -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();
}

209
core/rend/vulkan/adreno.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "build.h"
#if defined(__ANDROID__) && !defined(LIBRETRO) && HOST_CPU == CPU_ARM64
#include "adreno.h"
#include <dlfcn.h>
#include "cfg/option.h"
#include <adrenotools/driver.h>
#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<PFN_vkGetInstanceProcAddr>(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

25
core/rend/vulkan/adreno.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#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);

View File

@ -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<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("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()

View File

@ -73,6 +73,10 @@ android {
excludes += ['META-INF/DEPENDENCIES']
}
}
packaging {
// This is necessary for libadrenotools custom driver loading
jniLibs.useLegacyPackaging = true
}
}
dependencies {

View File

@ -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();
}
}

View File

@ -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;
}