diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 666b0f2f6..e9b6b3b9e 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -265,39 +265,20 @@ bool AndroidHostInterface::IsEmulationThreadPaused() const return System::IsValid() && System::IsPaused(); } -bool AndroidHostInterface::StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, - SystemBootParameters boot_params, bool resume_state) -{ - Assert(!IsEmulationThreadRunning()); - - emulation_activity = AndroidHelpers::GetJNIEnv()->NewGlobalRef(emulation_activity); - - Log_DevPrintf("Starting emulation thread..."); - m_emulation_thread_stop_request.store(false); - m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, emulation_activity, - initial_surface, std::move(boot_params), resume_state); - return true; -} - void AndroidHostInterface::PauseEmulationThread(bool paused) { Assert(IsEmulationThreadRunning()); RunOnEmulationThread([this, paused]() { PauseSystem(paused); }); } -void AndroidHostInterface::StopEmulationThread() +void AndroidHostInterface::StopEmulationThreadLoop() { if (!IsEmulationThreadRunning()) return; - Log_InfoPrint("Stopping emulation thread..."); - { - std::unique_lock lock(m_mutex); - m_emulation_thread_stop_request.store(true); - m_sleep_cv.notify_one(); - } - m_emulation_thread.join(); - Log_InfoPrint("Emulation thread stopped"); + std::unique_lock lock(m_mutex); + m_emulation_thread_stop_request.store(true); + m_sleep_cv.notify_one(); } void AndroidHostInterface::RunOnEmulationThread(std::function function, bool blocking) @@ -329,18 +310,19 @@ void AndroidHostInterface::RunOnEmulationThread(std::function function, m_mutex.unlock(); } -void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, +void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulation_activity, jobject surface, SystemBootParameters boot_params, bool resume_state) { - JNIEnv* thread_env; - if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK) + ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface); + if (!native_surface) { - ReportError("Failed to attach JNI to thread"); + Log_ErrorPrint("ANativeWindow_fromSurface() returned null"); + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); return; } CreateImGuiContext(); - m_surface = initial_surface; + m_surface = native_surface; m_emulation_activity_object = emulation_activity; ApplySettings(true); @@ -358,35 +340,36 @@ void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity, boot_result = BootSystem(boot_params); } - if (!boot_result) + if (boot_result) + { + // System is ready to go. + EmulationThreadLoop(env); + + if (g_settings.save_state_on_exit) + SaveResumeSaveState(); + + PowerOffSystem(); + } + else { ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str()); - DestroyImGuiContext(); - thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); - thread_env->DeleteGlobalRef(m_emulation_activity_object); - m_emulation_activity_object = {}; - s_jvm->DetachCurrentThread(); - return; } - // System is ready to go. - thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); - EmulationThreadLoop(); + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); - thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped); - - if (g_settings.save_state_on_exit) - SaveResumeSaveState(); - - PowerOffSystem(); DestroyImGuiContext(); - thread_env->DeleteGlobalRef(m_emulation_activity_object); m_emulation_activity_object = {}; - s_jvm->DetachCurrentThread(); } -void AndroidHostInterface::EmulationThreadLoop() +void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) { + { + std::unique_lock lock(m_mutex); + m_emulation_thread_running.store(true); + } + + env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); + for (;;) { // run any events @@ -408,7 +391,11 @@ void AndroidHostInterface::EmulationThreadLoop() } if (m_emulation_thread_stop_request.load()) + { + m_emulation_thread_running.store(false); + m_emulation_thread_stop_request.store(false); return; + } if (System::IsPaused()) { @@ -475,11 +462,12 @@ bool AndroidHostInterface::AcquireHostDisplay() !display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, g_settings.gpu_threaded_presentation)) { - ReportError("Failed to acquire host display."); display->DestroyRenderDevice(); return false; } + // The alignement was set prior to booting. + display->SetDisplayAlignment(m_display_alignment); m_display = std::move(display); if (!CreateHostDisplayResources()) @@ -591,6 +579,13 @@ void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, in } } +void AndroidHostInterface::SetDisplayAlignment(HostDisplay::Alignment alignment) +{ + m_display_alignment = alignment; + if (m_display) + m_display->SetDisplayAlignment(alignment); +} + void AndroidHostInterface::CreateImGuiContext() { ImGui::CreateContext(); @@ -956,28 +951,21 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning(); } -DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject emulationActivity, +DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_runEmulationThread, jobject obj, jobject emulationActivity, jobject surface, jstring filename, jboolean resume_state, jstring state_filename) { - ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface); - if (!native_surface) - { - Log_ErrorPrint("ANativeWindow_fromSurface() returned null"); - return false; - } - std::string state_filename_str = AndroidHelpers::JStringToString(env, state_filename); SystemBootParameters boot_params; boot_params.filename = AndroidHelpers::JStringToString(env, filename); - return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(emulationActivity, native_surface, - std::move(boot_params), resume_state); + AndroidHelpers::GetNativeClass(env, obj)->EmulationThreadEntryPoint(env, emulationActivity, surface, + std::move(boot_params), resume_state); } -DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThread, jobject obj) +DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThreadLoop, jobject obj) { - AndroidHelpers::GetNativeClass(env, obj)->StopEmulationThread(); + AndroidHelpers::GetNativeClass(env, obj)->StopEmulationThreadLoop(); } DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, jobject surface, jint format, jint width, @@ -1310,7 +1298,7 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setDisplayAlignment, jobject o { AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); hi->RunOnEmulationThread( - [hi, alignment]() { hi->GetDisplay()->SetDisplayAlignment(static_cast(alignment)); }); + [hi, alignment]() { hi->SetDisplayAlignment(static_cast(alignment)); }, false); } DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj) diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index 5bdb31fde..5854a050e 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -3,6 +3,7 @@ #include "common/byte_stream.h" #include "common/event.h" #include "common/progress_callback.h" +#include "core/host_display.h" #include "frontend-common/common_host_interface.h" #include #include @@ -38,15 +39,17 @@ public: float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; std::unique_ptr OpenPackageFile(const char* path, u32 flags) override; - bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); } + bool IsEmulationThreadRunning() const { return m_emulation_thread_running.load(); } bool IsEmulationThreadPaused() const; - bool StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface, - SystemBootParameters boot_params, bool resume_state); void RunOnEmulationThread(std::function function, bool blocking = false); void PauseEmulationThread(bool paused); - void StopEmulationThread(); + void StopEmulationThreadLoop(); + + void EmulationThreadEntryPoint(JNIEnv* env, jobject emulation_activity, jobject initial_surface, + SystemBootParameters boot_params, bool resume_state); void SurfaceChanged(ANativeWindow* surface, int format, int width, int height); + void SetDisplayAlignment(HostDisplay::Alignment alignment); void SetControllerType(u32 index, std::string_view type_name); void SetControllerButtonState(u32 index, s32 button_code, bool pressed); @@ -79,9 +82,7 @@ protected: void OnRunningGameChanged() override; private: - void EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, - SystemBootParameters boot_params, bool resume_state); - void EmulationThreadLoop(); + void EmulationThreadLoop(JNIEnv* env); void CreateImGuiContext(); void DestroyImGuiContext(); @@ -102,8 +103,10 @@ private: std::deque> m_callback_queue; std::atomic_bool m_callbacks_outstanding{false}; - std::thread m_emulation_thread; std::atomic_bool m_emulation_thread_stop_request{false}; + std::atomic_bool m_emulation_thread_running{false}; + + HostDisplay::Alignment m_display_alignment = HostDisplay::Alignment::Center; u64 m_last_vibration_update_time = 0; bool m_last_vibration_state = false; @@ -111,11 +114,13 @@ private: }; namespace AndroidHelpers { + JNIEnv* GetJNIEnv(); AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj); std::string JStringToString(JNIEnv* env, jstring str); std::unique_ptr ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size = 65536); jclass GetStringClass(); + } // namespace AndroidHelpers template diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java index e26f9b61b..5caad48d3 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/AndroidHostInterface.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.os.Environment; +import android.os.Process; import android.util.Log; import android.view.Surface; import android.widget.Toast; @@ -20,12 +21,6 @@ public class AndroidHostInterface { private long mNativePointer; private Context mContext; - static public native String getScmVersion(); - - static public native String getFullScmVersion(); - - static public native AndroidHostInterface create(Context context, String userDirectory); - public AndroidHostInterface(Context context) { this.mContext = context; } @@ -46,15 +41,21 @@ public class AndroidHostInterface { } } + static public native String getScmVersion(); + + static public native String getFullScmVersion(); + + static public native AndroidHostInterface create(Context context, String userDirectory); + public native boolean isEmulationThreadRunning(); - public native boolean startEmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String state_filename); + public native boolean runEmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String state_filename); public native boolean isEmulationThreadPaused(); public native void pauseEmulationThread(boolean paused); - public native void stopEmulationThread(); + public native void stopEmulationThreadLoop(); public native boolean hasSurface(); diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java index dea4fbe0a..d1d566dba 100644 --- a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationActivity.java @@ -108,12 +108,24 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde }); } + private EmulationThread mEmulationThread; + + private void stopEmulationThread() { + if (mEmulationThread == null) + return; + + mEmulationThread.stopAndJoin(); + mEmulationThread = null; + } + public void onEmulationStarted() { + runOnUiThread(() -> { + updateOrientation(); + }); } public void onEmulationStopped() { runOnUiThread(() -> { - AndroidHostInterface.getInstance().stopEmulationThread(); if (!mWasDestroyed && !mStopRequested) finish(); }); @@ -159,7 +171,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // Once we get a surface, we can boot. - if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { + if (mEmulationThread != null) { AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height); updateOrientation(); @@ -175,9 +187,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde final boolean resumeState = getIntent().getBooleanExtra("resumeState", false); final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath"); - AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath); - updateRequestedOrientation(); - updateOrientation(); + mEmulationThread = EmulationThread.create(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath); } @Override @@ -219,6 +229,10 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde } mContentView.requestFocus(); + // Sort out rotation. + updateRequestedOrientation(); + updateOrientation(); + // Hook up controller input. updateControllers(); registerInputDeviceListener(); @@ -240,9 +254,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde protected void onDestroy() { super.onDestroy(); Log.i("EmulationActivity", "OnStop"); - if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { + if (mEmulationThread != null) { mWasDestroyed = true; - AndroidHostInterface.getInstance().stopEmulationThread(); + stopEmulationThread(); } unregisterInputDeviceListener(); @@ -317,9 +331,6 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde } private void updateOrientation(int newOrientation) { - if (!AndroidHostInterface.getInstance().isEmulationThreadRunning()) - return; - if (newOrientation == Configuration.ORIENTATION_PORTRAIT) AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT); else diff --git a/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java new file mode 100644 index 000000000..8cd95b3a9 --- /dev/null +++ b/android/app/src/main/java/com/github/stenzek/duckstation/EmulationThread.java @@ -0,0 +1,53 @@ +package com.github.stenzek.duckstation; + +import android.os.Process; +import android.util.Log; +import android.view.Surface; + +import androidx.annotation.NonNull; + +public class EmulationThread extends Thread { + private EmulationActivity emulationActivity; + private Surface surface; + private String filename; + private boolean resumeState; + private String stateFilename; + + private EmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String stateFilename) { + super("EmulationThread"); + this.emulationActivity = emulationActivity; + this.surface = surface; + this.filename = filename; + this.resumeState = resumeState; + this.stateFilename = stateFilename; + } + + public static EmulationThread create(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String stateFilename) { + Log.i("EmulationThread", String.format("Starting emulation thread (%s)...", filename)); + + EmulationThread thread = new EmulationThread(emulationActivity, surface, filename, resumeState, stateFilename); + thread.start(); + return thread; + } + + @Override + public void run() { + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_MORE_FAVORABLE); + } catch (Exception e) { + Log.i("EmulationThread", "Failed to set priority for emulation thread: " + e.getMessage()); + } + + AndroidHostInterface.getInstance().runEmulationThread(emulationActivity, surface, filename, resumeState, stateFilename); + Log.i("EmulationThread", "Emulation thread exiting."); + } + + public void stopAndJoin() { + AndroidHostInterface.getInstance().stopEmulationThreadLoop(); + try { + join(); + } catch (InterruptedException e) { + Log.i("EmulationThread", "join() interrupted: " + e.getMessage()); + } + } +}