backend: Propagate backend thread exceptions to the main thread. (#9903)

* Filament Error Handling Remediation and noexcept Cleanup

- Replaced assert_invariant with FILAMENT_CHECK_PRECONDITION in
  Renderer::beginFrame for caller bugs.
- Removed noexcept from Camera::setCustomProjection to allow precondition
  checks to throw.
- Removed noexcept from Texture and InstanceBuffer methods using precondition
  checks.
- Documented silent clamping in View::setBloomOptions.
- Clarified comment in FrameGraphResources regarding preconditions.
- Refined MaterialInstance::commit to split texture loop into check and update
  passes, preventing partial state changes on precondition failure.

* backend: Propagate backend thread exceptions to the main thread.

Introduce a mechanism to catch exceptions thrown on the backend thread and
rethrow them on the main thread. This prevents deadlocks and allows the
application to handle fatal backend failures gracefully.

- Consolidate synchronization primitives in DriverBase.
- Add mHasUnrecoverableError flag to interrupt blocked fence waits.
- Optimize waitForFence to avoid extra atomic reads in common paths.
- Propagate FenceStatus::ERROR across all backends.
- CommandBufferQueue now stores a std::exception_ptr when the backend fails.
- The backend enters a "zombie" state on failure, skipping further command
  execution but allowing clean shutdown.
- Public APIs in Renderer and Engine now check for stored exceptions and
  rethrow them, documented with @throws.
- Guarded by __EXCEPTIONS to ensure no overhead when exceptions are disabled.
- Add unit test for fence interruption and document new APIs.

BUGS=[407545700]

* feat: harden backend exceptions and add hasUnrecoverableFailure API

- Update Renderer::beginFrame() and Renderer::shouldRenderFrame() to
  return false early if an unrecoverable backend exception has been
  delivered to the main thread.
- Document the new return behavior for beginFrame() and
  shouldRenderFrame() in Renderer.h.
- Add Engine::hasUnrecoverableFailure() to the public API to allow
  apps to check for fatal errors without relying on exceptions.
- Implement hasUnrecoverableFailure() in FEngine by delegating to
  CommandBufferQueue.
- Expose Engine::hasUnrecoverableFailure() to Java bindings
  (Engine.java and JNI).
- Expose Engine::hasUnrecoverableFailure() to JavaScript bindings
  (jsbindings.cpp).

* java: propagate C++ Panics & exceptions to JAVA
This commit is contained in:
Mathias Agopian
2026-04-22 10:56:09 -07:00
committed by GitHub
parent fa987f225d
commit f4a079f663
81 changed files with 1381 additions and 525 deletions
@@ -18,6 +18,7 @@
#include <filament/RenderableManager.h>
#include <filament/MaterialInstance.h>
#include <common/JniUtils.h>
#include <utils/Entity.h>
@@ -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<jboolean>(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<filament::math::mat4f const *>(data), (size_t)boneCount, (size_t)offset);
return 0;
jint result = 0;
wrapJni(env, [=]() {
rm->setBones((RenderableManager::Instance)i,
static_cast<filament::math::mat4f const *>(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<RenderableManager::Bone const *>(data), (size_t)boneCount, (size_t)offset);
return 0;
jint result = 0;
wrapJni(env, [=]() {
rm->setBones((RenderableManager::Instance)i,
static_cast<RenderableManager::Bone const *>(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