Android: Use Java thread for emulation thread

This commit is contained in:
Connor McLaughlin 2021-01-11 01:03:17 +10:00
parent a5f9aa11e1
commit 5996945b37
5 changed files with 145 additions and 87 deletions

View file

@ -265,39 +265,20 @@ bool AndroidHostInterface::IsEmulationThreadPaused() const
return System::IsValid() && System::IsPaused(); 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) void AndroidHostInterface::PauseEmulationThread(bool paused)
{ {
Assert(IsEmulationThreadRunning()); Assert(IsEmulationThreadRunning());
RunOnEmulationThread([this, paused]() { PauseSystem(paused); }); RunOnEmulationThread([this, paused]() { PauseSystem(paused); });
} }
void AndroidHostInterface::StopEmulationThread() void AndroidHostInterface::StopEmulationThreadLoop()
{ {
if (!IsEmulationThreadRunning()) if (!IsEmulationThreadRunning())
return; return;
Log_InfoPrint("Stopping emulation thread...");
{
std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> lock(m_mutex);
m_emulation_thread_stop_request.store(true); m_emulation_thread_stop_request.store(true);
m_sleep_cv.notify_one(); m_sleep_cv.notify_one();
}
m_emulation_thread.join();
Log_InfoPrint("Emulation thread stopped");
} }
void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function, bool blocking) void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function, bool blocking)
@ -329,18 +310,19 @@ void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function,
m_mutex.unlock(); 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) SystemBootParameters boot_params, bool resume_state)
{ {
JNIEnv* thread_env; ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface);
if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK) 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; return;
} }
CreateImGuiContext(); CreateImGuiContext();
m_surface = initial_surface; m_surface = native_surface;
m_emulation_activity_object = emulation_activity; m_emulation_activity_object = emulation_activity;
ApplySettings(true); ApplySettings(true);
@ -358,35 +340,36 @@ void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity,
boot_result = BootSystem(boot_params); boot_result = BootSystem(boot_params);
} }
if (!boot_result) if (boot_result)
{ {
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. // System is ready to go.
thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted); EmulationThreadLoop(env);
EmulationThreadLoop();
thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped);
if (g_settings.save_state_on_exit) if (g_settings.save_state_on_exit)
SaveResumeSaveState(); SaveResumeSaveState();
PowerOffSystem(); PowerOffSystem();
}
else
{
ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str());
}
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped);
DestroyImGuiContext(); DestroyImGuiContext();
thread_env->DeleteGlobalRef(m_emulation_activity_object);
m_emulation_activity_object = {}; m_emulation_activity_object = {};
s_jvm->DetachCurrentThread();
} }
void AndroidHostInterface::EmulationThreadLoop() void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env)
{ {
{
std::unique_lock<std::mutex> lock(m_mutex);
m_emulation_thread_running.store(true);
}
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted);
for (;;) for (;;)
{ {
// run any events // run any events
@ -408,7 +391,11 @@ void AndroidHostInterface::EmulationThreadLoop()
} }
if (m_emulation_thread_stop_request.load()) if (m_emulation_thread_stop_request.load())
{
m_emulation_thread_running.store(false);
m_emulation_thread_stop_request.store(false);
return; return;
}
if (System::IsPaused()) if (System::IsPaused())
{ {
@ -475,11 +462,12 @@ bool AndroidHostInterface::AcquireHostDisplay()
!display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, !display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation)) g_settings.gpu_threaded_presentation))
{ {
ReportError("Failed to acquire host display.");
display->DestroyRenderDevice(); display->DestroyRenderDevice();
return false; return false;
} }
// The alignement was set prior to booting.
display->SetDisplayAlignment(m_display_alignment);
m_display = std::move(display); m_display = std::move(display);
if (!CreateHostDisplayResources()) 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() void AndroidHostInterface::CreateImGuiContext()
{ {
ImGui::CreateContext(); ImGui::CreateContext();
@ -956,28 +951,21 @@ DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning,
return AndroidHelpers::GetNativeClass(env, obj)->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) 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); std::string state_filename_str = AndroidHelpers::JStringToString(env, state_filename);
SystemBootParameters boot_params; SystemBootParameters boot_params;
boot_params.filename = AndroidHelpers::JStringToString(env, filename); boot_params.filename = AndroidHelpers::JStringToString(env, filename);
return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(emulationActivity, native_surface, AndroidHelpers::GetNativeClass(env, obj)->EmulationThreadEntryPoint(env, emulationActivity, surface,
std::move(boot_params), resume_state); 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, 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); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
hi->RunOnEmulationThread( hi->RunOnEmulationThread(
[hi, alignment]() { hi->GetDisplay()->SetDisplayAlignment(static_cast<HostDisplay::Alignment>(alignment)); }); [hi, alignment]() { hi->SetDisplayAlignment(static_cast<HostDisplay::Alignment>(alignment)); }, false);
} }
DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj) DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj)

View file

@ -3,6 +3,7 @@
#include "common/byte_stream.h" #include "common/byte_stream.h"
#include "common/event.h" #include "common/event.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "core/host_display.h"
#include "frontend-common/common_host_interface.h" #include "frontend-common/common_host_interface.h"
#include <array> #include <array>
#include <atomic> #include <atomic>
@ -38,15 +39,17 @@ public:
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
std::unique_ptr<ByteStream> OpenPackageFile(const char* path, u32 flags) override; std::unique_ptr<ByteStream> 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 IsEmulationThreadPaused() const;
bool StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface,
SystemBootParameters boot_params, bool resume_state);
void RunOnEmulationThread(std::function<void()> function, bool blocking = false); void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
void PauseEmulationThread(bool paused); 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 SurfaceChanged(ANativeWindow* surface, int format, int width, int height);
void SetDisplayAlignment(HostDisplay::Alignment alignment);
void SetControllerType(u32 index, std::string_view type_name); void SetControllerType(u32 index, std::string_view type_name);
void SetControllerButtonState(u32 index, s32 button_code, bool pressed); void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
@ -79,9 +82,7 @@ protected:
void OnRunningGameChanged() override; void OnRunningGameChanged() override;
private: private:
void EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface, void EmulationThreadLoop(JNIEnv* env);
SystemBootParameters boot_params, bool resume_state);
void EmulationThreadLoop();
void CreateImGuiContext(); void CreateImGuiContext();
void DestroyImGuiContext(); void DestroyImGuiContext();
@ -102,8 +103,10 @@ private:
std::deque<std::function<void()>> m_callback_queue; std::deque<std::function<void()>> m_callback_queue;
std::atomic_bool m_callbacks_outstanding{false}; 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_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; u64 m_last_vibration_update_time = 0;
bool m_last_vibration_state = false; bool m_last_vibration_state = false;
@ -111,11 +114,13 @@ private:
}; };
namespace AndroidHelpers { namespace AndroidHelpers {
JNIEnv* GetJNIEnv(); JNIEnv* GetJNIEnv();
AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj); AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj);
std::string JStringToString(JNIEnv* env, jstring str); std::string JStringToString(JNIEnv* env, jstring str);
std::unique_ptr<GrowableMemoryByteStream> ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size = 65536); std::unique_ptr<GrowableMemoryByteStream> ReadInputStreamToMemory(JNIEnv* env, jobject obj, u32 chunk_size = 65536);
jclass GetStringClass(); jclass GetStringClass();
} // namespace AndroidHelpers } // namespace AndroidHelpers
template<typename T> template<typename T>

View file

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Environment; import android.os.Environment;
import android.os.Process;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import android.widget.Toast; import android.widget.Toast;
@ -20,12 +21,6 @@ public class AndroidHostInterface {
private long mNativePointer; private long mNativePointer;
private Context mContext; 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) { public AndroidHostInterface(Context context) {
this.mContext = 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 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 boolean isEmulationThreadPaused();
public native void pauseEmulationThread(boolean paused); public native void pauseEmulationThread(boolean paused);
public native void stopEmulationThread(); public native void stopEmulationThreadLoop();
public native boolean hasSurface(); public native boolean hasSurface();

View file

@ -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() { public void onEmulationStarted() {
runOnUiThread(() -> {
updateOrientation();
});
} }
public void onEmulationStopped() { public void onEmulationStopped() {
runOnUiThread(() -> { runOnUiThread(() -> {
AndroidHostInterface.getInstance().stopEmulationThread();
if (!mWasDestroyed && !mStopRequested) if (!mWasDestroyed && !mStopRequested)
finish(); finish();
}); });
@ -159,7 +171,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
@Override @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Once we get a surface, we can boot. // Once we get a surface, we can boot.
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { if (mEmulationThread != null) {
AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height); AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height);
updateOrientation(); updateOrientation();
@ -175,9 +187,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
final boolean resumeState = getIntent().getBooleanExtra("resumeState", false); final boolean resumeState = getIntent().getBooleanExtra("resumeState", false);
final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath"); final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath");
AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath); mEmulationThread = EmulationThread.create(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath);
updateRequestedOrientation();
updateOrientation();
} }
@Override @Override
@ -219,6 +229,10 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
} }
mContentView.requestFocus(); mContentView.requestFocus();
// Sort out rotation.
updateRequestedOrientation();
updateOrientation();
// Hook up controller input. // Hook up controller input.
updateControllers(); updateControllers();
registerInputDeviceListener(); registerInputDeviceListener();
@ -240,9 +254,9 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
Log.i("EmulationActivity", "OnStop"); Log.i("EmulationActivity", "OnStop");
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) { if (mEmulationThread != null) {
mWasDestroyed = true; mWasDestroyed = true;
AndroidHostInterface.getInstance().stopEmulationThread(); stopEmulationThread();
} }
unregisterInputDeviceListener(); unregisterInputDeviceListener();
@ -317,9 +331,6 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
} }
private void updateOrientation(int newOrientation) { private void updateOrientation(int newOrientation) {
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
return;
if (newOrientation == Configuration.ORIENTATION_PORTRAIT) if (newOrientation == Configuration.ORIENTATION_PORTRAIT)
AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT); AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT);
else else

View file

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