diff options
Diffstat (limited to 'src/android/app')
12 files changed, 167 insertions, 37 deletions
| diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 55abba093..53137b2e2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -261,7 +261,7 @@ object NativeLibrary {      /**       * Begins emulation.       */ -    external fun run(path: String?) +    external fun run(path: String?, programIndex: Int = 0)      // Surface Handling      external fun surfaceChanged(surf: Surface?) @@ -489,6 +489,12 @@ object NativeLibrary {          sEmulationActivity.get()!!.onEmulationStopped(status)      } +    @Keep +    @JvmStatic +    fun onProgramChanged(programIndex: Int) { +        sEmulationActivity.get()!!.onProgramChanged(programIndex) +    } +      /**       * Logs the Yuzu version, Android version and, CPU.       */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 26cddecf4..564aaf305 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -76,7 +76,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {      override fun onDestroy() {          stopForegroundService(this) -        emulationViewModel.clear()          super.onDestroy()      } @@ -446,9 +445,14 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {      }      fun onEmulationStopped(status: Int) { -        if (status == 0) { +        if (status == 0 && emulationViewModel.programChanged.value == -1) {              finish()          } +        emulationViewModel.setEmulationStopped(true) +    } + +    fun onProgramChanged(programIndex: Int) { +        emulationViewModel.setProgramChanged(programIndex)      }      private fun startMotionSensorListener() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 22da1d0e5..1f591ced1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -301,6 +301,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {                  R.id.menu_exit -> {                      emulationState.stop() +                    NativeConfig.reloadGlobalConfig()                      emulationViewModel.setIsEmulationStopping(true)                      binding.drawerLayout.close()                      binding.inGameMenu.requestFocus() @@ -423,10 +424,38 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {                      }                  }              } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.programChanged.collect { +                        if (it != 0) { +                            emulationViewModel.setEmulationStarted(false) +                            binding.drawerLayout.close() +                            binding.drawerLayout +                                .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) +                            ViewUtils.hideView(binding.surfaceInputOverlay) +                            ViewUtils.showView(binding.loadingIndicator) +                        } +                    } +                } +            } +            launch { +                repeatOnLifecycle(Lifecycle.State.CREATED) { +                    emulationViewModel.emulationStopped.collect { +                        if (it && emulationViewModel.programChanged.value != -1) { +                            if (perfStatsUpdater != null) { +                                perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) +                            } +                            emulationState.changeProgram(emulationViewModel.programChanged.value) +                            emulationViewModel.setProgramChanged(-1) +                            emulationViewModel.setEmulationStopped(false) +                        } +                    } +                } +            }          }      } -    private fun startEmulation() { +    private fun startEmulation(programIndex: Int = 0) {          if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {              if (!DirectoryInitialization.areDirectoriesReady) {                  DirectoryInitialization.start() @@ -434,7 +463,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              updateScreenLayout() -            emulationState.run(emulationActivity!!.isActivityRecreated) +            emulationState.run(emulationActivity!!.isActivityRecreated, programIndex)          }      } @@ -832,6 +861,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {      ) {          private var state: State          private var surface: Surface? = null +        lateinit var emulationThread: Thread          init {              // Starting state is stopped. @@ -877,7 +907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {          }          @Synchronized -        fun run(isActivityRecreated: Boolean) { +        fun run(isActivityRecreated: Boolean, programIndex: Int = 0) {              if (isActivityRecreated) {                  if (NativeLibrary.isRunning()) {                      state = State.PAUSED @@ -888,10 +918,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              // If the surface is set, run now. Otherwise, wait for it to get set.              if (surface != null) { -                runWithValidSurface() +                runWithValidSurface(programIndex)              }          } +        @Synchronized +        fun changeProgram(programIndex: Int) { +            emulationThread.join() +            emulationThread = Thread({ +                Log.debug("[EmulationFragment] Starting emulation thread.") +                NativeLibrary.run(gamePath, programIndex) +            }, "NativeEmulation") +            emulationThread.start() +        } +          // Surface callbacks          @Synchronized          fun newSurface(surface: Surface?) { @@ -931,7 +971,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              }          } -        private fun runWithValidSurface() { +        private fun runWithValidSurface(programIndex: Int = 0) {              NativeLibrary.surfaceChanged(surface)              if (!emulationCanStart.invoke()) {                  return @@ -939,9 +979,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {              when (state) {                  State.STOPPED -> { -                    val emulationThread = Thread({ +                    emulationThread = Thread({                          Log.debug("[EmulationFragment] Starting emulation thread.") -                        NativeLibrary.run(gamePath) +                        NativeLibrary.run(gamePath, programIndex)                      }, "NativeEmulation")                      emulationThread.start()                  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 5ed754c96..a49c887a1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -66,10 +66,13 @@ class DriverViewModel : ViewModel() {      fun updateDriverList() {          val selectedDriver = GpuDriverHelper.customDriverSettingData +        val systemDriverData = GpuDriverHelper.getSystemDriverInfo()          val newDriverList = mutableListOf(              Driver(                  selectedDriver == GpuDriverMetadata(), -                YuzuApplication.appContext.getString(R.string.system_gpu_driver) +                YuzuApplication.appContext.getString(R.string.system_gpu_driver), +                systemDriverData?.get(0) ?: "", +                systemDriverData?.get(1) ?: ""              )          )          driverData.forEach { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt index b66f47fe7..d024493cd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt @@ -15,6 +15,12 @@ class EmulationViewModel : ViewModel() {      val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping      private val _isEmulationStopping = MutableStateFlow(false) +    private val _emulationStopped = MutableStateFlow(false) +    val emulationStopped = _emulationStopped.asStateFlow() + +    private val _programChanged = MutableStateFlow(-1) +    val programChanged = _programChanged.asStateFlow() +      val shaderProgress: StateFlow<Int> get() = _shaderProgress      private val _shaderProgress = MutableStateFlow(0) @@ -35,6 +41,17 @@ class EmulationViewModel : ViewModel() {          _isEmulationStopping.value = value      } +    fun setEmulationStopped(value: Boolean) { +        if (value) { +            _emulationStarted.value = false +        } +        _emulationStopped.value = value +    } + +    fun setProgramChanged(programIndex: Int) { +        _programChanged.value = programIndex +    } +      fun setShaderProgress(progress: Int) {          _shaderProgress.value = progress      } @@ -56,20 +73,4 @@ class EmulationViewModel : ViewModel() {      fun setDrawerOpen(value: Boolean) {          _drawerOpen.value = value      } - -    fun clear() { -        setEmulationStarted(false) -        setIsEmulationStopping(false) -        setShaderProgress(0) -        setTotalShaders(0) -        setShaderMessage("") -    } - -    companion object { -        const val KEY_EMULATION_STARTED = "EmulationStarted" -        const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting" -        const val KEY_SHADER_PROGRESS = "ShaderProgress" -        const val KEY_TOTAL_SHADERS = "TotalShaders" -        const val KEY_SHADER_MESSAGE = "ShaderMessage" -    }  } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index 81212cbee..a72dea8f1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt @@ -3,8 +3,10 @@  package org.yuzu.yuzu_emu.utils +import android.graphics.SurfaceTexture  import android.net.Uri  import android.os.Build +import android.view.Surface  import java.io.File  import java.io.IOException  import org.yuzu.yuzu_emu.NativeLibrary @@ -195,6 +197,11 @@ object GpuDriverHelper {      external fun supportsCustomDriverLoading(): Boolean +    external fun getSystemDriverInfo( +        surface: Surface = Surface(SurfaceTexture(true)), +        hookLibPath: String = GpuDriverHelper.hookLibPath!! +    ): Array<String>? +      // Parse the custom driver metadata to retrieve the name.      val installedCustomDriverData: GpuDriverMetadata          get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 2acc93da8..abc6055ab 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -22,7 +22,7 @@ add_library(yuzu-android SHARED  set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) -target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common) +target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common Vulkan::Headers)  target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log)  if (ARCHITECTURE_arm64)      target_link_libraries(yuzu-android PRIVATE adrenotools) diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 96f2ad3d4..f30100bd8 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -19,6 +19,7 @@ static jmethodID s_exit_emulation_activity;  static jmethodID s_disk_cache_load_progress;  static jmethodID s_on_emulation_started;  static jmethodID s_on_emulation_stopped; +static jmethodID s_on_program_changed;  static jclass s_game_class;  static jmethodID s_game_constructor; @@ -123,6 +124,10 @@ jmethodID GetOnEmulationStopped() {      return s_on_emulation_stopped;  } +jmethodID GetOnProgramChanged() { +    return s_on_program_changed; +} +  jclass GetGameClass() {      return s_game_class;  } @@ -306,6 +311,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {          env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");      s_on_emulation_stopped =          env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); +    s_on_program_changed = +        env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");      const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");      s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class)); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index a002e705d..00e48afc0 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -19,6 +19,7 @@ jmethodID GetExitEmulationActivity();  jmethodID GetDiskCacheLoadProgress();  jmethodID GetOnEmulationStarted();  jmethodID GetOnEmulationStopped(); +jmethodID GetOnProgramChanged();  jclass GetGameClass();  jmethodID GetGameConstructor(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3fd9a500c..64627db88 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -60,6 +60,9 @@  #include "jni/id_cache.h"  #include "jni/native.h"  #include "video_core/renderer_base.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "video_core/vulkan_common/vulkan_instance.h" +#include "video_core/vulkan_common/vulkan_surface.h"  #define jconst [[maybe_unused]] const auto  #define jauto [[maybe_unused]] auto @@ -208,7 +211,8 @@ void EmulationSession::InitializeSystem(bool reload) {      m_system.GetFileSystemController().CreateFactories(*m_vfs);  } -Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) { +Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath, +                                                               const std::size_t program_index) {      std::scoped_lock lock(m_mutex);      // Create the render window. @@ -238,7 +242,8 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string      ConfigureFilesystemProvider(filepath);      // Load the ROM. -    m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); +    m_load_result = +        m_system.Load(EmulationSession::GetInstance().Window(), filepath, 0, program_index);      if (m_load_result != Core::SystemResultStatus::Success) {          return m_load_result;      } @@ -248,6 +253,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string      m_system.GetCpuManager().OnGpuReady();      m_system.RegisterExitCallback([&] { HaltEmulation(); }); +    // Register an ExecuteProgram callback such that Core can execute a sub-program +    m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { +        m_next_program_index = program_index_; +        EmulationSession::GetInstance().HaltEmulation(); +    }); +      OnEmulationStarted();      return Core::SystemResultStatus::Success;  } @@ -255,6 +266,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string  void EmulationSession::ShutdownEmulation() {      std::scoped_lock lock(m_mutex); +    if (m_next_program_index != -1) { +        ChangeProgram(m_next_program_index); +        m_next_program_index = -1; +    } +      m_is_running = false;      // Unload user input. @@ -402,6 +418,12 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {                                static_cast<jint>(result));  } +void EmulationSession::ChangeProgram(std::size_t program_index) { +    JNIEnv* env = IDCache::GetEnvForThread(); +    env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnProgramChanged(), +                              static_cast<jint>(program_index)); +} +  u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {      auto program_id_string = GetJString(env, jprogramId);      try { @@ -411,7 +433,8 @@ u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {      }  } -static Core::SystemResultStatus RunEmulation(const std::string& filepath) { +static Core::SystemResultStatus RunEmulation(const std::string& filepath, +                                             const size_t program_index = 0) {      MicroProfileOnThreadCreate("EmuThread");      SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -424,7 +447,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {      SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); }); -    jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath); +    jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index);      if (result != Core::SystemResultStatus::Success) {          return result;      } @@ -521,6 +544,37 @@ jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDri  #endif  } +jobjectArray Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getSystemDriverInfo( +    JNIEnv* env, jobject j_obj, jobject j_surf, jstring j_hook_lib_dir) { +    const char* file_redirect_dir_{}; +    int featureFlags{}; +    std::string hook_lib_dir = GetJString(env, j_hook_lib_dir); +    auto handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), +                                             nullptr, nullptr, file_redirect_dir_, nullptr); +    auto driver_library = std::make_shared<Common::DynamicLibrary>(handle); +    InputCommon::InputSubsystem input_subsystem; +    auto m_window = std::make_unique<EmuWindow_Android>( +        &input_subsystem, ANativeWindow_fromSurface(env, j_surf), driver_library); + +    Vulkan::vk::InstanceDispatch dld; +    Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance( +        *driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android); + +    auto surface = Vulkan::CreateSurface(vk_instance, m_window->GetWindowInfo()); + +    auto device = Vulkan::CreateDevice(vk_instance, dld, *surface); + +    auto driver_version = device.GetDriverVersion(); +    auto version_string = +        fmt::format("{}.{}.{}", VK_API_VERSION_MAJOR(driver_version), +                    VK_API_VERSION_MINOR(driver_version), VK_API_VERSION_PATCH(driver_version)); + +    jobjectArray j_driver_info = +        env->NewObjectArray(2, IDCache::GetStringClass(), ToJString(env, version_string)); +    env->SetObjectArrayElement(j_driver_info, 1, ToJString(env, device.GetDriverName())); +    return j_driver_info; +} +  jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {      Core::Crypto::KeyManager::Instance().ReloadKeys();      return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded()); @@ -689,11 +743,11 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj      Settings::LogSettings();  } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz, -                                                                    jstring j_path) { +void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path, +                                               jint j_program_index) {      const std::string path = GetJString(env, j_path); -    const Core::SystemResultStatus result{RunEmulation(path)}; +    const Core::SystemResultStatus result{RunEmulation(path, j_program_index)};      if (result != Core::SystemResultStatus::Success) {          env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),                                    IDCache::GetExitEmulationActivity(), static_cast<int>(result)); diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index dadb138ad..bfe3fccca 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -45,7 +45,8 @@ public:      const Core::PerfStatsResults& PerfStats();      void ConfigureFilesystemProvider(const std::string& filepath);      void InitializeSystem(bool reload); -    Core::SystemResultStatus InitializeEmulation(const std::string& filepath); +    Core::SystemResultStatus InitializeEmulation(const std::string& filepath, +                                                 const std::size_t program_index = 0);      bool IsHandheldOnly();      void SetDeviceType([[maybe_unused]] int index, int type); @@ -60,6 +61,7 @@ public:  private:      static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);      static void OnEmulationStopped(Core::SystemResultStatus result); +    static void ChangeProgram(std::size_t program_index);  private:      // Window management @@ -84,4 +86,7 @@ private:      // Synchronization      std::condition_variable_any m_cv;      mutable std::mutex m_mutex; + +    // Program index for next boot +    std::atomic<s32> m_next_program_index = -1;  }; diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index c01117d14..0d2bfe8d6 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -34,8 +34,10 @@                  android:layout_width="wrap_content"                  android:layout_height="wrap_content"                  android:layout_gravity="center" +                android:focusable="false"                  android:defaultFocusHighlightEnabled="false" -                android:clickable="false"> +                android:clickable="false" +                app:rippleColor="@android:color/transparent">                  <androidx.constraintlayout.widget.ConstraintLayout                      android:id="@+id/loading_layout" | 
