diff --git a/CMakeLists.txt b/CMakeLists.txt index ddb29bf4..6cc98f2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -681,6 +681,14 @@ if (ANDROID) MobileGL/MG_Util/SelfTest/DriverPostJni.cpp MobileGL/MG_Util/SelfTest/DriverBenchJni.cpp ) + if (MOBILEGL_BUILD_DISAGGREGATED) + # P12 (on-screen server window): the trace APK's MobileGLDisplayActivity - the in-process + # display server's thread, its SurfaceView's ANativeWindow in and out of ServerDisplay, and + # the geometry up-call. Split build only: nothing else has an in-process server to drive. + list(APPEND SOURCE_FILES + MobileGL/MG_Remote/Server/DisplayServerJni.cpp + ) + endif() endif() if (WIN32) diff --git a/MobileGL/MG_Remote/Server/DisplayServerJni.cpp b/MobileGL/MG_Remote/Server/DisplayServerJni.cpp new file mode 100644 index 00000000..257353bf --- /dev/null +++ b/MobileGL/MG_Remote/Server/DisplayServerJni.cpp @@ -0,0 +1,179 @@ +// MobileGL - MobileGL/MG_Remote/Server/DisplayServerJni.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// P12 (on-screen server window), D3/D5/D6/D7. THE JNI GLUE OF THE IN-PROCESS DISPLAY SERVER. +// +// The trace APK's MobileGLDisplayActivity (android:process=":mglwin") owns a SurfaceView and runs +// the TCP server on a thread of its own process, so a headless client (MOBILEGL_IPC_SURFACE=server) +// renders straight onto that SurfaceView. This file is the whole native half of that Activity: +// +// nativeInstallDisplay ServerDisplayInstance().Install(): ANativeWindow acquire/release plus the +// geometry up-call below. Before the server serves. +// nativeServe mobilegl_server_serve_inprocess(listen) on the CALLING (Java) thread; +// returns its exit code when the server stops or fails. +// nativeStop mobilegl_server_stop_inprocess(): non-blocking, safe on the UI thread. +// nativeAttachWindow surfaceCreated / surfaceChanged: ANativeWindow_fromSurface, Attach (which +// takes its OWN reference), then this call's reference is released. +// nativeDetachWindow surfaceDestroyed: ServerDisplay::Detach, which BLOCKS (bounded) until the +// session rendering into the window has released its backend surface on the +// apply thread. Returns the result's name for the Activity's log. +// nativeUninstallDisplay after the server returned: the display is gone. +// +// THE GEOMETRY UP-CALL. ServerDisplay calls RequestGeometry on the APPLY THREAD, from AcquireFor, +// during a ServerOwned CreateWindowSurface, with the size the client asked for. That thread is a +// native thread the JVM does not know, so it is attached for the call and detached after it (a +// native thread that exits while still attached aborts the runtime). The Java side only POSTS the +// request to the UI thread (SurfaceHolder.setFixedSize / setSizeFromLayout must run there) and +// returns: this must not block and must not call back into ServerDisplay. The class and method are +// resolved here, on the Java thread that installs the display, because a native thread's FindClass +// would search the system class loader, not the app's. +// +// Compiled only for Android with MOBILEGL_BUILD_DISAGGREGATED (CMakeLists.txt, beside +// DriverPostJni.cpp); the guard keeps it inert anywhere else. +#ifdef __ANDROID__ + +#include "InProcessServer.h" +#include "ServerDisplay.h" + +#include + +#include +#include +#include + +#include + +namespace { + using MobileGL::Bool; + using MobileGL::Uint32; + using MobileGL::MG_Remote::Server::ServerDisplayInstance; + using MobileGL::MG_Remote::Server::ServerWindowDetach; + using MobileGL::MG_Remote::Server::ServerWindowDetachName; + + // Set once by nativeInstallDisplay (a Java thread), read by the apply thread's up-call. + std::mutex g_upcallMutex; + JavaVM* g_vm = nullptr; + jclass g_activityClass = nullptr; // a global reference + jmethodID g_onGeometryRequest = nullptr; + + void RequestGeometry(void* /*user*/, Uint32 width, Uint32 height) { + JavaVM* vm = nullptr; + jclass activityClass = nullptr; + jmethodID onGeometryRequest = nullptr; + { + const std::lock_guard lock(g_upcallMutex); + vm = g_vm; + activityClass = g_activityClass; + onGeometryRequest = g_onGeometryRequest; + } + if (vm == nullptr || activityClass == nullptr || onGeometryRequest == nullptr) { + MGLOG_W("MG_Remote server: display geometry %ux%u requested, but no Java display is installed to " + "apply it; the window keeps its own size", + width, height); + return; + } + JNIEnv* env = nullptr; + Bool attached = false; + const jint got = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (got == JNI_EDETACHED) { + JavaVMAttachArgs args{JNI_VERSION_1_6, "mgl-srv-geometry", nullptr}; + if (vm->AttachCurrentThread(&env, &args) != JNI_OK || env == nullptr) { + MGLOG_E("MG_Remote server: could not attach the apply thread to the JVM to request display " + "geometry %ux%u; the window keeps its own size", + width, height); + return; + } + attached = true; + } else if (got != JNI_OK || env == nullptr) { + MGLOG_E("MG_Remote server: JavaVM::GetEnv failed (%d) requesting display geometry %ux%u", + static_cast(got), width, height); + return; + } + env->CallStaticVoidMethod(activityClass, onGeometryRequest, static_cast(width), + static_cast(height)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + MGLOG_E("MG_Remote server: the display's geometry up-call (%ux%u) threw; the window keeps its own size", + width, height); + } else { + MGLOG_I("MG_Remote server: display geometry %ux%u requested (%s)", width, height, + width != 0 && height != 0 ? "SurfaceHolder.setFixedSize" : "SurfaceHolder.setSizeFromLayout"); + } + if (attached) vm->DetachCurrentThread(); + } +} // namespace + +extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeInstallDisplay(JNIEnv* env, + jclass clazz) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != JNI_OK) vm = nullptr; + jmethodID onGeometryRequest = env->GetStaticMethodID(clazz, "onGeometryRequest", "(II)V"); + if (onGeometryRequest == nullptr) { + // NoSuchMethodError is pending; the display still works, at the window's own size. + env->ExceptionClear(); + MGLOG_E("MG_Remote server: MobileGLDisplayActivity has no static onGeometryRequest(int, int); " + "geometry requests will not reach the SurfaceHolder"); + } + { + const std::lock_guard lock(g_upcallMutex); + if (g_activityClass != nullptr) env->DeleteGlobalRef(g_activityClass); + g_vm = vm; + g_activityClass = static_cast(env->NewGlobalRef(clazz)); + g_onGeometryRequest = onGeometryRequest; + } + ServerDisplayInstance().Install( + MobileGL::MG_Remote::Server::AndroidNativeWindowHooks(&RequestGeometry, /*user=*/nullptr)); +} + +extern "C" JNIEXPORT jint JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeServe(JNIEnv* env, jclass, + jstring listen) { + if (listen == nullptr) return mobilegl_server_serve_inprocess(nullptr); + const char* endpoint = env->GetStringUTFChars(listen, nullptr); + if (endpoint == nullptr) return 71; // OutOfMemoryError pending + const int result = mobilegl_server_serve_inprocess(endpoint); + env->ReleaseStringUTFChars(listen, endpoint); + return static_cast(result); +} + +extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeStop(JNIEnv*, jclass) { + mobilegl_server_stop_inprocess(); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeAttachWindow( + JNIEnv* env, jclass, jobject surface, jint width, jint height) { + if (surface == nullptr) return JNI_FALSE; + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (window == nullptr) { + MGLOG_E("MG_Remote server: ANativeWindow_fromSurface returned no window (the Surface is gone)"); + return JNI_FALSE; + } + ServerDisplayInstance().Attach(window, width > 0 ? static_cast(width) : 0u, + height > 0 ? static_cast(height) : 0u); + // Attach took its own reference (the hooks' ANativeWindow_acquire) or already holds one for this + // window (surfaceChanged); this call's reference from ANativeWindow_fromSurface goes now. + ANativeWindow_release(window); + return JNI_TRUE; +} + +extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeDetachWindow(JNIEnv* env, + jclass) { + const ServerWindowDetach detached = ServerDisplayInstance().Detach(); + return env->NewStringUTF(ServerWindowDetachName(detached)); +} + +extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeUninstallDisplay(JNIEnv* env, + jclass) { + ServerDisplayInstance().Uninstall(); + const std::lock_guard lock(g_upcallMutex); + if (g_activityClass != nullptr) env->DeleteGlobalRef(g_activityClass); + g_activityClass = nullptr; + g_onGeometryRequest = nullptr; +} + +#endif // __ANDROID__