diff --git a/android/common/CallbackUtils.cpp b/android/common/CallbackUtils.cpp index 5cde5451af35..1adca3fe117f 100644 --- a/android/common/CallbackUtils.cpp +++ b/android/common/CallbackUtils.cpp @@ -75,6 +75,17 @@ void JniCallback::postToJavaAndDestroy(JniCallback* callback) { delete callback; } +void JniCallback::destroy(JniCallback* callback) { + JNIEnv* env = filament::VirtualMachineEnv::get().getEnvironment(); + env->DeleteGlobalRef(callback->mHandler); + env->DeleteGlobalRef(callback->mCallback); +#ifdef __ANDROID__ + env->DeleteGlobalRef(callback->mCallbackUtils.handlerClass); +#endif + env->DeleteGlobalRef(callback->mCallbackUtils.executorClass); + delete callback; +} + // ----------------------------------------------------------------------------------------------- JniBufferCallback* JniBufferCallback::make(filament::Engine*, diff --git a/android/common/CallbackUtils.h b/android/common/CallbackUtils.h index 8cd8671b80b1..f8332d5f3f08 100644 --- a/android/common/CallbackUtils.h +++ b/android/common/CallbackUtils.h @@ -48,6 +48,9 @@ struct JniCallback : private filament::backend::CallbackHandler { // execute the callback on the java thread and destroy ourselves static void postToJavaAndDestroy(JniCallback* callback); + // destroy ourselves without executing the callback + static void destroy(JniCallback* callback); + // CallbackHandler interface. void post(void* user, Callback callback) override; diff --git a/android/common/JniUtils.cpp b/android/common/JniUtils.cpp new file mode 100644 index 000000000000..e896c901c80a --- /dev/null +++ b/android/common/JniUtils.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "JniUtils.h" + +namespace filament { +namespace android { + +#ifdef __EXCEPTIONS + +UTILS_NOINLINE void wrapJniHelper(JNIEnv* env, void (*invoker)(void*), void* userData) { + try { + invoker(userData); + } catch (const utils::PreconditionPanic& e) { + jclass exClass = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(exClass, e.what()); + } catch (const utils::PostconditionPanic& e) { + jclass exClass = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(exClass, e.what()); + } catch (const std::exception& e) { + jclass exClass = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(exClass, e.what()); + } +} + +UTILS_NOINLINE void wrapJniBackendHelper(JNIEnv* env, void (*invoker)(void*), void* userData) { + try { + invoker(userData); + } catch (const std::exception& e) { + jclass exClass = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(exClass, e.what()); + } +} + +#endif + +} // namespace android +} // namespace filament diff --git a/android/common/JniUtils.h b/android/common/JniUtils.h new file mode 100644 index 000000000000..57d153836bd6 --- /dev/null +++ b/android/common/JniUtils.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_ANDROID_COMMON_JNIUTILS_H +#define TNT_ANDROID_COMMON_JNIUTILS_H + +#include +#include +#include +#include +#include + +namespace filament { +namespace android { + +#ifdef __EXCEPTIONS + +// Non-templated helpers implemented in JniUtils.cpp +void wrapJniHelper(JNIEnv* env, void (*invoker)(void*), void* userData); +void wrapJniBackendHelper(JNIEnv* env, void (*invoker)(void*), void* userData); + +// For JNI methods that return a value +template +R wrapJni(JNIEnv* env, F const& f) { + if constexpr (std::is_void_v) { + auto invoker = [](void* data) { + auto& f_ref = *reinterpret_cast(data); + f_ref(); + }; + wrapJniHelper(env, invoker, (void*)&f); + } else { + struct Context { + const F& f; + R result; + }; + Context ctx{ f, R{} }; + auto invoker = [](void* data) { + auto& context = *reinterpret_cast(data); + context.result = context.f(); + }; + wrapJniHelper(env, invoker, &ctx); + return ctx.result; + } +} + +// Overload for JNI methods that return void +template +inline void wrapJni(JNIEnv* env, F const& f) { + wrapJni(env, f); +} + +// For JNI methods that can return backend errors (mapped to java.lang.Error) +template +R wrapJniBackend(JNIEnv* env, F const& f) { + if constexpr (std::is_void_v) { + auto invoker = [](void* data) { + auto& f_ref = *reinterpret_cast(data); + f_ref(); + }; + wrapJniBackendHelper(env, invoker, (void*)&f); + } else { + struct Context { + const F& f; + R result; + }; + Context ctx{ f, R{} }; + auto invoker = [](void* data) { + auto& context = *reinterpret_cast(data); + context.result = context.f(); + }; + wrapJniBackendHelper(env, invoker, &ctx); + return ctx.result; + } +} + +template +inline void wrapJniBackend(JNIEnv* env, F const& f) { + wrapJniBackend(env, f); +} + +#else + +// For JNI methods that return a value +template +R wrapJni(JNIEnv* env, F const& f) { + return f(); +} + +// Overload for JNI methods that return void +template +inline void wrapJni(JNIEnv* env, F const& f) { + f(); +} + +template +R wrapJniBackend(JNIEnv* env, F const& f) { + return f(); +} + +template +inline void wrapJniBackend(JNIEnv* env, F const& f) { + f(); +} + +#endif + +} // namespace android +} // namespace filament + +#endif // TNT_ANDROID_COMMON_JNIUTILS_H diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index dec19c5bb2d6..d9b2694fadee 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -126,6 +126,7 @@ add_library(filament-jni SHARED # Common utils ../common/CallbackUtils.cpp ../common/NioUtils.cpp + ../common/JniUtils.cpp ) target_include_directories(filament-jni PRIVATE diff --git a/android/filament-android/src/main/cpp/BufferObject.cpp b/android/filament-android/src/main/cpp/BufferObject.cpp index cdb4c3969405..8595f6ecbbe5 100644 --- a/android/filament-android/src/main/cpp/BufferObject.cpp +++ b/android/filament-android/src/main/cpp/BufferObject.cpp @@ -16,7 +16,6 @@ #include -#include #include #include @@ -26,6 +25,7 @@ #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include using namespace filament; using namespace backend; @@ -63,7 +63,9 @@ Java_com_google_android_filament_BufferObject_nBuilderBuild(JNIEnv *env, jclass jlong nativeBuilder, jlong nativeEngine) { BufferObject::Builder* builder = (BufferObject::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT jint JNICALL @@ -81,20 +83,22 @@ Java_com_google_android_filament_BufferObject_nSetBuffer(JNIEnv *env, jclass typ BufferObject *bufferObject = (BufferObject *) nativeBufferObject; Engine *engine = (Engine *) nativeEngine; - AutoBuffer nioBuffer(env, buffer, count); - void* data = nioBuffer.getData(); - size_t sizeInBytes = nioBuffer.getSize(); - if (sizeInBytes > (remaining << nioBuffer.getShift())) { - // BufferOverflowException - return -1; - } + return filament::android::wrapJni(env, [=]() { + AutoBuffer nioBuffer(env, buffer, count); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } - auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); + auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); - BufferDescriptor desc(data, sizeInBytes, - callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); + BufferDescriptor desc(data, sizeInBytes, + callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - bufferObject->setBuffer(*engine, std::move(desc), (uint32_t) destOffsetInBytes); + bufferObject->setBuffer(*engine, std::move(desc), (uint32_t) destOffsetInBytes); - return 0; + return 0; + }); } diff --git a/android/filament-android/src/main/cpp/Camera.cpp b/android/filament-android/src/main/cpp/Camera.cpp index 90cd62e22a44..e55f96342cdd 100644 --- a/android/filament-android/src/main/cpp/Camera.cpp +++ b/android/filament-android/src/main/cpp/Camera.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -24,21 +25,26 @@ #include using namespace filament; +using namespace filament::android; extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Camera_nSetProjection(JNIEnv*, jclass, jlong nativeCamera, +Java_com_google_android_filament_Camera_nSetProjection(JNIEnv* env, jclass, jlong nativeCamera, jint projection, jdouble left, jdouble right, jdouble bottom, jdouble top, jdouble near, jdouble far) { Camera *camera = (Camera *) nativeCamera; - camera->setProjection((Camera::Projection) projection, left, right, bottom, top, near, far); + wrapJni(env, [=]() { + camera->setProjection((Camera::Projection) projection, left, right, bottom, top, near, far); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Camera_nSetProjectionFov(JNIEnv*, jclass , +Java_com_google_android_filament_Camera_nSetProjectionFov(JNIEnv* env, jclass , jlong nativeCamera, jdouble fovInDegrees, jdouble aspect, jdouble near, jdouble far, jint fov) { Camera *camera = (Camera *) nativeCamera; - camera->setProjection(fovInDegrees, aspect, near, far, (Camera::Fov) fov); + wrapJni(env, [=]() { + camera->setProjection(fovInDegrees, aspect, near, far, (Camera::Fov) fov); + }); } extern "C" JNIEXPORT jdouble JNICALL @@ -49,10 +55,12 @@ Java_com_google_android_filament_Camera_nGetFieldOfViewInDegrees(JNIEnv*, jclass } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Camera_nSetLensProjection(JNIEnv*, jclass, +Java_com_google_android_filament_Camera_nSetLensProjection(JNIEnv* env, jclass, jlong nativeCamera, jdouble focalLength, jdouble aspect, jdouble near, jdouble far) { Camera *camera = (Camera *) nativeCamera; - camera->setLensProjection(focalLength, aspect, near, far); + wrapJni(env, [=]() { + camera->setLensProjection(focalLength, aspect, near, far); + }); } extern "C" JNIEXPORT void JNICALL @@ -62,10 +70,12 @@ Java_com_google_android_filament_Camera_nSetCustomProjection(JNIEnv *env, jclass Camera *camera = (Camera *) nativeCamera; jdouble *inProjection = env->GetDoubleArrayElements(inProjection_, NULL); jdouble *inProjectionForCulling = env->GetDoubleArrayElements(inProjectionForCulling_, NULL); - camera->setCustomProjection( - *reinterpret_cast(inProjection), - *reinterpret_cast(inProjectionForCulling), - near, far); + wrapJni(env, [=]() { + camera->setCustomProjection( + *reinterpret_cast(inProjection), + *reinterpret_cast(inProjectionForCulling), + near, far); + }); env->ReleaseDoubleArrayElements(inProjection_, inProjection, JNI_ABORT); env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT); } @@ -77,10 +87,12 @@ Java_com_google_android_filament_Camera_nSetCustomEyeProjection(JNIEnv *env, jcl Camera *camera = (Camera *) nativeCamera; jdouble *inProjection = env->GetDoubleArrayElements(inProjection_, NULL); jdouble *inProjectionForCulling = env->GetDoubleArrayElements(inProjectionForCulling_, NULL); - camera->setCustomEyeProjection( - reinterpret_cast(inProjection), (size_t) count, - *reinterpret_cast(inProjectionForCulling), - near, far); + wrapJni(env, [=]() { + camera->setCustomEyeProjection( + reinterpret_cast(inProjection), (size_t) count, + *reinterpret_cast(inProjectionForCulling), + near, far); + }); env->ReleaseDoubleArrayElements(inProjection_, inProjection, JNI_ABORT); env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT); } @@ -154,7 +166,9 @@ Java_com_google_android_filament_Camera_nSetEyeModelMatrix(JNIEnv *env, jclass, jlong nativeCamera, jint eyeId, jdoubleArray model_) { Camera* camera = (Camera *) nativeCamera; jdouble *model = env->GetDoubleArrayElements(model_, NULL); - camera->setEyeModelMatrix((uint8_t)eyeId, *reinterpret_cast(model)); + wrapJni(env, [=]() { + camera->setEyeModelMatrix((uint8_t)eyeId, *reinterpret_cast(model)); + }); env->ReleaseDoubleArrayElements(model_, model, JNI_ABORT); } diff --git a/android/filament-android/src/main/cpp/ColorGrading.cpp b/android/filament-android/src/main/cpp/ColorGrading.cpp index d6ee01bef9f2..40d32fb97c14 100644 --- a/android/filament-android/src/main/cpp/ColorGrading.cpp +++ b/android/filament-android/src/main/cpp/ColorGrading.cpp @@ -21,6 +21,7 @@ #include #include +#include using namespace filament; using namespace math; @@ -37,10 +38,12 @@ Java_com_google_android_filament_ColorGrading_nDestroyBuilder(JNIEnv*, jclass, j } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_ColorGrading_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jlong nativeEngine) { +Java_com_google_android_filament_ColorGrading_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine) { ColorGrading::Builder* builder = (ColorGrading::Builder*) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 00bd4fc680a6..dd9443b8a313 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -16,6 +16,8 @@ #include +#include + #include #include #include @@ -28,14 +30,18 @@ #include #include "common/CallbackUtils.h" +#include "common/JniUtils.h" using namespace filament; using namespace utils; +using namespace filament::android; extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv*, jclass, jlong nativeEngine) { - Engine* engine = (Engine*) nativeEngine; - Engine::destroy(&engine); +Java_com_google_android_filament_Engine_nDestroyEngine(JNIEnv *env, jclass, jlong nativeEngine) { + wrapJni(env, [=]() { + Engine* engine = (Engine*) nativeEngine; + Engine::destroy(&engine); + }); } // SwapChain @@ -77,11 +83,13 @@ Java_com_google_android_filament_Engine_nCreateSwapChainFromRawPointer(JNIEnv*, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroySwapChain(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroySwapChain(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeSwapChain) { Engine* engine = (Engine*) nativeEngine; SwapChain* swapChain = (SwapChain*) nativeSwapChain; - return engine->destroy(swapChain); + return wrapJni(env, [=]() { + return engine->destroy(swapChain); + }); } // View @@ -94,11 +102,13 @@ Java_com_google_android_filament_Engine_nCreateView(JNIEnv*, jclass, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyView(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyView(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeView) { Engine* engine = (Engine*) nativeEngine; View* view = (View*) nativeView; - return engine->destroy(view); + return wrapJni(env, [=]() { + return engine->destroy(view); + }); } // Renderer @@ -111,11 +121,13 @@ Java_com_google_android_filament_Engine_nCreateRenderer(JNIEnv*, jclass, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyRenderer(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyRenderer(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeRenderer) { Engine* engine = (Engine*) nativeEngine; Renderer* renderer = (Renderer*) nativeRenderer; - return engine->destroy(renderer); + return wrapJni(env, [=]() { + return engine->destroy(renderer); + }); } // Camera @@ -137,11 +149,13 @@ Java_com_google_android_filament_Engine_nGetCameraComponent(JNIEnv*, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nDestroyCameraComponent(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyCameraComponent(JNIEnv *env, jclass, jlong nativeEngine, jint entity_) { - Engine* engine = (Engine*) nativeEngine; - Entity& entity = *reinterpret_cast(&entity_); - engine->destroyCameraComponent(entity); + wrapJni(env, [=]() { + Engine* engine = (Engine*) nativeEngine; + Entity entity = *reinterpret_cast(&entity_); + engine->destroyCameraComponent(entity); + }); } // Scene @@ -154,11 +168,13 @@ Java_com_google_android_filament_Engine_nCreateScene(JNIEnv*, jclass, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyScene(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyScene(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeScene) { Engine* engine = (Engine*) nativeEngine; Scene* scene = (Scene*) nativeScene; - return engine->destroy(scene); + return wrapJni(env, [=]() { + return engine->destroy(scene); + }); } // Fence @@ -171,119 +187,147 @@ Java_com_google_android_filament_Engine_nCreateFence(JNIEnv*, jclass, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyFence(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyFence(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeFence) { Engine* engine = (Engine*) nativeEngine; Fence* fence = (Fence*) nativeFence; - return engine->destroy(fence); + return wrapJni(env, [=]() { + return engine->destroy(fence); + }); } // Stream extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyStream(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyStream(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeStream) { Engine* engine = (Engine*) nativeEngine; Stream* stream = (Stream*) nativeStream; - return engine->destroy(stream); + return wrapJni(env, [=]() { + return engine->destroy(stream); + }); } // Others... extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyIndexBuffer(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyIndexBuffer(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeIndexBuffer) { Engine* engine = (Engine*) nativeEngine; IndexBuffer* indexBuffer = (IndexBuffer*) nativeIndexBuffer; - return engine->destroy(indexBuffer); + return wrapJni(env, [=]() { + return engine->destroy(indexBuffer); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyVertexBuffer(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyVertexBuffer(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeVertexBuffer) { Engine* engine = (Engine*) nativeEngine; VertexBuffer* vertexBuffer = (VertexBuffer*) nativeVertexBuffer; - return engine->destroy(vertexBuffer); + return wrapJni(env, [=]() { + return engine->destroy(vertexBuffer); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeSkinningBuffer) { Engine* engine = (Engine*) nativeEngine; SkinningBuffer* skinningBuffer = (SkinningBuffer*) nativeSkinningBuffer; - return engine->destroy(skinningBuffer); + return wrapJni(env, [=]() { + return engine->destroy(skinningBuffer); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeMorphTargetBuffer) { Engine* engine = (Engine*) nativeEngine; MorphTargetBuffer* mtb = (MorphTargetBuffer*) nativeMorphTargetBuffer; - return engine->destroy(mtb); + return wrapJni(env, [=]() { + return engine->destroy(mtb); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeIndirectLight) { Engine* engine = (Engine*) nativeEngine; IndirectLight* indirectLight = (IndirectLight*) nativeIndirectLight; - return engine->destroy(indirectLight); + return wrapJni(env, [=]() { + return engine->destroy(indirectLight); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyMaterial(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyMaterial(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeMaterial) { Engine* engine = (Engine*) nativeEngine; Material* material = (Material*) nativeMaterial; - return engine->destroy(material); + return wrapJni(env, [=]() { + return engine->destroy(material); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyMaterialInstance(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyMaterialInstance(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeMaterialInstance) { Engine* engine = (Engine*) nativeEngine; MaterialInstance* materialInstance = (MaterialInstance*) nativeMaterialInstance; - return engine->destroy(materialInstance); + return wrapJni(env, [=]() { + return engine->destroy(materialInstance); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroySkybox(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroySkybox(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeSkybox) { Engine* engine = (Engine*) nativeEngine; Skybox* skybox = (Skybox*) nativeSkybox; - return engine->destroy(skybox); + return wrapJni(env, [=]() { + return engine->destroy(skybox); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyColorGrading(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyColorGrading(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeColorGrading) { Engine* engine = (Engine*) nativeEngine; ColorGrading* colorGrading = (ColorGrading*) nativeColorGrading; - return engine->destroy(colorGrading); + return wrapJni(env, [=]() { + return engine->destroy(colorGrading); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyTexture(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyTexture(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeTexture) { Engine* engine = (Engine*) nativeEngine; Texture* texture = (Texture*) nativeTexture; - return engine->destroy(texture); + return wrapJni(env, [=]() { + return engine->destroy(texture); + }); } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nDestroyRenderTarget(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyRenderTarget(JNIEnv *env, jclass, jlong nativeEngine, jlong nativeTarget) { Engine* engine = (Engine*) nativeEngine; RenderTarget* target = (RenderTarget*) nativeTarget; - return engine->destroy(target); + return wrapJni(env, [=]() { + return engine->destroy(target); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nDestroyEntity(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nDestroyEntity(JNIEnv *env, jclass, jlong nativeEngine, jint entity_) { - Engine* engine = (Engine*) nativeEngine; - Entity& entity = *reinterpret_cast(&entity_); - engine->destroy(entity); + wrapJni(env, [=]() { + Engine* engine = (Engine*) nativeEngine; + Entity entity = *reinterpret_cast(&entity_); + engine->destroy(entity); + }); } @@ -415,17 +459,21 @@ Java_com_google_android_filament_Engine_nIsValidSwapChain(JNIEnv*, jclass, } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv *env, jclass, jlong nativeEngine, jlong timeout) { Engine* engine = (Engine*) nativeEngine; - return engine->flushAndWait((uint64_t)timeout); + return wrapJni(env, [=]() { + return engine->flushAndWait((uint64_t)timeout); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass, +Java_com_google_android_filament_Engine_nFlush(JNIEnv *env, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; - engine->flush(); + wrapJni(env, [=]() { + engine->flush(); + }); } extern "C" JNIEXPORT jboolean JNICALL @@ -436,21 +484,32 @@ Java_com_google_android_filament_Engine_nIsPaused(JNIEnv*, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Engine_nCompile(JNIEnv* env, jclass, +Java_com_google_android_filament_Engine_nCompile(JNIEnv *env, jclass, jlong nativeEngine, jint priority, jlong nativeMaterial, jlong nativeView, jint shadowReceiver, jint skinning, jobject handler, jobject runnable) { Engine* engine = (Engine*) nativeEngine; Material* material = (Material*) nativeMaterial; View* view = (View*) nativeView; JniCallback* jniCallback = JniCallback::make(env, handler, runnable); - engine->compile( - (backend::CompilerPriorityQueue) priority, - material, view, - (utils::tribool::value_t) shadowReceiver, - (utils::tribool::value_t) skinning, - jniCallback->getHandler(), [jniCallback](Material*){ - JniCallback::postToJavaAndDestroy(jniCallback); - }); + wrapJni(env, [=]() { +#if defined(__EXCEPTIONS) + try { +#endif + engine->compile( + (backend::CompilerPriorityQueue) priority, + material, view, + (utils::tribool::value_t) shadowReceiver, + (utils::tribool::value_t) skinning, + jniCallback->getHandler(), [jniCallback](Material*){ + JniCallback::postToJavaAndDestroy(jniCallback); + }); +#if defined(__EXCEPTIONS) + } catch (...) { + JniCallback::destroy(jniCallback); + throw; + } +#endif + }); } extern "C" JNIEXPORT void JNICALL @@ -511,6 +570,12 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j return (jboolean)engine->isAutomaticInstancingEnabled(); } +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Engine_nHasUnrecoverableFailure(JNIEnv*, jclass, jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jboolean)engine->hasUnrecoverableFailure(); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nGetMaxStereoscopicEyes(JNIEnv*, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; @@ -526,10 +591,12 @@ Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jcla } extern "C" JNIEXPORT jint JNICALL -Java_com_google_android_filament_Engine_nSetActiveFeatureLevel(JNIEnv *, jclass, +Java_com_google_android_filament_Engine_nSetActiveFeatureLevel(JNIEnv *env, jclass, jlong nativeEngine, jint ordinal) { Engine* engine = (Engine*) nativeEngine; - return (jint)engine->setActiveFeatureLevel((Engine::FeatureLevel)ordinal); + return wrapJni(env, [=]() { + return (jint)engine->setActiveFeatureLevel((Engine::FeatureLevel)ordinal); + }); } extern "C" JNIEXPORT jint JNICALL @@ -651,9 +718,11 @@ Java_com_google_android_filament_Engine_nSetBuilderFeature(JNIEnv *env, jclass c } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) { +Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv *env, jclass, jlong nativeBuilder) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; - return (jlong) builder->build(); + return wrapJniBackend(env, [=]() { + return (jlong) builder->build(); + }); } extern "C" diff --git a/android/filament-android/src/main/cpp/Fence.cpp b/android/filament-android/src/main/cpp/Fence.cpp index 9cd23e42233e..3b7fd816dab0 100644 --- a/android/filament-android/src/main/cpp/Fence.cpp +++ b/android/filament-android/src/main/cpp/Fence.cpp @@ -17,6 +17,7 @@ #include #include +#include using namespace filament; @@ -24,13 +25,17 @@ extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_Fence_nWait(JNIEnv *env, jclass type, jlong nativeFence, jint mode, jlong timeoutNanoSeconds) { Fence *fence = (Fence *) nativeFence; - return (jint) fence->wait((Fence::Mode) mode, (uint64_t) timeoutNanoSeconds); + return filament::android::wrapJniBackend(env, [=]() { + return (jint) fence->wait((Fence::Mode) mode, (uint64_t) timeoutNanoSeconds); + }); } extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_Fence_nWaitAndDestroy(JNIEnv *env, jclass type, jlong nativeFence, jint mode) { Fence *fence = (Fence *) nativeFence; - return (jint) Fence::waitAndDestroy(fence, (Fence::Mode) mode); + return filament::android::wrapJniBackend(env, [=]() { + return (jint) Fence::waitAndDestroy(fence, (Fence::Mode) mode); + }); } diff --git a/android/filament-android/src/main/cpp/IndexBuffer.cpp b/android/filament-android/src/main/cpp/IndexBuffer.cpp index f88f95fd6a2f..935e5e919ce7 100644 --- a/android/filament-android/src/main/cpp/IndexBuffer.cpp +++ b/android/filament-android/src/main/cpp/IndexBuffer.cpp @@ -16,11 +16,11 @@ #include -#include #include #include #include +#include #include @@ -29,6 +29,7 @@ using namespace filament; using namespace backend; +using namespace filament::android; extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_IndexBuffer_nCreateBuilder(JNIEnv *env, jclass type) { @@ -63,7 +64,9 @@ Java_com_google_android_filament_IndexBuffer_nBuilderBuild(JNIEnv *env, jclass t jlong nativeBuilder, jlong nativeEngine) { IndexBuffer::Builder* builder = (IndexBuffer::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT jint JNICALL @@ -94,7 +97,9 @@ Java_com_google_android_filament_IndexBuffer_nSetBuffer(JNIEnv *env, jclass type BufferDescriptor desc(data, sizeInBytes, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - indexBuffer->setBuffer(*engine, std::move(desc), (uint32_t) destOffsetInBytes); + wrapJni(env, [&]() { + indexBuffer->setBuffer(*engine, std::move(desc), (uint32_t) destOffsetInBytes); + }); return 0; } diff --git a/android/filament-android/src/main/cpp/IndirectLight.cpp b/android/filament-android/src/main/cpp/IndirectLight.cpp index 3834521b437c..30b9c2e1f746 100644 --- a/android/filament-android/src/main/cpp/IndirectLight.cpp +++ b/android/filament-android/src/main/cpp/IndirectLight.cpp @@ -21,6 +21,7 @@ #include #include #include +#include using namespace filament; @@ -37,11 +38,13 @@ Java_com_google_android_filament_IndirectLight_nDestroyBuilder(JNIEnv*, jclass, } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_IndirectLight_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_IndirectLight_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine) { IndirectLight::Builder* builder = (IndirectLight::Builder*) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index 23b9a7af2128..225ac06084d6 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -24,6 +25,7 @@ using namespace filament; using namespace utils; +using namespace filament::android; extern "C" JNIEXPORT jint JNICALL Java_com_google_android_filament_LightManager_nGetComponentCount(JNIEnv*, jclass, @@ -210,11 +212,13 @@ Java_com_google_android_filament_LightManager_nBuilderLightChannel(JNIEnv*, jcla } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_LightManager_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_LightManager_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine, jint entity) { LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return jboolean(builder->build(*engine, (Entity &) entity) == LightManager::Builder::Success); + return wrapJni(env, [=]() { + return jboolean(builder->build(*engine, (Entity &) entity) == LightManager::Builder::Success); + }); } // ------------------------------------------------------------------------------------------------ @@ -223,7 +227,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_LightManager_nComputeUniformSplits(JNIEnv* env, jclass, jfloatArray splitPositions, jint cascades) { jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); - LightManager::ShadowCascades::computeUniformSplits(nativeSplits, (uint8_t) cascades); + wrapJni(env, [=]() { + LightManager::ShadowCascades::computeUniformSplits(nativeSplits, (uint8_t) cascades); + }); env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); } @@ -231,7 +237,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_LightManager_nComputeLogSplits(JNIEnv* env, jclass, jfloatArray splitPositions, jint cascades, jfloat near, jfloat far) { jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); - LightManager::ShadowCascades::computeLogSplits(nativeSplits, (uint8_t) cascades, near, far); + wrapJni(env, [=]() { + LightManager::ShadowCascades::computeLogSplits(nativeSplits, (uint8_t) cascades, near, far); + }); env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); } @@ -239,7 +247,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_LightManager_nComputePracticalSplits(JNIEnv* env, jclass, jfloatArray splitPositions, jint cascades, jfloat near, jfloat far, jfloat lambda) { jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); - LightManager::ShadowCascades::computePracticalSplits(nativeSplits, (uint8_t) cascades, near, far, lambda); + wrapJni(env, [=]() { + LightManager::ShadowCascades::computePracticalSplits(nativeSplits, (uint8_t) cascades, near, far, lambda); + }); env->ReleaseFloatArrayElements(splitPositions, nativeSplits, 0); } diff --git a/android/filament-android/src/main/cpp/Material.cpp b/android/filament-android/src/main/cpp/Material.cpp index f3fb0e91ea9d..924db9696d4f 100644 --- a/android/filament-android/src/main/cpp/Material.cpp +++ b/android/filament-android/src/main/cpp/Material.cpp @@ -20,25 +20,28 @@ #include "common/NioUtils.h" #include "common/CallbackUtils.h" +#include using namespace filament; +using namespace filament::android; extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass, jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) { Engine* engine = (Engine*) nativeEngine; - AutoBuffer buffer(env, buffer_, size); - auto builder = Material::Builder(); - if (shBandCount) { - builder.sphericalHarmonicsBandCount(shBandCount); - } - builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality); - builder.uboBatching((Material::UboBatchingMode)uboBatchingMode); - Material* material = builder - .package(buffer.getData(), buffer.getSize()) - .build(*engine); - - return (jlong) material; + return wrapJni(env, [=]() { + AutoBuffer buffer(env, buffer_, size); + auto builder = Material::Builder(); + if (shBandCount) { + builder.sphericalHarmonicsBandCount(shBandCount); + } + builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality); + builder.uboBatching((Material::UboBatchingMode)uboBatchingMode); + Material* material = builder + .package(buffer.getData(), buffer.getSize()) + .build(*engine); + return (jlong) material; + }); } extern "C" JNIEXPORT jlong JNICALL diff --git a/android/filament-android/src/main/cpp/MaterialInstance.cpp b/android/filament-android/src/main/cpp/MaterialInstance.cpp index 48997326eaa5..5e6d35bcd153 100644 --- a/android/filament-android/src/main/cpp/MaterialInstance.cpp +++ b/android/filament-android/src/main/cpp/MaterialInstance.cpp @@ -25,9 +25,11 @@ #include #include #include +#include using namespace filament; using namespace filament::math; +using namespace filament::android; enum BooleanElement { BOOL, @@ -56,7 +58,9 @@ template static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) { MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance; const char *name = env->GetStringUTFChars(name_, 0); - instance->setParameter(name, v); + wrapJni(env, [=]() { + instance->setParameter(name, v); + }); env->ReleaseStringUTFChars(name_, name); } @@ -160,20 +164,22 @@ Java_com_google_android_filament_MaterialInstance_nSetBooleanParameterArray(JNIE // NOTE: In C++, bool has an implementation-defined size. Here we assume // it has the same size as jboolean, which is 1 byte. - switch ((BooleanElement) element) { - case BOOL: - instance->setParameter(name, ((const bool*) v) + offset, count); - break; - case BOOL2: - instance->setParameter(name, ((const bool2*) v) + offset, count); - break; - case BOOL3: - instance->setParameter(name, ((const bool3*) v) + offset, count); - break; - case BOOL4: - instance->setParameter(name, ((const bool4*) v) + offset, count); - break; - } + wrapJni(env, [=]() { + switch ((BooleanElement) element) { + case BOOL: + instance->setParameter(name, ((const bool*) v) + offset, count); + break; + case BOOL2: + instance->setParameter(name, ((const bool2*) v) + offset, count); + break; + case BOOL3: + instance->setParameter(name, ((const bool3*) v) + offset, count); + break; + case BOOL4: + instance->setParameter(name, ((const bool4*) v) + offset, count); + break; + } + }); env->ReleaseBooleanArrayElements(v_, v, 0); @@ -190,20 +196,22 @@ Java_com_google_android_filament_MaterialInstance_nSetIntParameterArray(JNIEnv * const char* name = env->GetStringUTFChars(name_, 0); jint* v = env->GetIntArrayElements(v_, NULL); - switch ((IntElement) element) { - case INT: - instance->setParameter(name, ((const int32_t*) v) + offset, count); - break; - case INT2: - instance->setParameter(name, ((const int2*) v) + offset, count); - break; - case INT3: - instance->setParameter(name, ((const int3*) v) + offset, count); - break; - case INT4: - instance->setParameter(name, ((const int4*) v) + offset, count); - break; - } + wrapJni(env, [=]() { + switch ((IntElement) element) { + case INT: + instance->setParameter(name, ((const int32_t*) v) + offset, count); + break; + case INT2: + instance->setParameter(name, ((const int2*) v) + offset, count); + break; + case INT3: + instance->setParameter(name, ((const int3*) v) + offset, count); + break; + case INT4: + instance->setParameter(name, ((const int4*) v) + offset, count); + break; + } + }); env->ReleaseIntArrayElements(v_, v, JNI_ABORT); @@ -220,26 +228,28 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv const char* name = env->GetStringUTFChars(name_, 0); jfloat* v = env->GetFloatArrayElements(v_, NULL); - switch ((FloatElement) element) { - case FLOAT: - instance->setParameter(name, ((const float*) v) + offset, count); - break; - case FLOAT2: - instance->setParameter(name, ((const float2*) v) + offset, count); - break; - case FLOAT3: - instance->setParameter(name, ((const float3*) v) + offset, count); - break; - case FLOAT4: - instance->setParameter(name, ((const float4*) v) + offset, count); - break; - case MAT3: - instance->setParameter(name, ((const mat3f*) v) + offset, count); - break; - case MAT4: - instance->setParameter(name, ((const mat4f*) v) + offset, count); - break; - } + wrapJni(env, [=]() { + switch ((FloatElement) element) { + case FLOAT: + instance->setParameter(name, ((const float*) v) + offset, count); + break; + case FLOAT2: + instance->setParameter(name, ((const float2*) v) + offset, count); + break; + case FLOAT3: + instance->setParameter(name, ((const float3*) v) + offset, count); + break; + case FLOAT4: + instance->setParameter(name, ((const float4*) v) + offset, count); + break; + case MAT3: + instance->setParameter(name, ((const mat3f*) v) + offset, count); + break; + case MAT4: + instance->setParameter(name, ((const mat4f*) v) + offset, count); + break; + } + }); env->ReleaseFloatArrayElements(v_, v, 0); @@ -260,7 +270,9 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture( Texture* texture = (Texture*) nativeTexture; const char *name = env->GetStringUTFChars(name_, 0); - instance->setParameter(name, texture, JniUtils::from_long(sampler_)); + wrapJni(env, [=]() { + instance->setParameter(name, texture, JniUtils::from_long(sampler_)); + }); env->ReleaseStringUTFChars(name_, name); } diff --git a/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp index cd6d31c2c3d1..adfb09d7a9cd 100644 --- a/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp +++ b/android/filament-android/src/main/cpp/MorphTargetBuffer.cpp @@ -16,7 +16,6 @@ #include -#include #include #include @@ -24,6 +23,7 @@ #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include using namespace filament; using namespace backend; @@ -81,11 +81,13 @@ extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine) { MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } // ------------------------------------------------------------------------------------------------ @@ -97,11 +99,13 @@ Java_com_google_android_filament_MorphTargetBuffer_nSetPositionsAt(JNIEnv* env, jint targetIndex, jfloatArray positions, jint count) { MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; Engine *engine = (Engine *) nativeEngine; - jfloat* data = env->GetFloatArrayElements(positions, NULL); - morphTargetBuffer->setPositionsAt(*engine, targetIndex, - (math::float4*) data, size_t(count)); - env->ReleaseFloatArrayElements(positions, data, JNI_ABORT); - return 0; + return filament::android::wrapJni(env, [=]() { + jfloat* data = env->GetFloatArrayElements(positions, NULL); + morphTargetBuffer->setPositionsAt(*engine, targetIndex, + (math::float4*) data, size_t(count)); + env->ReleaseFloatArrayElements(positions, data, JNI_ABORT); + return 0; + }); } extern "C" @@ -111,11 +115,13 @@ Java_com_google_android_filament_MorphTargetBuffer_nSetTangentsAt(JNIEnv* env, j jint targetIndex, jshortArray tangents, jint count) { MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject; Engine *engine = (Engine *) nativeEngine; - jshort* data = env->GetShortArrayElements(tangents, NULL); - morphTargetBuffer->setTangentsAt(*engine, targetIndex, - (math::short4*) data, size_t(count)); - env->ReleaseShortArrayElements(tangents, data, JNI_ABORT); - return 0; + return filament::android::wrapJni(env, [=]() { + jshort* data = env->GetShortArrayElements(tangents, NULL); + morphTargetBuffer->setTangentsAt(*engine, targetIndex, + (math::short4*) data, size_t(count)); + env->ReleaseShortArrayElements(tangents, data, JNI_ABORT); + return 0; + }); } extern "C" diff --git a/android/filament-android/src/main/cpp/RenderTarget.cpp b/android/filament-android/src/main/cpp/RenderTarget.cpp index a5289ae06324..34e71e388bdd 100644 --- a/android/filament-android/src/main/cpp/RenderTarget.cpp +++ b/android/filament-android/src/main/cpp/RenderTarget.cpp @@ -21,6 +21,7 @@ #include #include +#include using namespace filament; using namespace backend; @@ -72,7 +73,9 @@ Java_com_google_android_filament_RenderTarget_nBuilderBuild(JNIEnv *env, jclass jlong nativeBuilder, jlong nativeEngine) { RenderTarget::Builder* builder = (RenderTarget::Builder*) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index a66ebfccdd41..6b18aaacfbcd 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -25,6 +26,7 @@ using namespace filament; using namespace utils; +using namespace filament::android; extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_RenderableManager_nHasComponent(JNIEnv*, jclass, @@ -63,11 +65,13 @@ Java_com_google_android_filament_RenderableManager_nDestroyBuilder(JNIEnv*, jcla } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_RenderableManager_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_RenderableManager_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine, jint entity) { RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return jboolean(builder->build(*engine, (Entity &) entity) == RenderableManager::Builder::Success); + return wrapJni(env, [=]() { + return jboolean(builder->build(*engine, (Entity &) entity) == RenderableManager::Builder::Success); + }); } extern "C" JNIEXPORT void JNICALL @@ -276,11 +280,13 @@ Java_com_google_android_filament_RenderableManager_nBuilderInstances(JNIEnv*, jc // ------------------------------------------------------------------------------------------------ extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_RenderableManager_nSetSkinningBuffer(JNIEnv*, jclass, +Java_com_google_android_filament_RenderableManager_nSetSkinningBuffer(JNIEnv* env, jclass, jlong nativeRenderableManager, jint i, jlong nativeSkinningBuffer, jint count, jint offset) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; SkinningBuffer *sb = (SkinningBuffer *) nativeSkinningBuffer; - rm->setSkinningBuffer(i, sb, count, offset); + wrapJni(env, [=]() { + rm->setSkinningBuffer(i, sb, count, offset); + }); } extern "C" JNIEXPORT jint JNICALL @@ -295,9 +301,12 @@ Java_com_google_android_filament_RenderableManager_nSetBonesAsMatrices(JNIEnv* e // BufferOverflowException return -1; } - rm->setBones((RenderableManager::Instance)i, - static_cast(data), (size_t)boneCount, (size_t)offset); - return 0; + jint result = 0; + wrapJni(env, [=]() { + rm->setBones((RenderableManager::Instance)i, + static_cast(data), (size_t)boneCount, (size_t)offset); + }); + return result; } extern "C" JNIEXPORT jint JNICALL @@ -312,9 +321,12 @@ Java_com_google_android_filament_RenderableManager_nSetBonesAsQuaternions(JNIEnv // BufferOverflowException return -1; } - rm->setBones((RenderableManager::Instance)i, - static_cast(data), (size_t)boneCount, (size_t)offset); - return 0; + jint result = 0; + wrapJni(env, [=]() { + rm->setBones((RenderableManager::Instance)i, + static_cast(data), (size_t)boneCount, (size_t)offset); + }); + return result; } extern "C" JNIEXPORT void JNICALL @@ -323,17 +335,21 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env, RenderableManager *rm = (RenderableManager *) nativeRenderableManager; jfloat* vec = env->GetFloatArrayElements(weights, NULL); jsize count = env->GetArrayLength(weights); - rm->setMorphWeights((RenderableManager::Instance)instance, vec, count, offset); + wrapJni(env, [=]() { + rm->setMorphWeights((RenderableManager::Instance)instance, vec, count, offset); + }); env->ReleaseFloatArrayElements(weights, vec, JNI_ABORT); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*, +Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv* env, jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex, jlong, jint offset) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; - rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level, - (size_t) primitiveIndex, (size_t) offset); + wrapJni(env, [=]() { + rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level, + (size_t) primitiveIndex, (size_t) offset); + }); } extern "C" JNIEXPORT jint JNICALL @@ -344,12 +360,14 @@ Java_com_google_android_filament_RenderableManager_nGetMorphTargetCount(JNIEnv* } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_RenderableManager_nSetAxisAlignedBoundingBox(JNIEnv*, +Java_com_google_android_filament_RenderableManager_nSetAxisAlignedBoundingBox(JNIEnv* env, jclass, jlong nativeRenderableManager, jint i, jfloat cx, jfloat cy, jfloat cz, jfloat ex, jfloat ey, jfloat ez) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; - rm->setAxisAlignedBoundingBox((RenderableManager::Instance) i, {{cx, cy, cz}, - {ex, ey, ez}}); + wrapJni(env, [=]() { + rm->setAxisAlignedBoundingBox((RenderableManager::Instance) i, {{cx, cy, cz}, + {ex, ey, ez}}); + }); } extern "C" JNIEXPORT void JNICALL @@ -486,19 +504,23 @@ Java_com_google_android_filament_RenderableManager_nGetInstanceCount(JNIEnv*, jc } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_RenderableManager_nSetMaterialInstanceAt(JNIEnv*, jclass, +Java_com_google_android_filament_RenderableManager_nSetMaterialInstanceAt(JNIEnv* env, jclass, jlong nativeRenderableManager, jint i, jint primitiveIndex, jlong nativeMaterialInstance) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; const MaterialInstance *materialInstance = (const MaterialInstance *) nativeMaterialInstance; - rm->setMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex, - materialInstance); + wrapJni(env, [=]() { + rm->setMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex, + materialInstance); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_RenderableManager_nClearMaterialInstanceAt(JNIEnv*, jclass, +Java_com_google_android_filament_RenderableManager_nClearMaterialInstanceAt(JNIEnv* env, jclass, jlong nativeRenderableManager, jint i, jint primitiveIndex) { RenderableManager *rm = (RenderableManager *) nativeRenderableManager; - rm->clearMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex); + wrapJni(env, [=]() { + rm->clearMaterialInstanceAt((RenderableManager::Instance) i, (size_t) primitiveIndex); + }); } extern "C" JNIEXPORT jlong JNICALL diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp index 2a2659cece3c..c2d7dd3d4d77 100644 --- a/android/filament-android/src/main/cpp/Renderer.cpp +++ b/android/filament-android/src/main/cpp/Renderer.cpp @@ -22,18 +22,24 @@ #include #include +#include + #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include "common/JniUtils.h" using namespace filament; using namespace backend; +using namespace filament::android; extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer, +Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *env, jclass, jlong nativeRenderer, jlong vsyncSteadyClockTimeNano) { Renderer *renderer = (Renderer *) nativeRenderer; - renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano)); + wrapJni(env, [=]() { + renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano)); + }); } extern "C" JNIEXPORT jboolean JNICALL @@ -43,37 +49,45 @@ Java_com_google_android_filament_Renderer_nShouldRenderFrame(JNIEnv *, jclass, j } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer, +Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *env, jclass, jlong nativeRenderer, jlong nativeSwapChain, jlong frameTimeNanos) { Renderer *renderer = (Renderer *) nativeRenderer; SwapChain *swapChain = (SwapChain *) nativeSwapChain; - return (jboolean) renderer->beginFrame(swapChain, uint64_t(frameTimeNanos)); + return wrapJniBackend(env, [=]() { + return (jboolean) renderer->beginFrame(swapChain, uint64_t(frameTimeNanos)); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nEndFrame(JNIEnv *, jclass, jlong nativeRenderer) { +Java_com_google_android_filament_Renderer_nEndFrame(JNIEnv *env, jclass, jlong nativeRenderer) { Renderer *renderer = (Renderer *) nativeRenderer; - renderer->endFrame(); + wrapJniBackend(env, [=]() { + renderer->endFrame(); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nRender(JNIEnv *, jclass, jlong nativeRenderer, +Java_com_google_android_filament_Renderer_nRender(JNIEnv *env, jclass, jlong nativeRenderer, jlong nativeView) { Renderer *renderer = (Renderer *) nativeRenderer; View *view = (View *) nativeView; - renderer->render(view); + wrapJniBackend(env, [=]() { + renderer->render(view); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nRenderStandaloneView(JNIEnv *, jclass, jlong nativeRenderer, +Java_com_google_android_filament_Renderer_nRenderStandaloneView(JNIEnv *env, jclass, jlong nativeRenderer, jlong nativeView) { Renderer *renderer = (Renderer *) nativeRenderer; View *view = (View *) nativeView; - renderer->renderStandaloneView(view); + wrapJni(env, [=]() { + renderer->renderStandaloneView(view); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nCopyFrame(JNIEnv *, jclass, jlong nativeRenderer, +Java_com_google_android_filament_Renderer_nCopyFrame(JNIEnv *env, jclass, jlong nativeRenderer, jlong nativeDstSwapChain, jint dstLeft, jint dstBottom, jint dstWidth, jint dstHeight, jint srcLeft, jint srcBottom, jint srcWidth, jint srcHeight, @@ -82,7 +96,9 @@ Java_com_google_android_filament_Renderer_nCopyFrame(JNIEnv *, jclass, jlong nat SwapChain *dstSwapChain = (SwapChain *) nativeDstSwapChain; const filament::Viewport dstViewport {dstLeft, dstBottom, (uint32_t) dstWidth, (uint32_t) dstHeight}; const filament::Viewport srcViewport {srcLeft, srcBottom, (uint32_t) srcWidth, (uint32_t) srcHeight}; - renderer->copyFrame(dstSwapChain, dstViewport, srcViewport, (uint32_t) flags); + wrapJni(env, [=]() { + renderer->copyFrame(dstSwapChain, dstViewport, srcViewport, (uint32_t) flags); + }); } extern "C" JNIEXPORT jint JNICALL @@ -114,10 +130,11 @@ Java_com_google_android_filament_Renderer_nReadPixels(JNIEnv *env, jclass, (uint32_t) stride, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - renderer->readPixels(uint32_t(xoffset), uint32_t(yoffset), uint32_t(width), uint32_t(height), - std::move(desc)); - - return 0; + return wrapJni(env, [&]() { + renderer->readPixels(uint32_t(xoffset), uint32_t(yoffset), uint32_t(width), uint32_t(height), + std::move(desc)); + return 0; + }); } extern "C" JNIEXPORT jint JNICALL @@ -150,23 +167,28 @@ Java_com_google_android_filament_Renderer_nReadPixelsEx(JNIEnv *env, jclass, (uint32_t) stride, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - renderer->readPixels(renderTarget, - uint32_t(xoffset), uint32_t(yoffset), uint32_t(width), uint32_t(height), - std::move(desc)); - - return 0; + return wrapJni(env, [&]() { + renderer->readPixels(renderTarget, + uint32_t(xoffset), uint32_t(yoffset), uint32_t(width), uint32_t(height), + std::move(desc)); + return 0; + }); } extern "C" JNIEXPORT jdouble JNICALL -Java_com_google_android_filament_Renderer_nGetUserTime(JNIEnv*, jclass, jlong nativeRenderer) { +Java_com_google_android_filament_Renderer_nGetUserTime(JNIEnv *env, jclass, jlong nativeRenderer) { Renderer *renderer = (Renderer *) nativeRenderer; - return renderer->getUserTime(); + return wrapJni(env, [=]() { + return renderer->getUserTime(); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nResetUserTime(JNIEnv*, jclass, jlong nativeRenderer) { +Java_com_google_android_filament_Renderer_nResetUserTime(JNIEnv *env, jclass, jlong nativeRenderer) { Renderer *renderer = (Renderer *) nativeRenderer; - renderer->resetUserTime(); + wrapJni(env, [=]() { + renderer->resetUserTime(); + }); } extern "C" JNIEXPORT void JNICALL @@ -186,20 +208,24 @@ Java_com_google_android_filament_Renderer_nSetFrameRateOptions(JNIEnv*, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nSetClearOptions(JNIEnv *, jclass , +Java_com_google_android_filament_Renderer_nSetClearOptions(JNIEnv *env, jclass , jlong nativeRenderer, jfloat r, jfloat g, jfloat b, jfloat a, jboolean clear, jboolean discard) { Renderer *renderer = (Renderer *) nativeRenderer; - renderer->setClearOptions({ .clearColor = {r, g, b, a}, - .clear = (bool) clear, - .discard = (bool) discard}); + wrapJni(env, [=]() { + renderer->setClearOptions({ .clearColor = {r, g, b, a}, + .clear = (bool) clear, + .discard = (bool) discard}); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Renderer_nSetPresentationTime(JNIEnv *, jclass , +Java_com_google_android_filament_Renderer_nSetPresentationTime(JNIEnv *env, jclass , jlong nativeRenderer, jlong monotonicClockNanos) { Renderer *renderer = (Renderer *) nativeRenderer; - renderer->setPresentationTime(monotonicClockNanos); + wrapJni(env, [=]() { + renderer->setPresentationTime(monotonicClockNanos); + }); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/Scene.cpp b/android/filament-android/src/main/cpp/Scene.cpp index bfbc70d5e4c0..3527b2669723 100644 --- a/android/filament-android/src/main/cpp/Scene.cpp +++ b/android/filament-android/src/main/cpp/Scene.cpp @@ -19,9 +19,11 @@ #include #include +#include using namespace filament; using namespace utils; +using namespace filament::android; extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Scene_nSetSkybox(JNIEnv *env, jclass type, jlong nativeScene, @@ -43,15 +45,20 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Scene_nAddEntity(JNIEnv *env, jclass type, jlong nativeScene, jint entity) { Scene* scene = (Scene*) nativeScene; - scene->addEntity((Entity&) entity); + wrapJni(env, [=]() { + scene->addEntity((Entity&) entity); + }); } extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Scene_nAddEntities(JNIEnv *env, jclass type, jlong nativeScene, jintArray entities) { Scene* scene = (Scene*) nativeScene; + jsize length = env->GetArrayLength(entities); Entity* nativeEntities = (Entity*) env->GetIntArrayElements(entities, nullptr); - scene->addEntities(nativeEntities, env->GetArrayLength(entities)); + wrapJni(env, [=]() { + scene->addEntities(nativeEntities, length); + }); env->ReleaseIntArrayElements(entities, (jint*) nativeEntities, JNI_ABORT); } @@ -59,15 +66,20 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Scene_nRemove(JNIEnv *env, jclass type, jlong nativeScene, jint entity) { Scene* scene = (Scene*) nativeScene; - scene->remove((Entity&) entity); + wrapJni(env, [=]() { + scene->remove((Entity&) entity); + }); } extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Scene_nRemoveEntities(JNIEnv *env, jclass type, jlong nativeScene, jintArray entities) { Scene* scene = (Scene*) nativeScene; + jsize length = env->GetArrayLength(entities); Entity* nativeEntities = (Entity*) env->GetIntArrayElements(entities, nullptr); - scene->removeEntities(nativeEntities, env->GetArrayLength(entities)); + wrapJni(env, [=]() { + scene->removeEntities(nativeEntities, length); + }); env->ReleaseIntArrayElements(entities, (jint*) nativeEntities, JNI_ABORT); } diff --git a/android/filament-android/src/main/cpp/SkinningBuffer.cpp b/android/filament-android/src/main/cpp/SkinningBuffer.cpp index 7980ffee79ab..6f2a11f39594 100644 --- a/android/filament-android/src/main/cpp/SkinningBuffer.cpp +++ b/android/filament-android/src/main/cpp/SkinningBuffer.cpp @@ -16,7 +16,6 @@ #include -#include #include #include @@ -24,6 +23,7 @@ #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include using namespace filament; using namespace backend; @@ -60,11 +60,13 @@ Java_com_google_android_filament_SkinningBuffer_nBuilderInitialize(JNIEnv*, jcla extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_SkinningBuffer_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_SkinningBuffer_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine) { SkinningBuffer::Builder* builder = (SkinningBuffer::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } // ------------------------------------------------------------------------------------------------ @@ -76,16 +78,18 @@ Java_com_google_android_filament_SkinningBuffer_nSetBonesAsMatrices(JNIEnv* env, jint offset) { SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; Engine *engine = (Engine *) nativeEngine; - AutoBuffer nioBuffer(env, matrices, boneCount * 16); - void* data = nioBuffer.getData(); - size_t sizeInBytes = nioBuffer.getSize(); - if (sizeInBytes > (remaining << nioBuffer.getShift())) { - // BufferOverflowException - return -1; - } - skinningBuffer->setBones(*engine, - static_cast(data), (size_t)boneCount, (size_t)offset); - return 0; + return filament::android::wrapJni(env, [=]() { + AutoBuffer nioBuffer(env, matrices, boneCount * 16); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + skinningBuffer->setBones(*engine, + static_cast(data), (size_t)boneCount, (size_t)offset); + return 0; + }); } extern "C" @@ -95,16 +99,18 @@ Java_com_google_android_filament_SkinningBuffer_nSetBonesAsQuaternions(JNIEnv* e jint boneCount, jint offset) { SkinningBuffer *skinningBuffer = (SkinningBuffer *) nativeSkinningBuffer; Engine *engine = (Engine *) nativeEngine; - AutoBuffer nioBuffer(env, quaternions, boneCount * 8); - void* data = nioBuffer.getData(); - size_t sizeInBytes = nioBuffer.getSize(); - if (sizeInBytes > (remaining << nioBuffer.getShift())) { - // BufferOverflowException - return -1; - } - skinningBuffer->setBones(*engine, - static_cast(data), (size_t)boneCount, (size_t)offset); - return 0; + return filament::android::wrapJni(env, [=]() { + AutoBuffer nioBuffer(env, quaternions, boneCount * 8); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + skinningBuffer->setBones(*engine, + static_cast(data), (size_t)boneCount, (size_t)offset); + return 0; + }); } extern "C" diff --git a/android/filament-android/src/main/cpp/SkyBox.cpp b/android/filament-android/src/main/cpp/SkyBox.cpp index 6e1cc19976dd..4959c5742bae 100644 --- a/android/filament-android/src/main/cpp/SkyBox.cpp +++ b/android/filament-android/src/main/cpp/SkyBox.cpp @@ -19,6 +19,7 @@ #include #include +#include using namespace filament; @@ -76,7 +77,9 @@ Java_com_google_android_filament_Skybox_nBuilderBuild(JNIEnv *env, jclass type, jlong nativeSkyBoxBuilder, jlong nativeEngine) { Skybox::Builder *builder = (Skybox::Builder *) nativeSkyBoxBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/Stream.cpp b/android/filament-android/src/main/cpp/Stream.cpp index ecac11dc9134..f4db3dcfac8b 100644 --- a/android/filament-android/src/main/cpp/Stream.cpp +++ b/android/filament-android/src/main/cpp/Stream.cpp @@ -23,6 +23,7 @@ #include "common/NioUtils.h" #include "common/CallbackUtils.h" +#include #ifdef __ANDROID__ @@ -113,11 +114,13 @@ Java_com_google_android_filament_Stream_nBuilderHeight(JNIEnv*, jclass, } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_Stream_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_Stream_nBuilderBuild(JNIEnv* env, jclass, jlong nativeStreamBuilder, jlong nativeEngine) { StreamBuilder* builder = (StreamBuilder*) nativeStreamBuilder; Engine* engine = (Engine*) nativeEngine; - return (jlong) builder->builder()->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->builder()->build(*engine); + }); } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/cpp/SurfaceOrientation.cpp b/android/filament-android/src/main/cpp/SurfaceOrientation.cpp index 8f51aa8c6878..03d91f99b470 100644 --- a/android/filament-android/src/main/cpp/SurfaceOrientation.cpp +++ b/android/filament-android/src/main/cpp/SurfaceOrientation.cpp @@ -21,6 +21,7 @@ #include "common/NioUtils.h" #include +#include using namespace filament; using namespace filament::geometry; @@ -125,7 +126,9 @@ extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_SurfaceOrientation_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder) { auto wrapper = (JniWrapper *) nativeBuilder; - return (jlong) wrapper->builder->build(); + return filament::android::wrapJni(env, [=]() { + return (jlong) wrapper->builder->build(); + }); } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/cpp/SwapChain.cpp b/android/filament-android/src/main/cpp/SwapChain.cpp index 5f93b325070b..d1d280402f22 100644 --- a/android/filament-android/src/main/cpp/SwapChain.cpp +++ b/android/filament-android/src/main/cpp/SwapChain.cpp @@ -20,6 +20,7 @@ #include #include "common/CallbackUtils.h" +#include using namespace filament; @@ -58,11 +59,13 @@ Java_com_google_android_filament_SwapChain_nSetFrameScheduledCallback(JNIEnv* en jlong nativeSwapChain, jobject handler, jobject runnable) { SwapChain* swapChain = (SwapChain*) nativeSwapChain; auto* callback = JniCallback::make(env, handler, runnable); - swapChain->setFrameScheduledCallback(callback->getHandler(), - [callback](backend::PresentCallable) { - // Ignore PresentCallable, which is only meaningful with the Metal backend. - JniCallback::postToJavaAndDestroy(callback); - }); + filament::android::wrapJni(env, [=]() { + swapChain->setFrameScheduledCallback(callback->getHandler(), + [callback](backend::PresentCallable) { + // Ignore PresentCallable, which is only meaningful with the Metal backend. + JniCallback::postToJavaAndDestroy(callback); + }); + }); } extern "C" JNIEXPORT jboolean JNICALL diff --git a/android/filament-android/src/main/cpp/Texture.cpp b/android/filament-android/src/main/cpp/Texture.cpp index 75d45a4b668b..739b997281cf 100644 --- a/android/filament-android/src/main/cpp/Texture.cpp +++ b/android/filament-android/src/main/cpp/Texture.cpp @@ -17,7 +17,6 @@ #include #include -#include #ifdef __ANDROID__ #include @@ -38,12 +37,14 @@ #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include #include "private/backend/VirtualMachineEnv.h" using namespace filament; using namespace backend; +using namespace filament::android; static size_t getTextureDataSize(const Texture *texture, size_t level, Texture::Format format, Texture::Type type, @@ -273,12 +274,13 @@ Java_com_google_android_filament_Texture_nSetImage3D(JNIEnv* env, jclass, jlong (uint32_t) stride, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - texture->setImage(*engine, (size_t) level, - (uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset, - (uint32_t) width, (uint32_t) height, (uint32_t) depth, - std::move(desc)); - - return 0; + return wrapJni(env, [&]() { + texture->setImage(*engine, (size_t) level, + (uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset, + (uint32_t) width, (uint32_t) height, (uint32_t) depth, + std::move(desc)); + return 0; + }); } extern "C" JNIEXPORT jint JNICALL @@ -307,12 +309,13 @@ Java_com_google_android_filament_Texture_nSetImage3DCompressed(JNIEnv *env, jcla (backend::CompressedPixelDataType) compressedFormat, (uint32_t) compressedSizeInBytes, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - texture->setImage(*engine, (size_t) level, - (uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset, - (uint32_t) width, (uint32_t) height, (uint32_t) depth, - std::move(desc)); - - return 0; + return wrapJni(env, [&]() { + texture->setImage(*engine, (size_t) level, + (uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset, + (uint32_t) width, (uint32_t) height, (uint32_t) depth, + std::move(desc)); + return 0; + }); } extern "C" JNIEXPORT jint JNICALL @@ -346,12 +349,13 @@ Java_com_google_android_filament_Texture_nSetImageCubemap(JNIEnv *env, jclass, (uint32_t) stride, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); + return wrapJni(env, [&]() { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets); + texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets); #pragma clang diagnostic pop - - return 0; + return 0; + }); } extern "C" JNIEXPORT jint JNICALL @@ -384,20 +388,23 @@ Java_com_google_android_filament_Texture_nSetImageCubemapCompressed(JNIEnv *env, (backend::CompressedPixelDataType) compressedFormat, (uint32_t) compressedSizeInBytes, callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); + return wrapJni(env, [&]() { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets); + texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets); #pragma clang diagnostic pop - - return 0; + return 0; + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Texture_nSetExternalImage(JNIEnv*, jclass, jlong nativeTexture, +Java_com_google_android_filament_Texture_nSetExternalImage(JNIEnv* env, jclass, jlong nativeTexture, jlong nativeEngine, jlong eglImage) { Texture *texture = (Texture *) nativeTexture; Engine *engine = (Engine *) nativeEngine; - texture->setExternalImage(*engine, (void*)eglImage); + wrapJni(env, [=]() { + texture->setExternalImage(*engine, (void*)eglImage); + }); } extern "C" @@ -418,33 +425,35 @@ Java_com_google_android_filament_Texture_nSetExternalImageByAHB(JNIEnv *env, jcl return JNI_FALSE; } - if (engine->getBackend() == Backend::OPENGL) { - // CAVEAT: we assume that Backend::OPENGL on Android implies PlatformEGLAndroid. + return wrapJni(env, [=]() { + if (engine->getBackend() == Backend::OPENGL) { + // CAVEAT: we assume that Backend::OPENGL on Android implies PlatformEGLAndroid. #if UTILS_HAS_RTTI - if (!dynamic_cast(platform)) { - return JNI_FALSE; - } + if (!dynamic_cast(platform)) { + return JNI_FALSE; + } #endif - auto* eglPlatform = (PlatformEGLAndroid*) platform; - auto ref = eglPlatform->createExternalImage(nativeBuffer, false); - texture->setExternalImage(*engine, ref); - } + auto* eglPlatform = (PlatformEGLAndroid*) platform; + auto ref = eglPlatform->createExternalImage(nativeBuffer, false); + texture->setExternalImage(*engine, ref); + } #if FILAMENT_SUPPORTS_VULKAN - else if (engine->getBackend() == Backend::VULKAN) { - // CAVEAT: we assume that Backend::VULKAN on Android implies VulkanPlatformAndroid. + else if (engine->getBackend() == Backend::VULKAN) { + // CAVEAT: we assume that Backend::VULKAN on Android implies VulkanPlatformAndroid. #if UTILS_HAS_RTTI - if (!dynamic_cast(platform)) { - return JNI_FALSE; - } + if (!dynamic_cast(platform)) { + return JNI_FALSE; + } #endif - auto* vulkanPlatform = (VulkanPlatformAndroid*) platform; - auto ref = vulkanPlatform->createExternalImage(nativeBuffer, false); - texture->setExternalImage(*engine, ref); - } + auto* vulkanPlatform = (VulkanPlatformAndroid*) platform; + auto ref = vulkanPlatform->createExternalImage(nativeBuffer, false); + texture->setExternalImage(*engine, ref); + } #endif // FILAMENT_SUPPORTS_VULKAN - // success! - return JNI_TRUE; + // success! + return JNI_TRUE; + }); #else // other platforms could come here return JNI_FALSE; @@ -452,20 +461,24 @@ Java_com_google_android_filament_Texture_nSetExternalImageByAHB(JNIEnv *env, jcl } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Texture_nSetExternalStream(JNIEnv*, jclass, +Java_com_google_android_filament_Texture_nSetExternalStream(JNIEnv* env, jclass, jlong nativeTexture, jlong nativeEngine, jlong nativeStream) { Texture *texture = (Texture *) nativeTexture; Engine *engine = (Engine *) nativeEngine; Stream *stream = (Stream *) nativeStream; - texture->setExternalStream(*engine, stream); + wrapJni(env, [=]() { + texture->setExternalStream(*engine, stream); + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_Texture_nGenerateMipmaps(JNIEnv*, jclass, +Java_com_google_android_filament_Texture_nGenerateMipmaps(JNIEnv* env, jclass, jlong nativeTexture, jlong nativeEngine) { Texture *texture = (Texture *) nativeTexture; Engine *engine = (Engine *) nativeEngine; - texture->generateMipmaps(*engine); + wrapJni(env, [=]() { + texture->generateMipmaps(*engine); + }); } extern "C" @@ -515,9 +528,10 @@ Java_com_google_android_filament_Texture_nGeneratePrefilterMipmap(JNIEnv *env, j options.sampleCount = sampleCount; options.mirror = mirror; - filament::generatePrefilterMipmap(texture, *engine, std::move(desc), faceOffsets, &options); - - return 0; + return wrapJni(env, [&]() { + filament::generatePrefilterMipmap(texture, *engine, std::move(desc), faceOffsets, &options); + return 0; + }); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -640,10 +654,12 @@ Java_com_google_android_filament_android_TextureHelper_nSetBitmap(JNIEnv* env, j autoBitmap->getType(format), &AutoBitmap::invokeNoCallback, autoBitmap); - texture->setImage(*engine, (size_t) level, - (uint32_t) xoffset, (uint32_t) yoffset, - (uint32_t) width, (uint32_t) height, - std::move(desc)); + filament::android::wrapJni(env, [&]() { + texture->setImage(*engine, (size_t) level, + (uint32_t) xoffset, (uint32_t) yoffset, + (uint32_t) width, (uint32_t) height, + std::move(desc)); + }); } extern "C" @@ -663,10 +679,12 @@ Java_com_google_android_filament_android_TextureHelper_nSetBitmapWithCallback(JN autoBitmap->getType(format), autoBitmap->getHandler(), &AutoBitmap::invoke, autoBitmap); - texture->setImage(*engine, (size_t) level, - (uint32_t) xoffset, (uint32_t) yoffset, - (uint32_t) width, (uint32_t) height, - std::move(desc)); + filament::android::wrapJni(env, [&]() { + texture->setImage(*engine, (size_t) level, + (uint32_t) xoffset, (uint32_t) yoffset, + (uint32_t) width, (uint32_t) height, + std::move(desc)); + }); } #endif diff --git a/android/filament-android/src/main/cpp/TransformManager.cpp b/android/filament-android/src/main/cpp/TransformManager.cpp index 580367eb20bd..86f085cd4c88 100644 --- a/android/filament-android/src/main/cpp/TransformManager.cpp +++ b/android/filament-android/src/main/cpp/TransformManager.cpp @@ -22,9 +22,11 @@ #include #include +#include using namespace utils; using namespace filament; +using namespace filament::android; static_assert(sizeof(jint) == sizeof(Entity), "jint and Entity are not compatible!!"); @@ -45,12 +47,14 @@ Java_com_google_android_filament_TransformManager_nGetInstance(JNIEnv*, jclass, } extern "C" JNIEXPORT jint JNICALL -Java_com_google_android_filament_TransformManager_nCreate(JNIEnv*, jclass, +Java_com_google_android_filament_TransformManager_nCreate(JNIEnv* env, jclass, jlong nativeTransformManager, jint entity_) { TransformManager* tm = (TransformManager*) nativeTransformManager; - Entity& entity = *reinterpret_cast(&entity_); - tm->create(entity); - return tm->getInstance(entity); + return wrapJni(env, [=]() { + Entity entity = *reinterpret_cast(&entity_); + tm->create(entity); + return tm->getInstance(entity); + }); } extern "C" JNIEXPORT jint JNICALL @@ -58,16 +62,18 @@ Java_com_google_android_filament_TransformManager_nCreateArray(JNIEnv* env, jclass, jlong nativeTransformManager, jint entity_, jint parent, jfloatArray localTransform_) { TransformManager* tm = (TransformManager*) nativeTransformManager; - Entity& entity = *reinterpret_cast(&entity_); - if (localTransform_) { - jfloat *localTransform = env->GetFloatArrayElements(localTransform_, NULL); - tm->create(entity, (TransformManager::Instance) parent, - *reinterpret_cast(localTransform)); - env->ReleaseFloatArrayElements(localTransform_, localTransform, JNI_ABORT); - } else { - tm->create(entity, (TransformManager::Instance) parent); - } - return tm->getInstance(entity); + return wrapJni(env, [=]() { + Entity entity = *reinterpret_cast(&entity_); + if (localTransform_) { + jfloat *localTransform = env->GetFloatArrayElements(localTransform_, NULL); + tm->create(entity, (TransformManager::Instance) parent, + *reinterpret_cast(localTransform)); + env->ReleaseFloatArrayElements(localTransform_, localTransform, JNI_ABORT); + } else { + tm->create(entity, (TransformManager::Instance) parent); + } + return tm->getInstance(entity); + }); } extern "C" JNIEXPORT jint JNICALL @@ -75,16 +81,18 @@ Java_com_google_android_filament_TransformManager_nCreateArrayFp64(JNIEnv* env, jclass, jlong nativeTransformManager, jint entity_, jint parent, jdoubleArray localTransform_) { TransformManager* tm = (TransformManager*) nativeTransformManager; - Entity& entity = *reinterpret_cast(&entity_); - if (localTransform_) { - jdouble *localTransform = env->GetDoubleArrayElements(localTransform_, NULL); - tm->create(entity, (TransformManager::Instance) parent, - *reinterpret_cast(localTransform)); - env->ReleaseDoubleArrayElements(localTransform_, localTransform, JNI_ABORT); - } else { - tm->create(entity, (TransformManager::Instance) parent); - } - return tm->getInstance(entity); + return wrapJni(env, [=]() { + Entity entity = *reinterpret_cast(&entity_); + if (localTransform_) { + jdouble *localTransform = env->GetDoubleArrayElements(localTransform_, NULL); + tm->create(entity, (TransformManager::Instance) parent, + *reinterpret_cast(localTransform)); + env->ReleaseDoubleArrayElements(localTransform_, localTransform, JNI_ABORT); + } else { + tm->create(entity, (TransformManager::Instance) parent); + } + return tm->getInstance(entity); + }); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/VertexBuffer.cpp b/android/filament-android/src/main/cpp/VertexBuffer.cpp index d5b3fc326dee..e49193ea5fd3 100644 --- a/android/filament-android/src/main/cpp/VertexBuffer.cpp +++ b/android/filament-android/src/main/cpp/VertexBuffer.cpp @@ -26,6 +26,7 @@ #include "common/CallbackUtils.h" #include "common/NioUtils.h" +#include using namespace filament; using namespace filament::math; @@ -82,11 +83,13 @@ Java_com_google_android_filament_VertexBuffer_nBuilderNormalized(JNIEnv*, jclass } extern "C" JNIEXPORT jlong JNICALL -Java_com_google_android_filament_VertexBuffer_nBuilderBuild(JNIEnv*, jclass, +Java_com_google_android_filament_VertexBuffer_nBuilderBuild(JNIEnv* env, jclass, jlong nativeBuilder, jlong nativeEngine) { VertexBuffer::Builder* builder = (VertexBuffer::Builder *) nativeBuilder; Engine *engine = (Engine *) nativeEngine; - return (jlong) builder->build(*engine); + return filament::android::wrapJni(env, [=]() { + return (jlong) builder->build(*engine); + }); } extern "C" JNIEXPORT jint JNICALL @@ -104,30 +107,34 @@ Java_com_google_android_filament_VertexBuffer_nSetBufferAt(JNIEnv *env, jclass, VertexBuffer *vertexBuffer = (VertexBuffer *) nativeVertexBuffer; Engine *engine = (Engine *) nativeEngine; - AutoBuffer nioBuffer(env, buffer, count); - void* data = nioBuffer.getData(); - size_t sizeInBytes = nioBuffer.getSize(); - if (sizeInBytes > (remaining << nioBuffer.getShift())) { - // BufferOverflowException - return -1; - } + return filament::android::wrapJni(env, [=]() { + AutoBuffer nioBuffer(env, buffer, count); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } - auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); + auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); - BufferDescriptor desc(data, sizeInBytes, - callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); + BufferDescriptor desc(data, sizeInBytes, + callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback); - vertexBuffer->setBufferAt(*engine, (uint8_t) bufferIndex, std::move(desc), - (uint32_t) destOffsetInBytes); + vertexBuffer->setBufferAt(*engine, (uint8_t) bufferIndex, std::move(desc), + (uint32_t) destOffsetInBytes); - return 0; + return 0; + }); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_VertexBuffer_nSetBufferObjectAt(JNIEnv*, jclass, +Java_com_google_android_filament_VertexBuffer_nSetBufferObjectAt(JNIEnv* env, jclass, jlong nativeVertexBuffer, jlong nativeEngine, jint bufferIndex, jlong nativeBufferObject) { VertexBuffer *vertexBuffer = (VertexBuffer *) nativeVertexBuffer; Engine *engine = (Engine *) nativeEngine; BufferObject *bufferObject = (BufferObject *) nativeBufferObject; - vertexBuffer->setBufferObjectAt(*engine, (uint8_t) bufferIndex, bufferObject); + filament::android::wrapJni(env, [=]() { + vertexBuffer->setBufferObjectAt(*engine, (uint8_t) bufferIndex, bufferObject); + }); } diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index 8f1a944d53e6..35e17d1f51ef 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -21,10 +21,12 @@ #include #include "common/CallbackUtils.h" +#include #include "private/backend/VirtualMachineEnv.h" using namespace filament; +using namespace filament::android; extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetName(JNIEnv* env, jclass, jlong nativeView, jstring name_) { @@ -35,10 +37,12 @@ Java_com_google_android_filament_View_nSetName(JNIEnv* env, jclass, jlong native } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetScene(JNIEnv*, jclass, jlong nativeView, jlong nativeScene) { +Java_com_google_android_filament_View_nSetScene(JNIEnv* env, jclass, jlong nativeView, jlong nativeScene) { View* view = (View*) nativeView; Scene* scene = (Scene*) nativeScene; - view->setScene(scene); + wrapJni(env, [=]() { + view->setScene(scene); + }); } extern "C" JNIEXPORT void JNICALL @@ -549,21 +553,27 @@ Java_com_google_android_filament_View_nSetGuardBandOptions(JNIEnv *, jclass, extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetMaterialGlobal(JNIEnv * , jclass, jlong nativeView, +Java_com_google_android_filament_View_nSetMaterialGlobal(JNIEnv *env, jclass, jlong nativeView, jint index, jfloat x, jfloat y, jfloat z, jfloat w) { View *view = (View *) nativeView; - view->setMaterialGlobal((uint32_t)index, { x, y, z, w }); + wrapJni(env, [=]() { + view->setMaterialGlobal((uint32_t)index, { x, y, z, w }); + }); } extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nGetMaterialGlobal(JNIEnv *env, jclass clazz, jlong nativeView, jint index, jfloatArray out_) { - jfloat* out = env->GetFloatArrayElements(out_, nullptr); View *view = (View *) nativeView; - auto result = view->getMaterialGlobal(index); - std::copy_n(result.v, 4, out); - env->ReleaseFloatArrayElements(out_, out, 0); + wrapJni(env, [=]() { + auto result = view->getMaterialGlobal(index); + jfloat* out = env->GetFloatArrayElements(out_, nullptr); + if (out) { + std::copy_n(result.v, 4, out); + env->ReleaseFloatArrayElements(out_, out, 0); + } + }); } extern "C" diff --git a/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java b/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java index d21b1f35f94a..e64698a3987e 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java +++ b/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java @@ -91,6 +91,8 @@ public Builder bindingType(@NonNull BindingType bindingType) { * @return the newly created BufferObject object * * @exception IllegalStateException if the BufferObject could not be created + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. * * @see #setBuffer */ diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index cbee7cc6f88f..2b3568edd61f 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -321,8 +321,7 @@ public Builder feature(@NonNull String name, boolean value) { * be initialized, for instance if it doesn't support the right version of OpenGL or * OpenGL ES. * - * @exception IllegalStateException can be thrown if there isn't enough memory to - * allocate the command buffer. + * @throws Error if there isn't enough memory to allocate the command buffer. */ public Engine build() { long nativeEngine = nBuilderBuild(mNativeBuilder); @@ -729,6 +728,8 @@ public FeatureLevel getSupportedFeatureLevel() { * * @return the active feature level. * + * @throws RuntimeException if the feature level cannot be set. + * * @see Builder#featureLevel * @see #getSupportedFeatureLevel * @see #getActiveFeatureLevel @@ -775,6 +776,15 @@ public boolean isAutomaticInstancingEnabled() { return nIsAutomaticInstancingEnabled(getNativeObject()); } + /** + * Returns whether the engine has encountered an unrecoverable failure. + * + * @return true if an unrecoverable failure has occurred, false otherwise. + */ + public boolean hasUnrecoverableFailure() { + return nHasUnrecoverableFailure(getNativeObject()); + } + /** * Retrieves the configuration settings of this {@link Engine}. * @@ -1261,9 +1271,10 @@ public void destroyIndirectLight(@NonNull IndirectLight ibl) { * Destroys a {@link Material} and frees all its associated resources. *

* All {@link MaterialInstance} of the specified {@link Material} must be destroyed before - * destroying it; if some {@link MaterialInstance} remain, this method fails silently. + * destroying it. * * @param material the {@link Material} to destroy + * @throws RuntimeException if some MaterialInstances remain. */ public void destroyMaterial(@NonNull Material material) { assertDestroy(nDestroyMaterial(getNativeObject(), material.getNativeObject())); @@ -1580,6 +1591,7 @@ private static void assertDestroy(boolean success) { private static native long nGetEntityManager(long nativeEngine); private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable); private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine); + private static native boolean nHasUnrecoverableFailure(long nativeEngine); private static native long nGetMaxStereoscopicEyes(long nativeEngine); private static native int nGetSupportedFeatureLevel(long nativeEngine); private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Fence.java b/android/filament-android/src/main/java/com/google/android/filament/Fence.java index 5d51515a49db..3d0abe7b93af 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Fence.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Fence.java @@ -38,7 +38,17 @@ public enum FenceStatus { } /** + * Client-side wait on the Fence. + * * Blocks the current thread until the Fence signals. + * + * @param mode Whether the command stream is flushed before waiting or not. + * @param timeoutNanoSeconds Wait time out in nanoseconds. Using a timeout of 0 is a way to query the state of the fence. + * A timeout value of WAIT_FOR_EVER is used to disable the timeout. + * @return FenceStatus::CONDITION_SATISFIED on success, + * FenceStatus::TIMEOUT_EXPIRED if the time out expired or + * FenceStatus::ERROR in other cases. + * @throws Error if the backend thread encountered an unrecoverable error. */ public FenceStatus wait(@NonNull Mode mode, long timeoutNanoSeconds) { int nativeResult = nWait(getNativeObject(), mode.ordinal(), timeoutNanoSeconds); @@ -55,6 +65,15 @@ public FenceStatus wait(@NonNull Mode mode, long timeoutNanoSeconds) { } } + /** + * Client-side wait on a Fence and destroy the Fence. + * + * @param fence Fence object to wait on. + * @param mode Whether the command stream is flushed before waiting or not. + * @return FenceStatus::CONDITION_SATISFIED on success, + * FenceStatus::ERROR otherwise. + * @throws Error if the backend thread encountered an unrecoverable error. + */ public static FenceStatus waitAndDestroy(@NonNull Fence fence, @NonNull Mode mode) { int nativeResult = nWaitAndDestroy(fence.getNativeObject(), mode.ordinal()); switch (nativeResult) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/IndexBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/IndexBuffer.java index 78b94577e5d0..e740917ad21a 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/IndexBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/IndexBuffer.java @@ -99,6 +99,7 @@ public Builder bufferType(@NonNull IndexType indexType) { * @return the newly created IndexBuffer object * * @exception IllegalStateException if the IndexBuffer could not be created + * @throws RuntimeException if a runtime error occurred, such as running out of memory or other resources, or if a parameter to a builder function was invalid. * * @see #setBuffer */ diff --git a/android/filament-android/src/main/java/com/google/android/filament/IndirectLight.java b/android/filament-android/src/main/java/com/google/android/filament/IndirectLight.java index 8132bf4d0d73..70a48c6b4d9a 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/IndirectLight.java +++ b/android/filament-android/src/main/java/com/google/android/filament/IndirectLight.java @@ -316,6 +316,8 @@ public Builder rotation(@NonNull @Size(min = 9) float[] rotation) { * @return A newly created IndirectLight * * @exception IllegalStateException if a parameter to a builder function was invalid. + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. */ @NonNull public IndirectLight build(@NonNull Engine engine) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java index bc930d7c6cf0..6480a7e782a6 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java @@ -781,6 +781,7 @@ public Builder sunHaloFalloff(float haloFalloff) { * * @param engine Reference to the {@link Engine} to associate this light with. * @param entity Entity to add the light component to. + * @throws RuntimeException if a runtime error occurred, such as running out of memory or other resources, or if a parameter to a builder function was invalid. */ public void build(@NonNull Engine engine, @Entity int entity) { if (!nBuilderBuild(mNativeBuilder, engine.getNativeObject(), entity)) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/Material.java b/android/filament-android/src/main/java/com/google/android/filament/Material.java index 4e5388644f15..c74e7fda4d07 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Material.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Material.java @@ -476,6 +476,7 @@ public Builder uboBatching(UboBatchingMode mode) { * @return the newly created object * * @exception IllegalStateException if the material could not be created + * @throws RuntimeException if a parameter to a builder function was invalid. */ @NonNull public Material build(@NonNull Engine engine) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java index 72b4a171dd01..cf11ffac9859 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java +++ b/android/filament-android/src/main/java/com/google/android/filament/MaterialInstance.java @@ -156,6 +156,7 @@ public String getName() { * * @param name the name of the material parameter * @param x the value of the material parameter + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, boolean x) { nSetParameterBool(getNativeObject(), name, x); @@ -166,6 +167,7 @@ public void setParameter(@NonNull String name, boolean x) { * * @param name the name of the material parameter * @param x the value of the material parameter + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, float x) { nSetParameterFloat(getNativeObject(), name, x); @@ -176,6 +178,7 @@ public void setParameter(@NonNull String name, float x) { * * @param name the name of the material parameter * @param x the value of the material parameter + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, int x) { nSetParameterInt(getNativeObject(), name, x); @@ -187,6 +190,7 @@ public void setParameter(@NonNull String name, int x) { * @param name the name of the material parameter * @param x the value of the first component * @param y the value of the second component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, boolean x, boolean y) { nSetParameterBool2(getNativeObject(), name, x, y); @@ -198,6 +202,7 @@ public void setParameter(@NonNull String name, boolean x, boolean y) { * @param name the name of the material parameter * @param x the value of the first component * @param y the value of the second component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, float x, float y) { nSetParameterFloat2(getNativeObject(), name, x, y); @@ -209,6 +214,7 @@ public void setParameter(@NonNull String name, float x, float y) { * @param name the name of the material parameter * @param x the value of the first component * @param y the value of the second component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, int x, int y) { nSetParameterInt2(getNativeObject(), name, x, y); @@ -221,6 +227,7 @@ public void setParameter(@NonNull String name, int x, int y) { * @param x the value of the first component * @param y the value of the second component * @param z the value of the third component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, boolean x, boolean y, boolean z) { nSetParameterBool3(getNativeObject(), name, x, y, z); @@ -233,6 +240,7 @@ public void setParameter(@NonNull String name, boolean x, boolean y, boolean z) * @param x the value of the first component * @param y the value of the second component * @param z the value of the third component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, float x, float y, float z) { nSetParameterFloat3(getNativeObject(), name, x, y, z); @@ -245,6 +253,7 @@ public void setParameter(@NonNull String name, float x, float y, float z) { * @param x the value of the first component * @param y the value of the second component * @param z the value of the third component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, int x, int y, int z) { nSetParameterInt3(getNativeObject(), name, x, y, z); @@ -258,6 +267,7 @@ public void setParameter(@NonNull String name, int x, int y, int z) { * @param y the value of the second component * @param z the value of the third component * @param w the value of the fourth component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, boolean x, boolean y, boolean z, boolean w) { nSetParameterBool4(getNativeObject(), name, x, y, z, w); @@ -271,6 +281,7 @@ public void setParameter(@NonNull String name, boolean x, boolean y, boolean z, * @param y the value of the second component * @param z the value of the third component * @param w the value of the fourth component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, float x, float y, float z, float w) { nSetParameterFloat4(getNativeObject(), name, x, y, z, w); @@ -284,6 +295,7 @@ public void setParameter(@NonNull String name, float x, float y, float z, float * @param y the value of the second component * @param z the value of the third component * @param w the value of the fourth component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, int x, int y, int z, int w) { nSetParameterInt4(getNativeObject(), name, x, y, z, w); @@ -299,6 +311,7 @@ public void setParameter(@NonNull String name, int x, int y, int z, int w) { * @param name The name of the material texture parameter * @param texture The texture to set as parameter * @param sampler The sampler to be used with this texture + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull Texture texture, @NonNull TextureSampler sampler) { @@ -320,6 +333,7 @@ public void setParameter(@NonNull String name, * instance.setParameter("param", MaterialInstance.BooleanElement.BOOL4, a, 0, 4); * } *

+ * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull BooleanElement type, @NonNull boolean[] v, @@ -342,6 +356,7 @@ public void setParameter(@NonNull String name, * instance.setParameter("param", MaterialInstance.IntElement.INT4, a, 0, 4); * } *

+ * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull IntElement type, @NonNull int[] v, @@ -364,6 +379,7 @@ public void setParameter(@NonNull String name, * material.setDefaultParameter("param", MaterialInstance.FloatElement.FLOAT4, a, 0, 4); * } *

+ * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull FloatElement type, @NonNull float[] v, @@ -379,6 +395,7 @@ public void setParameter(@NonNull String name, * @param r red component * @param g green component * @param b blue component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull Colors.RgbType type, float r, float g, float b) { @@ -395,6 +412,7 @@ public void setParameter(@NonNull String name, @NonNull Colors.RgbType type, * @param g green component * @param b blue component * @param a alpha component + * @throws RuntimeException if name doesn't exist or no-op if exceptions are disabled. */ public void setParameter(@NonNull String name, @NonNull Colors.RgbaType type, float r, float g, float b, float a) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java index ad0f83f2e50c..a4aebcc8b3de 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/MorphTargetBuffer.java @@ -112,6 +112,8 @@ public Builder enableCustomMorphing(boolean enabled) { * @return the newly created MorphTargetBuffer object * * @exception IllegalStateException if the MorphTargetBuffer could not be created + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. * * @see #setMorphTargetBufferOffsetAt */ diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index 7d9cf1037a34..861593c5f1dd 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -587,6 +587,7 @@ public Builder morphing(@IntRange(from = 0) int level, * * @param engine reference to the Engine to associate this renderable with * @param entity entity to add the renderable component to + * @throws RuntimeException if a runtime error occurred, such as running out of memory or other resources, or if a parameter to a builder function was invalid. */ public void build(@NonNull Engine engine, @Entity int entity) { if (!nBuilderBuild(mNativeBuilder, engine.getNativeObject(), entity)) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java index 2424b7b85235..4b8663ab671b 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java @@ -269,9 +269,8 @@ public Engine getEngine() { /** * Set the time at which the frame must be presented to the display. - *

+ * * This must be called between {@link #beginFrame} and {@link #endFrame}. - *

* * @param monotonicClockNanos The time in nanoseconds corresponding to the system monotonic * up-time clock. The presentation time is typically set in the @@ -354,6 +353,8 @@ public boolean shouldRenderFrame() { * returned and produce a frame anyways, by making calls to {@link #render(View)}, * in which case {@link #endFrame} must be called. * + * @throws Error if the backend thread encountered an unrecoverable error. + * * @see #endFrame * @see #render */ @@ -370,6 +371,8 @@ public boolean beginFrame(@NonNull SwapChain swapChain, long frameTimeNanos) { * *

All calls to render() must happen before endFrame().

* + * @throws Error if the backend thread encountered an unrecoverable error, or if called again after a backend exception was already thrown. + * * @see #beginFrame * @see #render */ @@ -425,6 +428,7 @@ public void endFrame() { * * * @param view the {@link View} to render + * @throws Error if the backend thread encountered an unrecoverable error, or if called again after a backend exception was already thrown. * @see #beginFrame * @see #endFrame * @see View @@ -454,6 +458,7 @@ public void render(@NonNull View view) { *
  • renderStandaloneView() performs potentially heavy computations and cannot be * multi-threaded. However, internally, it is highly multi-threaded to both improve performance * and mitigate the call's latency.
  • + * * * @param view the {@link View} to render. This View must have an associated {@link RenderTarget} * @see View @@ -519,14 +524,14 @@ public void mirrorFrame( *

    readPixels must be called within a frame, meaning after {@link #beginFrame} * and before {@link #endFrame}. Typically, readPixels will be called after * {@link #render}.

    - *
    - *

    After calling this method, the callback associated with buffer - * will be invoked on the main thread, indicating that the read-back has completed. - * Typically, this will happen after multiple calls to {@link #beginFrame}, - * {@link #render}, {@link #endFrame}.

    - *
    - *

    readPixels is intended for debugging and testing. - * It will impact performance significantly.

    + * + *

    After issuing this method, the callback associated with buffer will be invoked on the + * main thread, indicating that the read-back has completed. Typically, this will happen + * after multiple calls to {@link #beginFrame}, {@link #render}, {@link #endFrame}.

    + * + *

    It is also possible to use a {@link Fence} to wait for the read-back.

    + * + *

    readPixels is intended for debugging and testing. It will impact performance significantly.

    * * @param xoffset left offset of the sub-region to read back * @param yoffset bottom offset of the sub-region to read back @@ -604,18 +609,19 @@ public void readPixels( * *

    Typically readPixels will be called after {@link #render} and before * {@link #endFrame}.

    - *
    - *

    After calling this method, the callback associated with buffer - * will be invoked on the main thread, indicating that the read-back has completed. - * Typically, this will happen after multiple calls to {@link #beginFrame}, - * {@link #render}, {@link #endFrame}.

    - *
    + * + *

    After issuing this method, the callback associated with buffer will be invoked on the + * main thread, indicating that the read-back has completed. Typically, this will happen + * after multiple calls to {@link #beginFrame}, {@link #render}, {@link #endFrame}.

    + * + *

    It is also possible to use a {@link Fence} to wait for the read-back.

    + * *

    OpenGL only: if issuing a readPixels on a {@link RenderTarget} backed by a * {@link Texture} that had data uploaded to it via {@link Texture#setImage}, the data returned * from readPixels will be y-flipped with respect to the {@link Texture#setImage} * call.

    - *

    readPixels is intended for debugging and testing. - * It will impact performance significantly.

    + * + *

    readPixels is intended for debugging and testing. It will impact performance significantly.

    * * @param renderTarget {@link RenderTarget} to read back from * @param xoffset left offset of the sub-region to read back diff --git a/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java index c286661df5f9..2b82c16b5569 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/SkinningBuffer.java @@ -78,6 +78,8 @@ public Builder initialize(boolean initialize) { * @return the newly created SkinningBuffer object * * @exception IllegalStateException if the SkinningBuffer could not be created + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. * * @see #setBonesAsMatrices * @see #setBonesAsQuaternions diff --git a/android/filament-android/src/main/java/com/google/android/filament/Texture.java b/android/filament-android/src/main/java/com/google/android/filament/Texture.java index cb26ed694973..565f1c25bdf1 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Texture.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Texture.java @@ -885,6 +885,8 @@ public Builder external() { * @return A newly created Texture * @exception IllegalStateException if a parameter to a builder function was invalid. * A mode detailed message about the error is output in the system log. + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. */ @NonNull public Texture build(@NonNull Engine engine) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java index d3494e81ef57..7d22b2927469 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java @@ -272,6 +272,8 @@ public Builder normalized(@NonNull VertexAttribute attribute, boolean enabled) { * @return the newly created VertexBuffer object * * @exception IllegalStateException if the VertexBuffer could not be created + * @throws RuntimeException if a runtime error occurred, such as running out of + * memory or other resources. */ @NonNull public VertexBuffer build(@NonNull Engine engine) { diff --git a/filament/backend/include/private/backend/CommandBufferQueue.h b/filament/backend/include/private/backend/CommandBufferQueue.h index a33778b93323..e2e446fbd96b 100644 --- a/filament/backend/include/private/backend/CommandBufferQueue.h +++ b/filament/backend/include/private/backend/CommandBufferQueue.h @@ -23,6 +23,8 @@ #include #include +#include +#include #include #include @@ -75,6 +77,25 @@ class CommandBufferQueue { bool isExitRequested() const; +#ifdef __EXCEPTIONS + bool hasUnrecoverableError() const noexcept { + return mHasUnrecoverableError.load(std::memory_order_acquire); + } + void setUnrecoverableException(std::exception_ptr e) noexcept { + mBackendException = e; + mHasUnrecoverableError.store(true, std::memory_order_release); + } + void propagateBackendException() const; + bool hasExceptionBeenRethrown() const noexcept { + return mExceptionRethrown.load(std::memory_order_relaxed); + } +#else + void propagateBackendException() const noexcept {} + constexpr bool hasUnrecoverableError() const noexcept { return false; } + constexpr bool hasExceptionBeenRethrown() const noexcept { return false; } +#endif + + private: const size_t mRequiredSize; @@ -91,6 +112,12 @@ class CommandBufferQueue { bool mPaused = false; static constexpr uint32_t EXIT_REQUESTED = 0x31415926; + +#ifdef __EXCEPTIONS + mutable std::exception_ptr mBackendException; + std::atomic mHasUnrecoverableError{false}; + mutable std::atomic mExceptionRethrown{false}; +#endif }; } // namespace filament::backend diff --git a/filament/backend/include/private/backend/Driver.h b/filament/backend/include/private/backend/Driver.h index ed2ff9b27c97..c3f8271f34ab 100644 --- a/filament/backend/include/private/backend/Driver.h +++ b/filament/backend/include/private/backend/Driver.h @@ -75,6 +75,12 @@ class Driver { // thread via `purge()`. virtual void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback) = 0; + /** + * Flags the driver as having encountered an unrecoverable error. + * This will interrupt all pending fence waits and prevent further waits. + */ + virtual void setUnrecoverableError() noexcept {} + virtual ShaderModel getShaderModel() const noexcept = 0; // The shader languages used for shaders for this driver in order of preference, used to inform diff --git a/filament/backend/src/CommandBufferQueue.cpp b/filament/backend/src/CommandBufferQueue.cpp index 5ea8aacd03a9..68f12e0f2888 100644 --- a/filament/backend/src/CommandBufferQueue.cpp +++ b/filament/backend/src/CommandBufferQueue.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -79,8 +80,28 @@ bool CommandBufferQueue::isExitRequested() const { } +#ifdef __EXCEPTIONS +void CommandBufferQueue::propagateBackendException() const { + if (UTILS_VERY_UNLIKELY(hasUnrecoverableError())) { + if (!mExceptionRethrown.exchange(true, std::memory_order_relaxed)) { + std::rethrow_exception(mBackendException); + } else { + FILAMENT_CHECK_POSTCONDITION(false) + << "Engine is in unrecoverable state due to previous backend exception"; + } + } +} +#endif + void CommandBufferQueue::flush() { FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT); +#ifdef __EXCEPTIONS + if (UTILS_VERY_UNLIKELY(hasUnrecoverableError())) { + // Drop the current buffer to avoid filling up the circular buffer + mCircularBuffer.getBuffer(); + propagateBackendException(); + } +#endif CircularBuffer& circularBuffer = mCircularBuffer; if (circularBuffer.empty()) { diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 6f539493f5fe..68fe83f33735 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -29,6 +29,7 @@ #include "private/backend/Dispatcher.h" #include "private/backend/Driver.h" +#include #include #include #include @@ -214,18 +215,48 @@ class DriverBase : public Driver { void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback) final; + /** + * Waits for a predicate to become true or until a timeout is reached. + * Returns ERROR if the driver encountered an unrecoverable error. + */ template - bool waitForFence(Predicate predicate, std::chrono::steady_clock::time_point until) { + FenceStatus waitForFence(Predicate predicate, std::chrono::steady_clock::time_point until) { std::unique_lock lock(mFenceMutex); - return mFenceCondition.wait_until(lock, until, predicate); + bool errorObserved = false; + bool success = mFenceCondition.wait_until(lock, until, [&]() { + return predicate() || + (errorObserved = UTILS_VERY_UNLIKELY(mHasUnrecoverableError.load(std::memory_order_relaxed))); + }); + + if (UTILS_VERY_UNLIKELY(errorObserved)) { + return FenceStatus::ERROR; + } + + return success ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED; } + /** + * Waits for a predicate to become true indefinitely. + * Returns ERROR if the driver encountered an unrecoverable error. + */ template - void waitForFence(Predicate predicate) { + FenceStatus waitForFence(Predicate predicate) { std::unique_lock lock(mFenceMutex); - mFenceCondition.wait(lock, predicate); + bool errorObserved = false; + mFenceCondition.wait(lock, [&]() { + return predicate() || + (errorObserved = UTILS_VERY_UNLIKELY(mHasUnrecoverableError.load(std::memory_order_relaxed))); + }); + + if (UTILS_VERY_UNLIKELY(errorObserved)) { + return FenceStatus::ERROR; + } + return FenceStatus::CONDITION_SATISFIED; } + /** + * Executes an action that signals a fence and notifies all waiters. + */ template void signalFence(Action action) { std::lock_guard lock(mFenceMutex); @@ -233,6 +264,22 @@ class DriverBase : public Driver { mFenceCondition.notify_all(); } + /** + * Flags the driver as having encountered an unrecoverable error. + */ + void setUnrecoverableError() noexcept override { + std::lock_guard lock(mFenceMutex); + mHasUnrecoverableError.store(true, std::memory_order_relaxed); + mFenceCondition.notify_all(); + } + + /** + * Returns true if the driver has encountered an unrecoverable error. + */ + bool hasUnrecoverableError() const noexcept { + return mHasUnrecoverableError.load(std::memory_order_relaxed); + } + // -------------------------------------------------------------------------------------------- // Privates // -------------------------------------------------------------------------------------------- @@ -275,6 +322,7 @@ class DriverBase : public Driver { std::condition_variable mFenceCondition; std::mutex mFenceMutex; + std::atomic mHasUnrecoverableError{false}; }; diff --git a/filament/backend/src/metal/MetalDriver.h b/filament/backend/src/metal/MetalDriver.h index e9cdbc0b01f6..3363640e9505 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -78,7 +78,7 @@ class MetalDriver final : public DriverBase { ShaderLanguage preferredLanguage) const noexcept final; // Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block. - void execute(std::function const& fn) noexcept final; + void execute(std::function const& fn) final; /* * Tasks run regularly on the driver thread. diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index b9ca89d8f66e..d10ed752eb9d 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -307,7 +307,7 @@ swapChain->setFrameCompletedCallback(handler, std::move(callback)); } -void MetalDriver::execute(std::function const& fn) noexcept { +void MetalDriver::execute(std::function const& fn) { @autoreleasepool { fn(); } diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index f01864281794..911bf5963d0b 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -1428,11 +1428,17 @@ static void func(void* user) { } return false; }; + + FenceStatus status; if (timeoutNs == FENCE_WAIT_FOR_EVER) { - context.driver->waitForFence(predicate); + status = context.driver->waitForFence(predicate); } else { auto const until = std::chrono::steady_clock::now() + ns(timeoutNs); - context.driver->waitForFence(predicate, until); + status = context.driver->waitForFence(predicate, until); + } + + if (status == FenceStatus::ERROR) { + return FenceStatus::ERROR; } return result; } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index bc130cbf43fa..b678ced92794 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -2665,12 +2665,17 @@ FenceStatus OpenGLDriver::fenceWait(FenceHandle fh, uint64_t const timeout) { // so we need to wait for that, before using the real fence. // By construction, "f" can't be destroyed while we wait, because its // construction call is in the queue and a destroy call will have to come later. - waitForFence([f] { + FenceStatus status = waitForFence([f] { return f->fence != nullptr; }, until); + + if (status == FenceStatus::ERROR) { + return FenceStatus::ERROR; + } + if (f->fence == nullptr) { // the only possible choice here is that we timed out - assert_invariant(f->state->status == FenceStatus::TIMEOUT_EXPIRED); + assert_invariant(status == FenceStatus::TIMEOUT_EXPIRED); return FenceStatus::TIMEOUT_EXPIRED; } // here we know that we have the platform fence @@ -2686,13 +2691,17 @@ FenceStatus OpenGLDriver::fenceWait(FenceHandle fh, uint64_t const timeout) { // This is the case where we need to use OpenGL fences #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 FenceStatus result = FenceStatus::TIMEOUT_EXPIRED; - waitForFence([&] { + FenceStatus status = waitForFence([&] { if (f->state->status != FenceStatus::TIMEOUT_EXPIRED) { result = f->state->status; return true; } return false; }, until); + + if (status == FenceStatus::ERROR) { + return FenceStatus::ERROR; + } return result; #else return FenceStatus::ERROR; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 97f345c2b1f9..e49b21cb2aa5 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1492,16 +1492,20 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout std::shared_ptr cmdfence; bool canceled = false; - waitForFence([&] { - auto status = fence->getStatus(); - if (bool(status.first) || status.second) { - cmdfence = status.first; - canceled = status.second; + FenceStatus status = waitForFence([&] { + auto fstatus = fence->getStatus(); + if (bool(fstatus.first) || fstatus.second) { + cmdfence = fstatus.first; + canceled = fstatus.second; return true; } return false; }, until); + if (status == FenceStatus::ERROR) { + return FenceStatus::ERROR; + } + if (!cmdfence || canceled) { return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED; } diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 21717d1a30d7..da73bada45e5 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -796,12 +796,16 @@ FenceStatus WebGPUDriver::fenceWait(FenceHandle fenceHandle, uint64_t const time } std::shared_ptr state; - bool const success = waitForFence([&] { + FenceStatus status = waitForFence([&] { state = fence->getState(); return bool(state); }, until); - if (!success) { + if (status == FenceStatus::ERROR) { + return FenceStatus::ERROR; + } + + if (status == FenceStatus::TIMEOUT_EXPIRED) { return FenceStatus::TIMEOUT_EXPIRED; } diff --git a/filament/backend/test/test_Callbacks.cpp b/filament/backend/test/test_Callbacks.cpp index 826d4a132aae..101881709e2b 100644 --- a/filament/backend/test/test_Callbacks.cpp +++ b/filament/backend/test/test_Callbacks.cpp @@ -18,6 +18,7 @@ #include "Lifetimes.h" #include "Skip.h" +#include "../src/DriverBase.h" using namespace filament; using namespace filament::backend; @@ -140,4 +141,32 @@ TEST_F(BackendTest, FrameCompletedCallback) { EXPECT_EQ(callbackCountB, 1); } +#ifdef __EXCEPTIONS +TEST_F(BackendTest, FenceUnrecoverableErrorInterruption) { + DriverBase* driverBase = static_cast(&getDriver()); + + std::atomic waitStarted = false; + std::atomic waitFinished = false; + FenceStatus waitResult = FenceStatus::TIMEOUT_EXPIRED; + + std::thread waitingThread([&]() { + waitStarted = true; + waitResult = driverBase->waitForFence([]() { return false; }); + waitFinished = true; + }); + + while (!waitStarted) { + std::this_thread::yield(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + driverBase->setUnrecoverableError(); + + waitingThread.join(); + + EXPECT_TRUE(waitFinished); + EXPECT_EQ(waitResult, FenceStatus::ERROR); +} +#endif + } // namespace test diff --git a/filament/backend/test/test_CommandBufferQueue.cpp b/filament/backend/test/test_CommandBufferQueue.cpp index 3f8ef1d4db6c..9484f3aa048d 100644 --- a/filament/backend/test/test_CommandBufferQueue.cpp +++ b/filament/backend/test/test_CommandBufferQueue.cpp @@ -303,4 +303,37 @@ TEST_F(CommandBufferQueueTest, StressTest) { } +#ifdef __EXCEPTIONS +TEST_F(CommandBufferQueueTest, BackendExceptionHandling) { + CommandBufferQueue queue(1024, 4096, false); + + // Simulate an exception in the backend thread + std::thread backend([&]() { + try { + std::this_thread::sleep_for(10ms); + throw std::runtime_error("Artificial Backend Failure"); + } catch (...) { + queue.setUnrecoverableException(std::current_exception()); + } + + }); + + // Wait for backend to fail + std::this_thread::sleep_for(50ms); + + // First call to flush should throw the original exception + EXPECT_THROW({ + queue.flush(); + }, std::runtime_error); + + // Subsequent calls should throw due to postcondition check + EXPECT_THROW({ + queue.flush(); + }, utils::Panic); + + backend.join(); +} +#endif + } // namespace filament::backend + diff --git a/filament/include/filament/Camera.h b/filament/include/filament/Camera.h index 8c960bd7f26a..627539c470f2 100644 --- a/filament/include/filament/Camera.h +++ b/filament/include/filament/Camera.h @@ -273,7 +273,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * @param near distance in world units from the camera to the near plane. * @param far distance in world units from the camera to the far plane. \p far != \p near. */ - void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept; + void setCustomProjection(math::mat4 const& projection, double near, double far); /** Sets the projection matrix. * @@ -286,7 +286,7 @@ class UTILS_PUBLIC Camera : public FilamentAPI { * @param far distance in world units from the camera to the far plane. \p far != \p near. */ void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling, - double near, double far) noexcept; + double near, double far); /** Sets a custom projection matrix for each eye. * diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index bdacc00efa9c..fcf92cdd0677 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -782,6 +782,17 @@ class UTILS_PUBLIC Engine { */ bool isAsynchronousModeEnabled() const noexcept; + /** + * Returns whether the engine has encountered an unrecoverable failure. + * + * If this returns true, the engine is in an unrecoverable state and further calls to + * rendering methods will fail or be ignored. Apps can use this to check for fatal + * errors instead of relying on exceptions. + * + * @return true if an unrecoverable failure has occurred, false otherwise. + */ + bool hasUnrecoverableFailure() const noexcept; + /** * Retrieves the configuration settings of this Engine. * @@ -1099,6 +1110,8 @@ class UTILS_PUBLIC Engine { * in cases where a guarantee about the SwapChain destruction is needed in a * timely fashion, such as when responding to Android's * android.view.SurfaceHolder.Callback.surfaceDestroyed

    + * + * @note If the backend thread has encountered an unrecoverable error, this function becomes a no-op. */ void flushAndWait(); @@ -1118,6 +1131,8 @@ class UTILS_PUBLIC Engine { * @param timeout A timeout in nanoseconds * @return true if successful, false if flushAndWait timed out, in which case it wasn't successful and commands * might still be executing on both the CPU and GPU sides. + * + * @note If the backend thread has encountered an unrecoverable error, this function becomes a no-op and returns false. */ bool flushAndWait(uint64_t timeout); @@ -1127,7 +1142,9 @@ class UTILS_PUBLIC Engine { * *

    This is typically used after creating a lot of objects to start draining the command * queue which has a limited size.

    - */ + * + * @note If the backend thread has encountered an unrecoverable error, this function becomes a no-op. + */ void flush(); /** diff --git a/filament/include/filament/Fence.h b/filament/include/filament/Fence.h index 75213f11efef..7598c0178e7d 100644 --- a/filament/include/filament/Fence.h +++ b/filament/include/filament/Fence.h @@ -62,6 +62,9 @@ class UTILS_PUBLIC Fence : public FilamentAPI { * @return FenceStatus::CONDITION_SATISFIED on success, * FenceStatus::TIMEOUT_EXPIRED if the time out expired or * FenceStatus::ERROR in other cases. + * @throws std::exception (or derived) if the backend thread encountered an unrecoverable error (when exceptions are enabled and mode is Mode::FLUSH). + * @throws utils::Panic if called again after a backend exception was already thrown. + * * @see #Mode */ FenceStatus wait(Mode mode = Mode::FLUSH, uint64_t timeout = FENCE_WAIT_FOR_EVER); diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index 74b765d234af..62ae8411d30f 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -298,9 +298,11 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * This is a convenience method that returns the same value as beginFrame(). * * @return - * *false* the current frame should be skipped, + * *false* the current frame should be skipped, or an unrecoverable backend exception has occurred. * *true* the current frame can be rendered * + * @note This method will return false once a backend exception has been delivered to the main thread. + * * @see * beginFrame() */ @@ -344,6 +346,10 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * It is recommended to use the same swapChain for every call to beginFrame, failing to do * so can result is losing all or part of the FrameInfo history. * + * @throws std::exception (or derived) if the backend thread encountered an unrecoverable error (when exceptions are enabled). + * + * @note This method will return false if called again after a backend exception was already thrown and delivered to the main thread. + * * @see * endFrame() */ @@ -414,6 +420,9 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * @remark * render() is typically called once per frame (but not necessarily). * + * @throws std::exception (or derived) if the backend thread encountered an unrecoverable error (when exceptions are enabled). + * @throws utils::Panic if called again after a backend exception was already thrown. + * * @see * beginFrame(), endFrame(), View * @@ -506,6 +515,9 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * beginFrame() returned true, otherwise, endFrame() must not be called unless the caller * ignored beginFrame()'s return value. * + * @throws std::exception (or derived) if the backend thread encountered an unrecoverable error (when exceptions are enabled). + * @throws utils::Panic if called again after a backend exception was already thrown. + * * @see * beginFrame() */ diff --git a/filament/include/filament/Texture.h b/filament/include/filament/Texture.h index e50010b113d1..a0feb28fe95a 100644 --- a/filament/include/filament/Texture.h +++ b/filament/include/filament/Texture.h @@ -567,7 +567,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @see PlatformCocoaGL::createExternalImage * @see PlatformCocoaTouchGL::createExternalImage */ - void setExternalImage(Engine& engine, ExternalImageHandleRef image) noexcept; + void setExternalImage(Engine& engine, ExternalImageHandleRef image); /** * Specify the external image to associate with this Texture. Typically, the external @@ -594,7 +594,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @deprecated Instead, use setExternalImage(Engine& engine, ExternalImageHandleRef image) */ UTILS_DEPRECATED - void setExternalImage(Engine& engine, void* UTILS_NONNULL image) noexcept; + void setExternalImage(Engine& engine, void* UTILS_NONNULL image); /** * Specify the external image and plane to associate with this Texture. Typically, the external @@ -625,7 +625,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * kCVPixelFormatType_420YpCbCr8BiPlanarFullRange images. On platforms * other than iOS, this method is a no-op. */ - void setExternalImage(Engine& engine, void* UTILS_NONNULL image, size_t plane) noexcept; + void setExternalImage(Engine& engine, void* UTILS_NONNULL image, size_t plane); /** * Specify the external stream to associate with this Texture. Typically, the external @@ -644,7 +644,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @see Builder::sampler(), Stream * */ - void setExternalStream(Engine& engine, Stream* UTILS_NULLABLE stream) noexcept; + void setExternalStream(Engine& engine, Stream* UTILS_NULLABLE stream); /** * Generates all the mipmap levels automatically. This requires the texture to have a @@ -656,7 +656,7 @@ class UTILS_PUBLIC Texture : public FilamentAPI { * @attention \p engine must be the instance passed to Builder::build() * @attention This Texture instance must NOT use SamplerType::SAMPLER_3D or it has no effect */ - void generateMipmaps(Engine& engine) const noexcept; + void generateMipmaps(Engine& engine) const; /** * This non-blocking method checks if the resource has finished creation. If the resource diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 651b51e5d94b..254e33e87983 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -464,7 +464,7 @@ class UTILS_PUBLIC View : public FilamentAPI { /** * Enables or disables bloom in the post-processing stage. Disabled by default. * - * @param options options + * @param options options. Values may be silently clamped to valid ranges. */ void setBloomOptions(BloomOptions options) noexcept; diff --git a/filament/src/Camera.cpp b/filament/src/Camera.cpp index 863bd03af4f2..a9d950ab8809 100644 --- a/filament/src/Camera.cpp +++ b/filament/src/Camera.cpp @@ -81,12 +81,12 @@ void Camera::setProjection(Projection const projection, double const left, doubl downcast(this)->setProjection(projection, left, right, bottom, top, near, far); } -void Camera::setCustomProjection(mat4 const& projection, double const near, double const far) noexcept { +void Camera::setCustomProjection(mat4 const& projection, double const near, double const far) { downcast(this)->setCustomProjection(projection, near, far); } void Camera::setCustomProjection(mat4 const& projection, mat4 const& projectionForCulling, - double const near, double const far) noexcept { + double const near, double const far) { downcast(this)->setCustomProjection(projection, projectionForCulling, near, far); } diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 940bb24f3758..c526fb383aba 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -474,6 +474,10 @@ bool Engine::isAsynchronousModeEnabled() const noexcept { return downcast(this)->isAsynchronousModeEnabled(); } +bool Engine::hasUnrecoverableFailure() const noexcept { + return downcast(this)->hasUnrecoverableFailure(); +} + size_t Engine::getMaxStereoscopicEyes() noexcept { return FEngine::getMaxStereoscopicEyes(); } diff --git a/filament/src/Texture.cpp b/filament/src/Texture.cpp index ec0a166f855a..d7aca16178a6 100644 --- a/filament/src/Texture.cpp +++ b/filament/src/Texture.cpp @@ -68,23 +68,23 @@ backend::AsyncCallId Texture::setImageAsync(Engine& engine, size_t const level, handler, std::move(callback), user); } -void Texture::setExternalImage(Engine& engine, ExternalImageHandleRef image) noexcept { +void Texture::setExternalImage(Engine& engine, ExternalImageHandleRef image) { downcast(this)->setExternalImage(downcast(engine), image); } -void Texture::setExternalImage(Engine& engine, void* image) noexcept { +void Texture::setExternalImage(Engine& engine, void* image) { downcast(this)->setExternalImage(downcast(engine), image); } -void Texture::setExternalImage(Engine& engine, void* image, size_t const plane) noexcept { +void Texture::setExternalImage(Engine& engine, void* image, size_t const plane) { downcast(this)->setExternalImage(downcast(engine), image, plane); } -void Texture::setExternalStream(Engine& engine, Stream* stream) noexcept { +void Texture::setExternalStream(Engine& engine, Stream* stream) { downcast(this)->setExternalStream(downcast(engine), downcast(stream)); } -void Texture::generateMipmaps(Engine& engine) const noexcept { +void Texture::generateMipmaps(Engine& engine) const { downcast(this)->generateMipmaps(downcast(engine)); } diff --git a/filament/src/details/Camera.cpp b/filament/src/details/Camera.cpp index d006e22d4c49..046f260ec89a 100644 --- a/filament/src/details/Camera.cpp +++ b/filament/src/details/Camera.cpp @@ -93,7 +93,7 @@ mat4 FCamera::projection(double const focalLengthInMillimeters, */ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection, - mat4 const& projectionForCulling, double const near, double const far) noexcept { + mat4 const& projectionForCulling, double const near, double const far) { FILAMENT_CHECK_PRECONDITION(near != far) << "Camera preconditions not met in setCustomProjection(): near = far = " << near; diff --git a/filament/src/details/Camera.h b/filament/src/details/Camera.h index 9c8b5e0e8555..8cdc2650b641 100644 --- a/filament/src/details/Camera.h +++ b/filament/src/details/Camera.h @@ -53,10 +53,10 @@ class FCamera : public Camera { // Sets custom projection matrices (sets both the viewing and culling projections). void setCustomProjection(math::mat4 const& projection, - math::mat4 const& projectionForCulling, double near, double far) noexcept; + math::mat4 const& projectionForCulling, double near, double far); inline void setCustomProjection(math::mat4 const& projection, - double const near, double const far) noexcept { + double const near, double const far) { setCustomProjection(projection, projection, near, far); } diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 77aa59ac0136..48aeed8131c9 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -91,6 +91,10 @@ #include #include +#ifdef __EXCEPTIONS +#include +#endif + #include #include #include @@ -806,6 +810,11 @@ void FEngine::submitFrame() { } void FEngine::flush() { + if (UTILS_VERY_UNLIKELY(mCommandBufferQueue.hasUnrecoverableError())) { + return; + } + propagateBackendException(); + // flush the command buffer flushCommandBuffer(mCommandBufferQueue); @@ -821,6 +830,11 @@ void FEngine::flushAndWait() { } bool FEngine::flushAndWait(uint64_t const timeout) { + if (UTILS_VERY_UNLIKELY(mCommandBufferQueue.hasUnrecoverableError())) { + return false; + } + propagateBackendException(); + FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused()) << "Cannot call Engine::flushAndWait() when rendering thread is paused!"; @@ -941,8 +955,10 @@ int FEngine::loop() { } void FEngine::flushCommandBuffer(CommandBufferQueue& commandBufferQueue) const { - getDriver().purge(); - commandBufferQueue.flush(); + if (!commandBufferQueue.hasUnrecoverableError()) { + getDriver().purge(); + commandBufferQueue.flush(); + } } const FMaterial* FEngine::getSkyboxMaterial() const noexcept { @@ -1581,14 +1597,38 @@ bool FEngine::execute() { return false; } + if (UTILS_VERY_UNLIKELY(mCommandBufferQueue.hasUnrecoverableError())) { + for (auto& item : buffers) { + mCommandBufferQueue.releaseBuffer(item); + } + return true; + } + + // execute all command buffers auto& driver = getDriverApi(); - for (auto& item : buffers) { - if (UTILS_LIKELY(item.begin)) { - driver.execute(item.begin); - mCommandBufferQueue.releaseBuffer(item); + size_t i = 0; +#ifdef __EXCEPTIONS + try { +#endif + for (; i < buffers.size(); ++i) { + auto& item = buffers[i]; + if (UTILS_LIKELY(item.begin)) { + driver.execute(item.begin); + mCommandBufferQueue.releaseBuffer(item); + } + } +#ifdef __EXCEPTIONS + } catch (...) { + mCommandBufferQueue.setUnrecoverableException(std::current_exception()); + mDriver->setUnrecoverableError(); + setFenceUnrecoverableError(); + // Release remaining buffers (including the one that failed) + for (; i < buffers.size(); ++i) { + mCommandBufferQueue.releaseBuffer(buffers[i]); } } +#endif return true; } @@ -1608,6 +1648,40 @@ void FEngine::setPaused(bool const paused) { mCommandBufferQueue.setPaused(paused); } +void FEngine::setFenceUnrecoverableError() noexcept { + std::lock_guard const lock(mFenceLock); + mFenceHasUnrecoverableError = true; + mFenceCondition.notify_all(); +} + +void FEngine::signalFence(FenceSignal& signal, FenceSignal::State s) noexcept { + std::lock_guard const lock(mFenceLock); + signal.mState = s; + mFenceCondition.notify_all(); +} + +Fence::FenceStatus FEngine::waitFence(FenceSignal& signal, uint64_t const timeout) noexcept { + std::unique_lock lock(mFenceLock); + while (signal.mState == FenceSignal::UNSIGNALED && !mFenceHasUnrecoverableError) { + if (timeout == FENCE_WAIT_FOR_EVER) { + mFenceCondition.wait(lock); + } else { + if (timeout == 0 || + mFenceCondition.wait_for(lock, std::chrono::nanoseconds(timeout)) == std::cv_status::timeout) { + if (mFenceHasUnrecoverableError) break; + return Fence::FenceStatus::TIMEOUT_EXPIRED; + } + } + } + if (UTILS_VERY_UNLIKELY(mFenceHasUnrecoverableError)) { + return Fence::FenceStatus::ERROR; + } + if (signal.mState == FenceSignal::DESTROYED) { + return Fence::FenceStatus::ERROR; + } + return Fence::FenceStatus::CONDITION_SATISFIED; +} + Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept { DriverApi& driver = const_cast(this)->getDriverApi(); return driver.getFeatureLevel(); diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 592aba64a7b1..6144051d05a0 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -84,6 +84,7 @@ #include #include #include +#include #include #include @@ -147,6 +148,22 @@ class FEngine : public Engine, public utils::FeatureFlagManager { using Epoch = clock::time_point; using duration = clock::duration; +#ifdef __EXCEPTIONS + void propagateBackendException() const { + mCommandBufferQueue.propagateBackendException(); + } +#else + void propagateBackendException() const noexcept {} +#endif + + bool hasExceptionBeenRethrown() const noexcept { + return mCommandBufferQueue.hasExceptionBeenRethrown(); + } + + bool hasUnrecoverableFailure() const noexcept { + return mCommandBufferQueue.hasUnrecoverableError(); + } + public: static Engine* create(Builder const& builder); @@ -206,6 +223,15 @@ class FEngine : public Engine, public utils::FeatureFlagManager { return mActiveFeatureLevel; } + // Sets the fence unrecoverable error state and notifies all waiters. + void setFenceUnrecoverableError() noexcept; + + // Signals a fence and notifies all waiters. + void signalFence(FenceSignal& signal, FenceSignal::State s) noexcept; + + // Waits for a fence to be signaled or for a timeout. + Fence::FenceStatus waitFence(FenceSignal& signal, uint64_t timeout) noexcept; + size_t getMaxAutomaticInstances() const noexcept { return CONFIG_MAX_INSTANCES; } @@ -665,6 +691,10 @@ class FEngine : public Engine, public utils::FeatureFlagManager { utils::Mutex mFenceListLock; ResourceList mFences{"Fence"}; + mutable utils::Mutex mFenceLock; + mutable utils::Condition mFenceCondition; + bool mFenceHasUnrecoverableError = false; + // the sync list is accessed from multiple threads, because they are // synchronization objects. utils::Mutex mSyncListLock; diff --git a/filament/src/details/Fence.cpp b/filament/src/details/Fence.cpp index 86536020aa8a..9f9103201346 100644 --- a/filament/src/details/Fence.cpp +++ b/filament/src/details/Fence.cpp @@ -37,8 +37,6 @@ namespace filament { using namespace backend; -utils::Mutex FFence::sLock; -utils::Condition FFence::sCondition; static constexpr uint64_t PUMP_INTERVAL_MILLISECONDS = 1; @@ -51,14 +49,15 @@ FFence::FFence(FEngine& engine) // we have to first wait for the fence to be signaled by the command stream auto& fs = mFenceSignal; - driverApi.queueCommand([fs]() { - fs->signal(); + FEngine* const pEngine = &engine; + driverApi.queueCommand([fs, pEngine]() { + pEngine->signalFence(*fs, FenceSignal::SIGNALED); }); } -void FFence::terminate(FEngine&) noexcept { +void FFence::terminate(FEngine& engine) noexcept { FenceSignal * const fs = mFenceSignal.get(); - fs->signal(FenceSignal::DESTROYED); + engine.signalFence(*fs, FenceSignal::DESTROYED); } UTILS_NOINLINE @@ -85,14 +84,14 @@ FenceStatus FFence::wait(Mode const mode, uint64_t const timeout) { FenceStatus status; if (UTILS_LIKELY(!engine.pumpPlatformEvents())) { - status = fs->wait(timeout); + status = engine.waitFence(*fs, timeout); } else { // Unfortunately, some platforms might force us to have sync points between the GL thread // and user thread. To prevent deadlock on these platforms, we chop up the waiting time into // polling and pumping the platform's event queue. const auto startTime = std::chrono::system_clock::now(); while (true) { - status = fs->wait(ns(ms(PUMP_INTERVAL_MILLISECONDS)).count()); + status = engine.waitFence(*fs, ns(ms(PUMP_INTERVAL_MILLISECONDS)).count()); if (status != FenceStatus::TIMEOUT_EXPIRED) { break; } @@ -111,30 +110,6 @@ FenceStatus FFence::wait(Mode const mode, uint64_t const timeout) { return status; } -UTILS_NOINLINE -void FFence::FenceSignal::signal(State const s) noexcept { - std::lock_guard const lock(sLock); - mState = s; - sCondition.notify_all(); -} -UTILS_NOINLINE -Fence::FenceStatus FFence::FenceSignal::wait(uint64_t const timeout) noexcept { - std::unique_lock lock(sLock); - while (mState == UNSIGNALED) { - if (timeout == FENCE_WAIT_FOR_EVER) { - sCondition.wait(lock); - } else { - if (timeout == 0 || - sCondition.wait_for(lock, ns(timeout)) == std::cv_status::timeout) { - return FenceStatus::TIMEOUT_EXPIRED; - } - } - } - if (mState == DESTROYED) { - return FenceStatus::ERROR; - } - return FenceStatus::CONDITION_SATISFIED; -} } // namespace filament diff --git a/filament/src/details/Fence.h b/filament/src/details/Fence.h index 092798f0b04b..a4fa52b23846 100644 --- a/filament/src/details/Fence.h +++ b/filament/src/details/Fence.h @@ -31,6 +31,11 @@ namespace filament { class FEngine; +struct FenceSignal { + enum State : uint8_t { UNSIGNALED, SIGNALED, DESTROYED }; + State mState = UNSIGNALED; +}; + class FFence : public Fence { public: FFence(FEngine& engine); @@ -42,19 +47,6 @@ class FFence : public Fence { static FenceStatus waitAndDestroy(FFence* fence, Mode mode) noexcept; private: - // We assume we don't have a lot of contention of fence and have all of them - // share a single lock/condition - static utils::Mutex sLock; - static utils::Condition sCondition; - - struct FenceSignal { - explicit FenceSignal() noexcept = default; - enum State : uint8_t { UNSIGNALED, SIGNALED, DESTROYED }; - State mState = UNSIGNALED; - void signal(State s = SIGNALED) noexcept; - FenceStatus wait(uint64_t timeout) noexcept; - }; - FEngine& mEngine; // TODO: use custom allocator for these small objects std::shared_ptr mFenceSignal; diff --git a/filament/src/details/InstanceBuffer.cpp b/filament/src/details/InstanceBuffer.cpp index cb623691f261..e1c4bb38924c 100644 --- a/filament/src/details/InstanceBuffer.cpp +++ b/filament/src/details/InstanceBuffer.cpp @@ -117,7 +117,7 @@ void FInstanceBuffer::setLocalTransforms( memcpy(mLocalTransforms.data() + offset, localTransforms, sizeof(math::mat4f) * count); } -math::mat4f const& FInstanceBuffer::getLocalTransform(size_t index) const noexcept { +math::mat4f const& FInstanceBuffer::getLocalTransform(size_t index) const { FILAMENT_CHECK_PRECONDITION(index < mInstanceCount) << "getLocalTransform overflow: 'index (" << index << ") must be < getInstanceCount() ("<< mInstanceCount << ")."; diff --git a/filament/src/details/InstanceBuffer.h b/filament/src/details/InstanceBuffer.h index b1a61358da2b..e3ded8134105 100644 --- a/filament/src/details/InstanceBuffer.h +++ b/filament/src/details/InstanceBuffer.h @@ -49,7 +49,7 @@ class FInstanceBuffer : public InstanceBuffer { void setLocalTransforms(math::mat4f const* localTransforms, size_t count, size_t offset); - math::mat4f const& getLocalTransform(size_t index) const noexcept; + math::mat4f const& getLocalTransform(size_t index) const; void prepare( PerRenderableData* buffer, uint32_t index, uint32_t count, diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index b93ea25cc102..4c7258da08fe 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -216,6 +216,24 @@ void FMaterialInstance::commit(FEngine& engine) const { } void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const { + if (!mTextureParameters.empty()) { + FEngine const& engine = mMaterial->getEngine(); + // First pass: check preconditions + for (auto const& [binding, p]: mTextureParameters) { + assert_invariant(p.texture); + FILAMENT_CHECK_PRECONDITION(engine.isValid(p.texture)) + << "Invalid texture still bound to MaterialInstance: '" << getName() << "'\n"; + } + // Second pass: update state + for (auto const& [binding, p]: mTextureParameters) { + Handle const handle = p.texture->getHwHandleForSampling(); + assert_invariant(handle); + mDescriptorSet.setSampler(mMaterial->getDescriptorSetLayout(), + binding, handle, p.params); + } + } + + if (mUniforms.isDirty()) { mUniforms.clean(); if (isUsingUboBatching()) { @@ -232,19 +250,6 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage driver.updateBufferObject(*ubHandle, mUniforms.toBufferDescriptor(driver), 0); } } - if (!mTextureParameters.empty()) { - for (auto const& [binding, p]: mTextureParameters) { - assert_invariant(p.texture); - // TODO: figure out a way to do this more efficiently (isValid() is a hashmap lookup) - FEngine const& engine = mMaterial->getEngine(); - FILAMENT_CHECK_PRECONDITION(engine.isValid(p.texture)) - << "Invalid texture still bound to MaterialInstance: '" << getName() << "'\n"; - Handle const handle = p.texture->getHwHandleForSampling(); - assert_invariant(handle); - mDescriptorSet.setSampler(mMaterial->getDescriptorSetLayout(), - binding, handle, p.params); - } - } // TODO: eventually we should remove this in RELEASE builds fixMissingSamplers(); diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 1181ddee2f40..c570dfa1b73c 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -316,6 +316,9 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) { } bool FRenderer::shouldRenderFrame() const noexcept { + if (UTILS_VERY_UNLIKELY(mEngine.hasExceptionBeenRethrown())) { + return false; + } FEngine& engine = mEngine; FEngine::DriverApi& driver = engine.getDriverApi(); bool const renderFrame = mFrameSkipper.shouldRenderFrame(driver); @@ -365,10 +368,16 @@ bool FRenderer::shouldRenderFrame() const noexcept { } bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) { - assert_invariant(swapChain); + FILAMENT_CHECK_PRECONDITION(swapChain) << "swapChain cannot be null."; FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT, "frameId", (mFrameId + 1)); + if (UTILS_VERY_UNLIKELY(mEngine.hasExceptionBeenRethrown())) { + return false; + } + + mEngine.propagateBackendException(); + if (!vsyncSteadyClockTimeNano) { vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano; mVsyncSteadyClockTimeNano = 0; @@ -469,6 +478,8 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN void FRenderer::endFrame() { FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT); + mEngine.propagateBackendException(); + if (UTILS_UNLIKELY(mBeginFrameInternal)) { mBeginFrameInternal(); mBeginFrameInternal = {}; @@ -637,6 +648,8 @@ void FRenderer::renderStandaloneView(FView const* view) { void FRenderer::render(FView const* view) { FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT); + mEngine.propagateBackendException(); + if (UTILS_UNLIKELY(mBeginFrameInternal)) { // This is unlikely to happen, the user should not call render() if we returned false from // beginFrame(). But because this is allowed, we handle it gracefully. diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index e6763004a574..d4c398edd31e 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -656,7 +656,7 @@ AsyncCallId FTexture::setImageAsync(FEngine& engine, size_t const level, return id; } -void FTexture::setExternalImage(FEngine& engine, ExternalImageHandleRef image) noexcept { +void FTexture::setExternalImage(FEngine& engine, ExternalImageHandleRef image) { FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external."; FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; @@ -678,7 +678,7 @@ void FTexture::setExternalImage(FEngine& engine, ExternalImageHandleRef image) n setHandles(texture); } -void FTexture::setExternalImage(FEngine& engine, void* image) noexcept { +void FTexture::setExternalImage(FEngine& engine, void* image) { FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external."; FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; @@ -700,7 +700,7 @@ void FTexture::setExternalImage(FEngine& engine, void* image) noexcept { setHandles(texture); } -void FTexture::setExternalImage(FEngine& engine, void* image, size_t const plane) noexcept { +void FTexture::setExternalImage(FEngine& engine, void* image, size_t const plane) { FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external."; FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; @@ -723,7 +723,7 @@ void FTexture::setExternalImage(FEngine& engine, void* image, size_t const plane setHandles(texture); } -void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept { +void FTexture::setExternalStream(FEngine& engine, FStream* stream) { FILAMENT_CHECK_PRECONDITION(mExternal) << "The texture must be external."; FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; @@ -750,7 +750,7 @@ void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept { } } -void FTexture::generateMipmaps(FEngine& engine) const noexcept { +void FTexture::generateMipmaps(FEngine& engine) const { FILAMENT_CHECK_PRECONDITION(!mExternal) << "External Textures are not mipmappable."; FILAMENT_CHECK_PRECONDITION(isCreationComplete()) @@ -831,14 +831,14 @@ Handle FTexture::createPlaceholderTexture( return handle; } -backend::Handle FTexture::getHwHandle() const noexcept { +backend::Handle FTexture::getHwHandle() const { FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; return mHandle; } -Handle FTexture::getHwHandleForSampling() const noexcept { +Handle FTexture::getHwHandleForSampling() const { FILAMENT_CHECK_PRECONDITION(isCreationComplete()) << "Texture is not created yet. It may be in the process of asynchronous loading"; diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index e3d49cde99ed..2b4b4d931f13 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -47,8 +47,8 @@ class FTexture : public Texture { // frees driver resources, object becomes invalid void terminate(FEngine& engine); - backend::Handle getHwHandle() const noexcept; - backend::Handle getHwHandleForSampling() const noexcept; + backend::Handle getHwHandle() const; + backend::Handle getHwHandleForSampling() const; size_t getWidth(size_t level = 0) const noexcept; size_t getHeight(size_t level = 0) const noexcept; @@ -73,12 +73,12 @@ class FTexture : public Texture { PixelBufferDescriptor&& buffer, backend::CallbackHandler* handler, AsyncCompletionCallback callback, void* user) const; - void setExternalImage(FEngine& engine, ExternalImageHandleRef image) noexcept; - void setExternalImage(FEngine& engine, void* image) noexcept; - void setExternalImage(FEngine& engine, void* image, size_t plane) noexcept; - void setExternalStream(FEngine& engine, FStream* stream) noexcept; + void setExternalImage(FEngine& engine, ExternalImageHandleRef image); + void setExternalImage(FEngine& engine, void* image); + void setExternalImage(FEngine& engine, void* image, size_t plane); + void setExternalStream(FEngine& engine, FStream* stream); - void generateMipmaps(FEngine& engine) const noexcept; + void generateMipmaps(FEngine& engine) const; bool isCompressed() const noexcept { return isCompressedFormat(mFormat); } diff --git a/filament/src/fg/FrameGraphResources.cpp b/filament/src/fg/FrameGraphResources.cpp index 00e31922b2fc..efcb5cb6e956 100644 --- a/filament/src/fg/FrameGraphResources.cpp +++ b/filament/src/fg/FrameGraphResources.cpp @@ -38,7 +38,7 @@ const char* FrameGraphResources::getPassName() const noexcept { // this perhaps weirdly returns a reference, this is to express the fact that if this method // fails, it has to assert (or throw), it can't return for e.g. a nullptr, because the public // API doesn't return pointers. -// We still use FILAMENT_CHECK_PRECONDITION() because these failures are due to post conditions not met. +// We use FILAMENT_CHECK_PRECONDITION() because these failures are due to caller contract violations (preconditions). VirtualResource& FrameGraphResources::getResource(FrameGraphHandle const handle) const { FILAMENT_CHECK_PRECONDITION(handle) << "Uninitialized handle when using FrameGraphResources."; diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 269952cb5d7c..49d8349c88fc 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -78,6 +78,10 @@ #include #include +#ifdef __EXCEPTIONS +#include +#endif + #include #include "generated/resources/filamentapp.h" @@ -248,6 +252,9 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, SDL_EventState(SDL_DROPFILE, SDL_ENABLE); SDL_Window* sdlWindow = window->getSDLWindow(); +#ifdef __EXCEPTIONS +try { +#endif while (!mClosed) { if (mWindowTitle != SDL_GetWindowTitle(sdlWindow)) { SDL_SetWindowTitle(sdlWindow, mWindowTitle.c_str()); @@ -262,7 +269,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, cameraFocalLength = mCameraFocalLength; cameraNear = mCameraNear; cameraFar = mCameraFar; - } + } if (!UTILS_HAS_THREADING) { mEngine->execute(); @@ -371,10 +378,10 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, break; case SDL_WINDOWEVENT: switch (event.window.event) { - case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_RESIZED: window->resize(); break; - default: + default: break; } break; @@ -562,6 +569,16 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback, ++mSkippedFrames; } } +#ifdef __EXCEPTIONS +} catch (Panic const& e) { + LOG(ERROR) << "Filament exception (terminate cleanly): " << e.what(); + LOG(ERROR) << e.getCallStack(); +} catch (std::exception const& e) { + LOG(ERROR) << "System exception (terminate cleanly): " << e.what(); +} catch (...) { + LOG(ERROR) << "Unknown exception! (terminate cleanly)"; +} +#endif if (mImGuiHelper) { mImGuiHelper.reset(); diff --git a/libs/utils/include/utils/Panic.h b/libs/utils/include/utils/Panic.h index 38736cb0b1f2..8c393bd66c09 100644 --- a/libs/utils/include/utils/Panic.h +++ b/libs/utils/include/utils/Panic.h @@ -35,6 +35,7 @@ #ifdef __EXCEPTIONS # define UTILS_EXCEPTIONS 1 +# include #else # ifdef UTILS_EXCEPTIONS # error UTILS_EXCEPTIONS is already defined! @@ -262,7 +263,11 @@ namespace utils { * The Panic class provides the std::exception protocol, it is the base exception object * used for all thrown exceptions. */ -class UTILS_PUBLIC Panic { +class UTILS_PUBLIC Panic +#ifdef __EXCEPTIONS + : public std::exception +#endif +{ public: using PanicHandlerCallback = void(*)(void* user, Panic const& panic); diff --git a/libs/utils/src/Panic.cpp b/libs/utils/src/Panic.cpp index 011070e6ba1a..5b8aec6f82db 100644 --- a/libs/utils/src/Panic.cpp +++ b/libs/utils/src/Panic.cpp @@ -207,8 +207,7 @@ void TPanic::panic(char const* function, char const* file, int const line, ch } template -void TPanic::panic(char const* function, char const* file, int line, char const* literal, - CString reason) { +void TPanic::panic(char const* function, char const* file, int line, char const* literal, CString reason) { if (reason.empty()) { reason = CString(literal); @@ -216,15 +215,18 @@ void TPanic::panic(char const* function, char const* file, int line, char con T e(function, formatFile(file), line, literal, std::move(reason)); - // always log the Panic at the point it is detected +#ifndef __EXCEPTIONS + // log the Panic at the point it is detected unless we have exceptions, the exception handler will be + // responsible for that. e.log(); +#endif // Call the user provided handler UserPanicHandler::get().call(e); // if exceptions are enabled, throw now. #ifdef __EXCEPTIONS - throw e; + throw std::move(e); #endif // and finally abort if we somehow get here diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 0048576aff56..2fb541f41e0c 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -450,6 +450,8 @@ class_("Engine") .function("isAutomaticInstancingEnabled", &Engine::isAutomaticInstancingEnabled) + .function("hasUnrecoverableFailure", &Engine::hasUnrecoverableFailure) + .function("getSupportedFeatureLevel", &Engine::getSupportedFeatureLevel) .function("setActiveFeatureLevel", &Engine::setActiveFeatureLevel)