Skip to content

Commit

Permalink
Merge pull request #18236 from hrydgard/c-emuthread
Browse files Browse the repository at this point in the history
Manage the Vulkan "EmuThread" from C++.
  • Loading branch information
hrydgard committed Sep 29, 2023
2 parents 8eefb9f + 1a19884 commit f0d3a8f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 69 deletions.
76 changes: 45 additions & 31 deletions android/jni/app-android.cpp
Expand Up @@ -295,12 +295,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {

// Only used in OpenGL mode.
static void EmuThreadFunc() {
JNIEnv *env;

SetCurrentThreadName("EmuThread");

// Name the thread in the JVM, because why not (might result in better debug output in Play Console).
// TODO: Do something clever with getEnv() and stored names from SetCurrentThreadName?
JNIEnv *env;
JavaVMAttachArgs args{};
args.version = JNI_VERSION_1_6;
args.name = "EmuThread";
Expand Down Expand Up @@ -1293,15 +1292,6 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNI
}
}

extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_requestExitVulkanRenderLoop(JNIEnv *env, jobject obj) {
if (!renderLoopRunning) {
ERROR_LOG(SYSTEM, "Render loop already exited");
return;
}
exitRenderLoop = true;
// The caller joins the thread anyway, so no point in doing a wait loop here, only leads to misleading hang diagnostics.
}

void correctRatio(int &sz_x, int &sz_y, float scale) {
float x = (float)sz_x;
float y = (float)sz_y;
Expand Down Expand Up @@ -1449,40 +1439,71 @@ static void ProcessFrameCommands(JNIEnv *env) {
}
}

std::thread g_vulkanRenderLoopThread;

static void VulkanEmuThread(ANativeWindow *wnd);

// This runs in Vulkan mode only.
// This handles the entire lifecycle of the Vulkan context, init and exit.
extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoop(JNIEnv *env, jobject obj, jobject _surf) {
extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoop(JNIEnv * env, jobject obj, jobject _surf) {
_assert_(!useCPUThread);

if (g_vulkanRenderLoopThread.joinable()) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Already running");
}

ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;

if (!wnd) {
// This shouldn't ever happen.
ERROR_LOG(G3D, "Error: Surface is null.");
renderLoopRunning = false;
return false;
}

g_vulkanRenderLoopThread = std::thread(VulkanEmuThread, wnd);
return true;
}

extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_requestExitVulkanRenderLoop(JNIEnv * env, jobject obj) {
if (!renderLoopRunning) {
ERROR_LOG(SYSTEM, "Render loop already exited");
return;
}
_assert_(g_vulkanRenderLoopThread.joinable());
exitRenderLoop = true;
g_vulkanRenderLoopThread.join();
_assert_(!g_vulkanRenderLoopThread.joinable());
g_vulkanRenderLoopThread = std::thread();
}

// TODO: Merge with the Win32 EmuThread and so on, and the Java EmuThread?
static void VulkanEmuThread(ANativeWindow *wnd) {
SetCurrentThreadName("EmuThread");

AndroidJNIThreadContext ctx;
JNIEnv *env = getEnv();

if (!graphicsContext) {
ERROR_LOG(G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
renderLoopRunning = false;
exitRenderLoop = false;
return false;
return;
}

if (exitRenderLoop) {
WARN_LOG(G3D, "runVulkanRenderLoop: ExitRenderLoop requested at start, skipping the whole thing.");
renderLoopRunning = false;
exitRenderLoop = false;
return true;
return;
}

// This is up here to prevent race conditions, in case we pause during init.
renderLoopRunning = true;

ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;

WARN_LOG(G3D, "runVulkanRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d",
display_xres, display_yres, desiredBackbufferSizeX, desiredBackbufferSizeY);

if (!wnd) {
// This shouldn't ever happen.
ERROR_LOG(G3D, "Error: Surface is null.");
renderLoopRunning = false;
return false;
}

if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
// On Android, if we get here, really no point in continuing.
// The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work
Expand All @@ -1493,7 +1514,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoo
delete graphicsContext;
graphicsContext = nullptr;
renderLoopRunning = false;
return false;
return;
}

if (!exitRenderLoop) {
Expand All @@ -1504,12 +1525,6 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoo
graphicsContext->ThreadStart();
renderer_inited = true;

static bool hasSetThreadName = false;
if (!hasSetThreadName) {
hasSetThreadName = true;
SetCurrentThreadName("AndroidRender");
}

while (!exitRenderLoop) {
LockedNativeUpdateRender();
ProcessFrameCommands(env);
Expand All @@ -1530,7 +1545,6 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runVulkanRenderLoo
exitRenderLoop = false;

WARN_LOG(G3D, "Render loop function exited.");
return true;
}

// NOTE: This is defunct and not working, due to how the Android storage functions currently require
Expand Down
47 changes: 9 additions & 38 deletions android/src/org/ppsspp/ppsspp/NativeActivity.java
Expand Up @@ -68,7 +68,6 @@ public abstract class NativeActivity extends Activity {
// Graphics and audio interfaces for Vulkan (javaGL = false)
private NativeSurfaceView mSurfaceView;
private Surface mSurface;
private Thread mRenderLoopThread = null;

// Graphics and audio interfaces for Java EGL (javaGL = true)
private NativeGLView mGLSurfaceView;
Expand Down Expand Up @@ -568,19 +567,6 @@ public void checkForVibrator() {
}
}

private final Runnable mEmulationRunner = new Runnable() {
@Override
public void run() {
Log.i(TAG, "Starting the render loop: " + mSurface);
// Start emulation using the provided Surface.
if (!runVulkanRenderLoop(mSurface)) {
// Shouldn't happen.
Log.e(TAG, "Failed to start up OpenGL/Vulkan - runVulkanRenderLoop returned false");
}
Log.i(TAG, "Left the render loop: " + mSurface);
}
};

public native boolean runVulkanRenderLoop(Surface surface);
// Tells the render loop thread to exit, so we can restart it.
public native void requestExitVulkanRenderLoop();
Expand Down Expand Up @@ -694,45 +680,30 @@ public void notifySurface(Surface surface) {
updateSustainedPerformanceMode();
}

// Invariants: After this, mRenderLoopThread will be set, and the thread will be running,
// if in Vulkan mode.
// The render loop thread (EmuThread) is now spawned from the native side.
protected synchronized void startRenderLoopThread() {
if (javaGL) {
Log.e(TAG, "JavaGL mode - should not get into ensureRenderLoop.");
Log.e(TAG, "JavaGL mode - should not get into startRenderLoopThread.");
return;
}
if (mSurface == null) {
Log.w(TAG, "ensureRenderLoop - not starting thread, needs surface");
Log.w(TAG, "startRenderLoopThread - not starting thread, needs surface");
return;
}
if (mRenderLoopThread == null) {
Log.w(TAG, "ensureRenderLoop: Starting thread");
mRenderLoopThread = new Thread(mEmulationRunner);
mRenderLoopThread.start();
}

Log.w(TAG, "startRenderLoopThread: Starting thread");
runVulkanRenderLoop(mSurface);
}

// Invariants: After this, mRenderLoopThread will be null, and the thread has exited.
private synchronized void joinRenderLoopThread() {
if (javaGL) {
Log.e(TAG, "JavaGL - should not get into joinRenderLoopThread.");
return;
}

if (mRenderLoopThread != null) {
// This will wait until the thread has exited.
Log.i(TAG, "requestExitVulkanRenderLoop");
requestExitVulkanRenderLoop();
try {
Log.i(TAG, "joining render loop thread...");
mRenderLoopThread.join();
Log.w(TAG, "Joined render loop thread.");
mRenderLoopThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
mRenderLoopThread = null;
}
}
// This will wait until the thread has exited.
Log.i(TAG, "requestExitVulkanRenderLoop");
requestExitVulkanRenderLoop();
}

@TargetApi(Build.VERSION_CODES.KITKAT)
Expand Down

0 comments on commit f0d3a8f

Please sign in to comment.