diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index fd2e0ed7..1c6eced5 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -129,6 +129,7 @@ target_link_libraries_system(shader_recompiler Boost::intrusive Boost::container # Skyline add_library(skyline SHARED + ${source_DIR}/driver_jni.cpp ${source_DIR}/emu_jni.cpp ${source_DIR}/loader_jni.cpp ${source_DIR}/skyline/common.cpp diff --git a/app/src/main/cpp/driver_jni.cpp b/app/src/main/cpp/driver_jni.cpp new file mode 100644 index 00000000..8c413736 --- /dev/null +++ b/app/src/main/cpp/driver_jni.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include +#include "skyline/common/signal.h" +#include "skyline/common/utils.h" + +extern "C" JNIEXPORT jobjectArray JNICALL Java_emu_skyline_utils_GpuDriverHelper_00024Companion_getSystemDriverInfo(JNIEnv *env, jobject) { + auto libvulkanHandle{dlopen("libvulkan.so", RTLD_NOW)}; + + vk::raii::Context vkContext{reinterpret_cast(dlsym(libvulkanHandle, "vkGetInstanceProcAddr"))}; + vk::raii::Instance vkInstance{vkContext, vk::InstanceCreateInfo{}}; + vk::raii::PhysicalDevice physicalDevice{std::move(vk::raii::PhysicalDevices(vkInstance).front())}; // Get the first device as we aren't expecting multiple GPUs + + auto deviceProperties2{physicalDevice.getProperties2()}; + auto properties{deviceProperties2.get().properties}; + + auto driverId{vk::to_string(deviceProperties2.get().driverID)}; + auto driverVersion{skyline::util::Format("{}.{}.{}", VK_API_VERSION_MAJOR(properties.driverVersion), VK_API_VERSION_MINOR(properties.driverVersion), VK_API_VERSION_PATCH(properties.driverVersion))}; + + auto array = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr); + env->SetObjectArrayElement(array, 0, env->NewStringUTF(driverId.c_str())); + env->SetObjectArrayElement(array, 1, env->NewStringUTF(driverVersion.c_str())); + + return array; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_emu_skyline_utils_GpuDriverHelper_00024Companion_supportsCustomDriverLoading(JNIEnv *env, jobject instance) { + // Check if the Qualcomm KGSL (Kernel Graphics Support Layer) device exists, this indicates that custom drivers can be loaded + constexpr auto KgslPath{"/dev/kgsl-3d0"}; + + return access(KgslPath, F_OK) == 0; +} diff --git a/app/src/main/java/emu/skyline/data/DriverPackageMetadata.kt b/app/src/main/java/emu/skyline/data/DriverPackageMetadata.kt new file mode 100644 index 00000000..1af2d420 --- /dev/null +++ b/app/src/main/java/emu/skyline/data/DriverPackageMetadata.kt @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.data + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.* +import java.io.File + +data class GpuDriverMetadata( + val name : String, + val author : String, + val packageVersion : String, + val vendor : String, + val driverVersion : String, + val minApi : Int, + val description : String, + val libraryName : String, +) { + private constructor(metadataV1 : GpuDriverMetadataV1) : this( + name = metadataV1.name, + author = metadataV1.author, + packageVersion = metadataV1.packageVersion, + vendor = metadataV1.vendor, + driverVersion = metadataV1.driverVersion, + minApi = metadataV1.minApi, + description = metadataV1.description, + libraryName = metadataV1.libraryName, + ) + + val label get() = "${name}-v${packageVersion}" + + companion object { + private const val SCHEMA_VERSION_V1 = 1 + + fun deserialize(metadataFile : File) : GpuDriverMetadata { + val metadataJson = Json.parseToJsonElement(metadataFile.readText()) + + return when (metadataJson.jsonObject["schemaVersion"]?.jsonPrimitive?.intOrNull) { + SCHEMA_VERSION_V1 -> GpuDriverMetadata(Json.decodeFromJsonElement(metadataJson)) + else -> throw SerializationException("Unsupported metadata version") + } + } + } +} + +@Serializable +private data class GpuDriverMetadataV1( + val schemaVersion : Int, + val name : String, + val author : String, + val packageVersion : String, + val vendor : String, + val driverVersion : String, + val minApi : Int, + val description : String, + val libraryName : String, +) diff --git a/app/src/main/java/emu/skyline/utils/GpuDriverHelper.kt b/app/src/main/java/emu/skyline/utils/GpuDriverHelper.kt new file mode 100644 index 00000000..392a6c05 --- /dev/null +++ b/app/src/main/java/emu/skyline/utils/GpuDriverHelper.kt @@ -0,0 +1,225 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.utils + +import android.content.Context +import android.os.Build +import android.util.Log +import emu.skyline.R +import emu.skyline.data.GpuDriverMetadata +import emu.skyline.getPublicFilesDir +import kotlinx.serialization.SerializationException +import java.io.File +import java.io.IOException +import java.io.InputStream + +private const val GPU_DRIVER_DIRECTORY = "gpu_drivers" +private const val GPU_DRIVER_FILE_REDIRECT_DIR = "gpu/vk_file_redirect" +private const val GPU_DRIVER_INSTALL_TEMP_DIR = "driver_temp" +private const val GPU_DRIVER_META_FILE = "meta.json" +private const val TAG = "GPUDriverHelper" + +interface GpuDriverHelper { + companion object { + /** + * Returns information about the system GPU driver. + * @return An array containing the driver vendor and the driver version, in this order, or `null` if an error occurred + */ + private external fun getSystemDriverInfo() : Array? + + /** + * Queries the driver for custom driver loading support. + * @return `true` if the device supports loading custom drivers, `false` otherwise + */ + external fun supportsCustomDriverLoading() : Boolean + + /** + * Returns the list of installed gpu drivers. + * @return A map from the folder the driver is installed to the metadata of the driver + */ + fun getInstalledDrivers(context : Context) : Map { + val gpuDriverDir = getDriversDirectory(context) + + // A map between the driver location and its metadata + val driverMap = mutableMapOf() + + gpuDriverDir.listFiles()?.forEach { entry -> + // Delete any files that aren't a directory + if (!entry.isDirectory) { + entry.delete() + return@forEach + } + + val metadataFile = File(entry.canonicalPath, GPU_DRIVER_META_FILE) + // Delete entries without metadata + if (!metadataFile.exists()) { + entry.delete() + return@forEach + } + + try { + driverMap[entry] = GpuDriverMetadata.deserialize(metadataFile) + } catch (e : SerializationException) { + Log.w(TAG, "Failed to load gpu driver metadata for ${entry.name}, skipping\n${e.message}") + } + } + + return driverMap + } + + /** + * Fetches metadata about the system driver. + * @return A [GpuDriverMetadata] object containing data about the system driver + */ + fun getSystemDriverMetadata(context : Context) : GpuDriverMetadata { + val systemDriverInfo = getSystemDriverInfo() + + return GpuDriverMetadata( + name = context.getString(R.string.system_driver), + author = "", + packageVersion = "", + vendor = systemDriverInfo?.get(0) ?: "", + driverVersion = systemDriverInfo?.get(1) ?: "", + minApi = 0, + description = context.getString(R.string.system_driver_desc), + libraryName = "" + ) + } + + /** + * Installs the given driver to the emulator's drivers directory. + * @param stream The input stream to read the driver from + * @return The exit status of the installation process + */ + fun installDriver(context : Context, stream : InputStream) : GpuDriverInstallResult { + val installTempDir = File(context.cacheDir.canonicalPath, GPU_DRIVER_INSTALL_TEMP_DIR).apply { + deleteRecursively() + } + + try { + ZipUtils.unzip(stream, installTempDir) + } catch (e : Exception) { + e.printStackTrace() + installTempDir.deleteRecursively() + return GpuDriverInstallResult.INVALID_ARCHIVE + } + + return installUnpackedDriver(context, installTempDir) + } + + /** + * Installs the given driver to the emulator's drivers directory. + * @param file The file to read the driver from + * @return The exit status of the installation process + */ + fun installDriver(context : Context, file : File) : GpuDriverInstallResult { + val installTempDir = File(context.cacheDir.canonicalPath, GPU_DRIVER_INSTALL_TEMP_DIR).apply { + deleteRecursively() + } + + try { + ZipUtils.unzip(file, installTempDir) + } catch (e : Exception) { + e.printStackTrace() + installTempDir.deleteRecursively() + return GpuDriverInstallResult.INVALID_ARCHIVE + } + + return installUnpackedDriver(context, installTempDir) + } + + /** + * Installs the given unpacked driver to the emulator's drivers directory. + * @param unpackDir The location of the unpacked driver + * @return The exit status of the installation process + */ + private fun installUnpackedDriver(context : Context, unpackDir : File) : GpuDriverInstallResult { + val cleanup = { + unpackDir.deleteRecursively() + } + + // Check that the metadata file exists + val metadataFile = File(unpackDir, GPU_DRIVER_META_FILE) + if (!metadataFile.isFile) { + cleanup() + return GpuDriverInstallResult.MISSING_METADATA + } + + // Check that the driver metadata is valid + val driverMetadata = try { + GpuDriverMetadata.deserialize(metadataFile) + } catch (e : SerializationException) { + cleanup() + return GpuDriverInstallResult.INVALID_METADATA + } + + // Check that the device satisfies the driver's minimum Android version requirements + if (Build.VERSION.SDK_INT < driverMetadata.minApi) { + cleanup() + return GpuDriverInstallResult.UNSUPPORTED_ANDROID_VERSION + } + + // Check that the driver is not already installed + val installedDrivers = getInstalledDrivers(context) + val finalInstallDir = File(getDriversDirectory(context), driverMetadata.label) + if (installedDrivers[finalInstallDir] != null) { + cleanup() + return GpuDriverInstallResult.ALREADY_INSTALLED + } + + // Move the driver files to the final location + if (!unpackDir.renameTo(finalInstallDir)) { + cleanup() + throw IOException("Failed to create directory ${finalInstallDir.name}") + } + + return GpuDriverInstallResult.SUCCESS + } + + /** + * Retrieves the library name from the driver metadata for the given driver. + */ + fun getLibraryName(context : Context, driverLabel : String) : String { + val driverDir = File(getDriversDirectory(context), driverLabel) + val metadataFile = File(driverDir, GPU_DRIVER_META_FILE) + return try { + GpuDriverMetadata.deserialize(metadataFile).libraryName + } catch (e : SerializationException) { + Log.w(TAG, "Failed to load library name for driver ${driverLabel}, driver may not exist or have invalid metadata") + "" + } + } + + fun ensureFileRedirectDir(context : Context) { + File(context.getPublicFilesDir(), GPU_DRIVER_FILE_REDIRECT_DIR).apply { + if (!isDirectory) { + delete() + mkdirs() + } + } + } + + /** + * Returns the drivers' folder from + */ + private fun getDriversDirectory(context : Context) = File(context.filesDir.canonicalPath, GPU_DRIVER_DIRECTORY).apply { + // Create the directory if it doesn't exist + if (!isDirectory) { + delete() + mkdirs() + } + } + } +} + +enum class GpuDriverInstallResult { + SUCCESS, + INVALID_ARCHIVE, + MISSING_METADATA, + INVALID_METADATA, + UNSUPPORTED_ANDROID_VERSION, + ALREADY_INSTALLED, +}