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
+ * @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)