From df7ee9b563bc7c4179b6971b95c54123fd596b5d Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Sat, 28 Feb 2026 07:04:12 -0500 Subject: [PATCH] added custom driver option in settings --- android/app/src/main/cpp/xemu_android.cpp | 12 ++++ .../com/izzy2lost/x1box/SettingsActivity.kt | 58 ++++++++++++++++++- .../src/main/res/layout/activity_settings.xml | 53 +++++++++++++++++ android/app/src/main/res/values/strings.xml | 5 ++ hw/xbox/nv2a/pgraph/vk/instance.c | 37 ++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index 9329049ee1..1de633707d 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -805,6 +805,18 @@ static SetupFiles SyncSetupFiles() { int displayMode = GetPrefInt(env, activity, "setting_display_mode", 0); xemu_android_set_display_mode_setting(displayMode); + const std::string vulkanDriverUri = GetPrefString(env, activity, "setting_vulkan_driver_uri"); + if (!vulkanDriverUri.empty()) { + std::string driverPath = base + "/vulkan_driver.so"; + if (CopyUriToPath(env, activity, vulkanDriverUri, driverPath)) { + chmod(driverPath.c_str(), 0755); + setenv("XEMU_VULKAN_DRIVER", driverPath.c_str(), 1); + LogInfoFmt("Custom Vulkan driver staged: %s", driverPath.c_str()); + } else { + LogError("Failed to copy custom Vulkan driver; using system default"); + } + } + out.config_path = base + "/xemu.toml"; WriteConfigToml(out.config_path, out.mcpx, out.flash, out.hdd, out.dvd, out.eeprom, emuSettings); LogInfoFmt("SyncSetupFiles: config %s", out.config_path.c_str()); diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt index 21364664a4..7469a9e3b0 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -1,8 +1,12 @@ package com.izzy2lost.x1box import android.content.Context +import android.net.Uri import android.os.Bundle +import android.provider.OpenableColumns +import android.widget.TextView import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButtonToggleGroup @@ -12,6 +16,21 @@ class SettingsActivity : AppCompatActivity() { private val prefs by lazy { getSharedPreferences("x1box_prefs", Context.MODE_PRIVATE) } + private var pendingVulkanUri: String? = null + private var pendingVulkanName: String? = null + private var clearVulkan = false + + private lateinit var tvVulkanDriverName: TextView + + private val pickDriver = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> + uri ?: return@registerForActivityResult + pendingVulkanUri = uri.toString() + pendingVulkanName = getFileName(uri) ?: uri.lastPathSegment ?: "custom_driver.so" + clearVulkan = false + tvVulkanDriverName.text = pendingVulkanName + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) @@ -30,6 +49,9 @@ class SettingsActivity : AppCompatActivity() { val switchFpu = findViewById(R.id.switch_hard_fpu) val toggleAudioDriver = findViewById(R.id.toggle_audio_driver) val btnSave = findViewById(R.id.btn_settings_save) + tvVulkanDriverName = findViewById(R.id.tv_vulkan_driver_name) + val btnVulkanBrowse = findViewById(R.id.btn_vulkan_browse) + val btnVulkanClear = findViewById(R.id.btn_vulkan_clear) // Load current values val scale = prefs.getInt("setting_surface_scale", 1) @@ -46,6 +68,10 @@ class SettingsActivity : AppCompatActivity() { else -> toggleDisplayMode.check(R.id.btn_display_stretch) } + tvVulkanDriverName.text = + prefs.getString("setting_vulkan_driver_name", null) + ?: getString(R.string.settings_vulkan_driver_none) + val tcgThread = prefs.getString("setting_tcg_thread", "multi") ?: "multi" if (tcgThread == "single") { toggleThread.check(R.id.btn_thread_single) @@ -65,6 +91,17 @@ class SettingsActivity : AppCompatActivity() { else -> toggleAudioDriver.check(R.id.btn_audio_opensles) } + btnVulkanBrowse.setOnClickListener { + pickDriver.launch(arrayOf("*/*")) + } + + btnVulkanClear.setOnClickListener { + pendingVulkanUri = null + pendingVulkanName = null + clearVulkan = true + tvVulkanDriverName.text = getString(R.string.settings_vulkan_driver_none) + } + btnSave.setOnClickListener { val selectedDisplayMode = when (toggleDisplayMode.checkedButtonId) { R.id.btn_display_4_3 -> 1 @@ -86,7 +123,7 @@ class SettingsActivity : AppCompatActivity() { else -> "openslES" } - prefs.edit() + val edit = prefs.edit() .putInt("setting_display_mode", selectedDisplayMode) .putInt("setting_surface_scale", selectedScale) .putString("setting_tcg_thread", selectedThread) @@ -95,10 +132,27 @@ class SettingsActivity : AppCompatActivity() { .putBoolean("setting_cache_shaders", switchShaders.isChecked) .putBoolean("setting_hard_fpu", switchFpu.isChecked) .putString("setting_audio_driver", selectedAudioDriver) - .apply() + + when { + clearVulkan -> edit + .remove("setting_vulkan_driver_uri") + .remove("setting_vulkan_driver_name") + pendingVulkanUri != null -> edit + .putString("setting_vulkan_driver_uri", pendingVulkanUri) + .putString("setting_vulkan_driver_name", pendingVulkanName) + } + + edit.apply() Toast.makeText(this, R.string.settings_saved, Toast.LENGTH_SHORT).show() finish() } } + + private fun getFileName(uri: Uri): String? { + return contentResolver.query(uri, null, null, null, null)?.use { cursor -> + val col = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (col >= 0 && cursor.moveToFirst()) cursor.getString(col) else null + } + } } diff --git a/android/app/src/main/res/layout/activity_settings.xml b/android/app/src/main/res/layout/activity_settings.xml index 3828ad5797..6dfff464e2 100644 --- a/android/app/src/main/res/layout/activity_settings.xml +++ b/android/app/src/main/res/layout/activity_settings.xml @@ -140,6 +140,59 @@ + + + + + + + + + + + + + + + Save Display + Custom Vulkan Driver + System Default + Browse… + Clear + For Adreno GPUs with Turnip/Mesa. Restart required. Aspect Ratio Stretch 4:3 diff --git a/hw/xbox/nv2a/pgraph/vk/instance.c b/hw/xbox/nv2a/pgraph/vk/instance.c index 0f30b685f3..445f3e6d59 100644 --- a/hw/xbox/nv2a/pgraph/vk/instance.c +++ b/hw/xbox/nv2a/pgraph/vk/instance.c @@ -24,9 +24,38 @@ #ifdef __ANDROID__ #include +#include #endif #include +#ifdef __ANDROID__ +static void *g_custom_vk_handle = NULL; + +static bool android_try_load_custom_vulkan(void) +{ + const char *path = getenv("XEMU_VULKAN_DRIVER"); + if (!path || path[0] == '\0') { + return false; + } + g_custom_vk_handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (!g_custom_vk_handle) { + fprintf(stderr, "Custom Vulkan driver dlopen failed: %s\n", dlerror()); + return false; + } + PFN_vkGetInstanceProcAddr proc = + (PFN_vkGetInstanceProcAddr)dlsym(g_custom_vk_handle, "vkGetInstanceProcAddr"); + if (!proc) { + fprintf(stderr, "Custom Vulkan driver: vkGetInstanceProcAddr not found\n"); + dlclose(g_custom_vk_handle); + g_custom_vk_handle = NULL; + return false; + } + volkInitializeCustom(proc); + fprintf(stderr, "Custom Vulkan driver loaded: %s\n", path); + return true; +} +#endif + #define VkExtensionPropertiesArray GArray #define StringArray GArray @@ -154,7 +183,15 @@ static bool create_instance(PGRAPHState *pg, Error **errp) PGRAPHVkState *r = pg->vk_renderer_state; VkResult result; +#ifdef __ANDROID__ + if (!android_try_load_custom_vulkan()) { + result = volkInitialize(); + } else { + result = VK_SUCCESS; + } +#else result = volkInitialize(); +#endif if (result != VK_SUCCESS) { error_setg(errp, "volkInitialize failed"); return false;