diff --git a/filament/backend/include/private/backend/VirtualMachineEnv.h b/filament/backend/include/private/backend/VirtualMachineEnv.h index 692abb31df32..00dfa439e5b1 100644 --- a/filament/backend/include/private/backend/VirtualMachineEnv.h +++ b/filament/backend/include/private/backend/VirtualMachineEnv.h @@ -18,7 +18,7 @@ #define TNT_FILAMENT_DRIVER_ANDROID_VIRTUAL_MACHINE_ENV_H #include -#include +#include #include @@ -26,25 +26,17 @@ namespace filament { class VirtualMachineEnv { public: + // must be called before VirtualMachineEnv::get() from a thread that is attached to the JavaVM static jint JNI_OnLoad(JavaVM* vm) noexcept; - static VirtualMachineEnv& get() noexcept { - // declaring this thread local, will ensure it's destroyed with the calling thread - static thread_local VirtualMachineEnv instance; - return instance; - } + // must be called on backend thread + static VirtualMachineEnv& get() noexcept; - static JNIEnv* getThreadEnvironment() noexcept { - JNIEnv* env; - assert_invariant(sVirtualMachine); - if (sVirtualMachine->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { - return nullptr; // this should not happen - } - return env; - } + // can be called from any thread that already has a JniEnv + static JNIEnv* getThreadEnvironment() noexcept; - inline JNIEnv* getEnvironment() noexcept { - assert_invariant(mVirtualMachine); + // must be called from the backend thread + JNIEnv* getEnvironment() noexcept { JNIEnv* env = mJniEnv; if (UTILS_UNLIKELY(!env)) { return getEnvironmentSlow(); @@ -55,20 +47,14 @@ class VirtualMachineEnv { static void handleException(JNIEnv* env) noexcept; private: - VirtualMachineEnv() noexcept : mVirtualMachine(sVirtualMachine) { - // We're not initializing the JVM here -- but we could -- because most of the time - // we don't need the jvm. Instead we do the initialization on first use. This means we could get - // a nasty slow down the very first time, but we'll live with it for now. - } - - ~VirtualMachineEnv() { - if (mVirtualMachine) { - mVirtualMachine->DetachCurrentThread(); - } - } - + explicit VirtualMachineEnv(JavaVM* vm) noexcept; + ~VirtualMachineEnv() noexcept; JNIEnv* getEnvironmentSlow() noexcept; + + static utils::Mutex sLock; static JavaVM* sVirtualMachine; + static JavaVM* getVirtualMachine(); + JNIEnv* mJniEnv = nullptr; JavaVM* mVirtualMachine = nullptr; }; diff --git a/filament/backend/src/VirtualMachineEnv.cpp b/filament/backend/src/VirtualMachineEnv.cpp index c7ff7b179061..7909afc1307c 100644 --- a/filament/backend/src/VirtualMachineEnv.cpp +++ b/filament/backend/src/VirtualMachineEnv.cpp @@ -18,12 +18,28 @@ #include #include +#include +#include #include +#include + namespace filament { -JavaVM* VirtualMachineEnv::sVirtualMachine = nullptr; +using namespace utils; + +// This Mutex shouldn't be subject to the Static Initialization Order Fiasco because its initial state is +// a single int initialized to 0. +/* static*/ Mutex VirtualMachineEnv::sLock; +/* static*/ JavaVM* VirtualMachineEnv::sVirtualMachine = nullptr; + +UTILS_NOINLINE +JavaVM* VirtualMachineEnv::getVirtualMachine() { + std::lock_guard const lock(sLock); + assert_invariant(sVirtualMachine); + return sVirtualMachine; +} /* * This is typically called by filament_jni.so when it is loaded. If filament_jni.so is not used, @@ -35,33 +51,82 @@ JavaVM* VirtualMachineEnv::sVirtualMachine = nullptr; UTILS_PUBLIC UTILS_NOINLINE jint VirtualMachineEnv::JNI_OnLoad(JavaVM* vm) noexcept { - JNIEnv* env = nullptr; - if (UTILS_UNLIKELY(vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK)) { - // this should not happen - return -1; + std::lock_guard const lock(sLock); + if (sVirtualMachine) { + // It doesn't make sense for JNI_OnLoad() to be called more than once + return JNI_VERSION_1_6; } + + // Here we check this VM at least has JNI_VERSION_1_6 + JNIEnv* env = nullptr; + jint const result = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + + FILAMENT_CHECK_POSTCONDITION(result == JNI_OK) + << "Couldn't get JniEnv* from the VM, error = " << result; + sVirtualMachine = vm; return JNI_VERSION_1_6; } UTILS_NOINLINE -void VirtualMachineEnv::handleException(JNIEnv* const env) noexcept { - if (UTILS_UNLIKELY(env->ExceptionCheck())) { - env->ExceptionDescribe(); - env->ExceptionClear(); +VirtualMachineEnv& VirtualMachineEnv::get() noexcept { + JavaVM* const vm = getVirtualMachine(); + // declaring this thread local, will ensure it's destroyed with the calling thread + thread_local VirtualMachineEnv instance{ vm }; + return instance; +} + +UTILS_NOINLINE +JNIEnv* VirtualMachineEnv::getThreadEnvironment() noexcept { + JavaVM* const vm = getVirtualMachine(); + JNIEnv* env = nullptr; + jint const result = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + + FILAMENT_CHECK_POSTCONDITION(result == JNI_OK) + << "Couldn't get JniEnv* from the VM, error = " << result; + + return env; +} + +VirtualMachineEnv::VirtualMachineEnv(JavaVM* vm) noexcept : mVirtualMachine(vm) { + // We're not initializing the JVM here -- but we could -- because most of the time + // we don't need the jvm. Instead, we do the initialization on first use. This means we could get + // a nasty slow down the very first time, but we'll live with it for now. +} + +VirtualMachineEnv::~VirtualMachineEnv() noexcept { + if (mVirtualMachine) { + mVirtualMachine->DetachCurrentThread(); } } UTILS_NOINLINE JNIEnv* VirtualMachineEnv::getEnvironmentSlow() noexcept { + FILAMENT_CHECK_PRECONDITION(mVirtualMachine) + << "JNI_OnLoad() has not been called"; + #if defined(__ANDROID__) - mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr); + jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr); #else - mVirtualMachine->AttachCurrentThread(reinterpret_cast(&mJniEnv), nullptr); + jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast(&mJniEnv), nullptr); #endif - assert_invariant(mJniEnv); + + FILAMENT_CHECK_POSTCONDITION(result == JNI_OK) + << "JavaVM::AttachCurrentThread failed with error " << result; + + FILAMENT_CHECK_POSTCONDITION(mJniEnv) + << "JavaVM::AttachCurrentThread returned a null mJniEnv"; + return mJniEnv; } +UTILS_NOINLINE +void VirtualMachineEnv::handleException(JNIEnv* const env) noexcept { + if (UTILS_UNLIKELY(env->ExceptionCheck())) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + } // namespace filament diff --git a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp index 9a37198b3b78..88b929d8bb0d 100644 --- a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp +++ b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.cpp @@ -16,13 +16,28 @@ #include "ExternalStreamManagerAndroid.h" -#include +#include + +#include + #include +#include #include +#include + +#if __has_include() +# include +# include +#else +struct ASurfaceTexture; +typedef struct ASurfaceTexture ASurfaceTexture; +#endif -#include +#include -#include +#include + +#include using namespace utils; @@ -32,13 +47,8 @@ using namespace backend; using Stream = Platform::Stream; -template -static void loadSymbol(T*& pfn, const char *symbol) noexcept { - pfn = (T*)dlsym(RTLD_DEFAULT, symbol); -} - ExternalStreamManagerAndroid& ExternalStreamManagerAndroid::create() noexcept { - return *(new ExternalStreamManagerAndroid{}); + return *(new(std::nothrow) ExternalStreamManagerAndroid{}); } void ExternalStreamManagerAndroid::destroy(ExternalStreamManagerAndroid* pExternalStreamManagerAndroid) noexcept { @@ -56,23 +66,13 @@ ExternalStreamManagerAndroid::~ExternalStreamManagerAndroid() noexcept = default UTILS_NOINLINE JNIEnv* ExternalStreamManagerAndroid::getEnvironmentSlow() noexcept { - JNIEnv * env = mVm.getEnvironment(); + JNIEnv* const env = mVm.getEnvironment(); mJniEnv = env; - jclass SurfaceTextureClass = env->FindClass("android/graphics/SurfaceTexture"); - - mSurfaceTextureClass_updateTexImage = env->GetMethodID( - SurfaceTextureClass, "updateTexImage", "()V"); - - mSurfaceTextureClass_attachToGLContext = env->GetMethodID( - SurfaceTextureClass, "attachToGLContext", "(I)V"); - - mSurfaceTextureClass_detachFromGLContext = env->GetMethodID( - SurfaceTextureClass, "detachFromGLContext", "()V"); - - mSurfaceTextureClass_getTimestamp = env->GetMethodID( - SurfaceTextureClass, "getTimestamp", "()J"); - + mSurfaceTextureClass_updateTexImage = env->GetMethodID(SurfaceTextureClass, "updateTexImage", "()V"); + mSurfaceTextureClass_attachToGLContext = env->GetMethodID(SurfaceTextureClass, "attachToGLContext", "(I)V"); + mSurfaceTextureClass_detachFromGLContext = env->GetMethodID(SurfaceTextureClass, "detachFromGLContext", "()V"); + mSurfaceTextureClass_getTimestamp = env->GetMethodID(SurfaceTextureClass, "getTimestamp", "()J"); return env; } @@ -82,7 +82,7 @@ Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept { if (!env) { return nullptr; // this should not happen } - EGLStream* stream = new EGLStream(); + EGLStream* stream = new(std::nothrow) EGLStream(); stream->jSurfaceTexture = env->NewGlobalRef(surfaceTexture); if (__builtin_available(android 28, *)) { stream->nSurfaceTexture = ASurfaceTexture_fromSurfaceTexture(env, surfaceTexture); @@ -91,29 +91,30 @@ Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept { } void ExternalStreamManagerAndroid::release(Stream* handle) noexcept { - EGLStream* stream = static_cast(handle); + EGLStream const* stream = static_cast(handle); if (__builtin_available(android 28, *)) { ASurfaceTexture_release(stream->nSurfaceTexture); } - JNIEnv* const env = getEnvironment(); + // use VirtualMachineEnv::getEnvironment() directly because we don't need to cache JNI methods here + JNIEnv* const env = mVm.getEnvironment(); assert_invariant(env); // we should have called attach() by now env->DeleteGlobalRef(stream->jSurfaceTexture); delete stream; } void ExternalStreamManagerAndroid::attach(Stream* handle, intptr_t tname) noexcept { - EGLStream* stream = static_cast(handle); + EGLStream const* stream = static_cast(handle); if (__builtin_available(android 28, *)) { // associate our GL texture to the SurfaceTexture ASurfaceTexture* const aSurfaceTexture = stream->nSurfaceTexture; - if (UTILS_UNLIKELY(ASurfaceTexture_attachToGLContext(aSurfaceTexture, (uint32_t)tname))) { + if (UTILS_UNLIKELY(ASurfaceTexture_attachToGLContext(aSurfaceTexture, uint32_t(tname)))) { // Unfortunately, before API 26 SurfaceTexture was always created in attached mode, // so attachToGLContext can fail. We consider this the unlikely case, because // this is how it should work. // So now we have to detach the surfacetexture from its texture ASurfaceTexture_detachFromGLContext(aSurfaceTexture); // and finally, try attaching again - ASurfaceTexture_attachToGLContext(aSurfaceTexture, (uint32_t)tname); + ASurfaceTexture_attachToGLContext(aSurfaceTexture, uint32_t(tname)); } } else { JNIEnv* const env = getEnvironment(); @@ -121,19 +122,19 @@ void ExternalStreamManagerAndroid::attach(Stream* handle, intptr_t tname) noexce // associate our GL texture to the SurfaceTexture jobject jSurfaceTexture = stream->jSurfaceTexture; - env->CallVoidMethod(jSurfaceTexture, mSurfaceTextureClass_attachToGLContext, (jint)tname); + env->CallVoidMethod(jSurfaceTexture, mSurfaceTextureClass_attachToGLContext, jint(tname)); if (UTILS_UNLIKELY(env->ExceptionCheck())) { // Unfortunately, before API 26 SurfaceTexture was always created in attached mode, // so attachToGLContext can fail. We consider this the unlikely case, because // this is how it should work. env->ExceptionClear(); - // so now we have to detach the surfacetexture from its texture + // so now we have to detach the SurfaceTexture from its texture env->CallVoidMethod(jSurfaceTexture, mSurfaceTextureClass_detachFromGLContext); VirtualMachineEnv::handleException(env); // and finally, try attaching again - env->CallVoidMethod(jSurfaceTexture, mSurfaceTextureClass_attachToGLContext, (jint)tname); + env->CallVoidMethod(jSurfaceTexture, mSurfaceTextureClass_attachToGLContext, jint(tname)); VirtualMachineEnv::handleException(env); } } @@ -144,7 +145,7 @@ void ExternalStreamManagerAndroid::detach(Stream* handle) noexcept { if (__builtin_available(android 28, *)) { ASurfaceTexture_detachFromGLContext(stream->nSurfaceTexture); } else { - JNIEnv* const env = mVm.getEnvironment(); + JNIEnv* const env = getEnvironment(); assert_invariant(env); // we should have called attach() by now env->CallVoidMethod(stream->jSurfaceTexture, mSurfaceTextureClass_detachFromGLContext); VirtualMachineEnv::handleException(env); @@ -152,12 +153,12 @@ void ExternalStreamManagerAndroid::detach(Stream* handle) noexcept { } void ExternalStreamManagerAndroid::updateTexImage(Stream* handle, int64_t* timestamp) noexcept { - EGLStream* stream = static_cast(handle); + EGLStream const* stream = static_cast(handle); if (__builtin_available(android 28, *)) { ASurfaceTexture_updateTexImage(stream->nSurfaceTexture); *timestamp = ASurfaceTexture_getTimestamp(stream->nSurfaceTexture); } else { - JNIEnv* const env = mVm.getEnvironment(); + JNIEnv* const env = getEnvironment(); assert_invariant(env); // we should have called attach() by now env->CallVoidMethod(stream->jSurfaceTexture, mSurfaceTextureClass_updateTexImage); VirtualMachineEnv::handleException(env); diff --git a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h index 9296a0e1c85e..d1257269c36b 100644 --- a/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h +++ b/filament/backend/src/opengl/platforms/ExternalStreamManagerAndroid.h @@ -21,14 +21,19 @@ #include +#include + #if __has_include() # include -# include #else struct ASurfaceTexture; typedef struct ASurfaceTexture ASurfaceTexture; #endif +#include + +#include + namespace filament::backend { /* @@ -70,7 +75,8 @@ class ExternalStreamManagerAndroid { ASurfaceTexture* nSurfaceTexture = nullptr; }; - inline JNIEnv* getEnvironment() noexcept { + // Must only be called from the backend thread + JNIEnv* getEnvironment() noexcept { JNIEnv* env = mJniEnv; if (UTILS_UNLIKELY(!env)) { return getEnvironmentSlow(); @@ -85,7 +91,6 @@ class ExternalStreamManagerAndroid { jmethodID mSurfaceTextureClass_attachToGLContext{}; jmethodID mSurfaceTextureClass_detachFromGLContext{}; }; - } // namespace filament::backend #endif //TNT_FILAMENT_BACKEND_OPENGL_ANDROID_EXTERNAL_STREAM_MANAGER_ANDROID_H