Skip to content

Commit

Permalink
add a version of Engine::flushAndWait that takes a timeout
Browse files Browse the repository at this point in the history
The old version that doesn't take a timeout now always waits forever;
before it waited for a few seconds on Android debug builds.

The new version has an explicit timeout that works on all platforms and
returns success status.

FIXES=[384043020]
  • Loading branch information
pixelflinger committed Jan 29, 2025
1 parent 4465e6c commit 41f7dc3
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 37 deletions.
6 changes: 3 additions & 3 deletions android/filament-android/src/main/cpp/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,11 @@ Java_com_google_android_filament_Engine_nIsValidSwapChain(JNIEnv*, jclass,
return (jboolean)engine->isValid((SwapChain*)nativeSwapChain);
}

extern "C" JNIEXPORT void JNICALL
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass,
jlong nativeEngine) {
jlong nativeEngine, jlong timeout) {
Engine* engine = (Engine*) nativeEngine;
engine->flushAndWait();
return engine->flushAndWait((uint64_t)timeout);
}

extern "C" JNIEXPORT void JNICALL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,28 @@ public EntityManager getEntityManager() {
* {@link android.view.SurfaceHolder.Callback#surfaceDestroyed surfaceDestroyed}.</p>
*/
public void flushAndWait() {
nFlushAndWait(getNativeObject());
flushAndWait(Fence.WAIT_FOR_EVER);
}

/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
* all commands to this point are executed. Note that does guarantee that the
* hardware is actually finished.
*
* A timeout can be specified, if for some reason this flushAndWait doesn't complete before the timeout, it will
* return false, true otherwise.
*
* <p>This is typically used right after destroying the <code>SwapChain</code>,
* in cases where a guarantee about the <code>SwapChain</code> destruction is needed in a
* timely fashion, such as when responding to Android's
* <code>android.view.SurfaceHolder.Callback.surfaceDestroyed</code></p>
*
* @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.
*/
public boolean flushAndWait(long timeout) {
return nFlushAndWait(getNativeObject(), timeout);
}

/**
Expand Down Expand Up @@ -1437,7 +1458,7 @@ private static void assertDestroy(boolean success) {
private static native boolean nIsValidRenderTarget(long nativeEngine, long nativeTarget);
private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain);
private static native void nDestroyEntity(long nativeEngine, int entity);
private static native void nFlushAndWait(long nativeEngine);
private static native boolean nFlushAndWait(long nativeEngine, long timeout);
private static native void nFlush(long nativeEngine);
private static native boolean nIsPaused(long nativeEngine);
private static native void nSetPaused(long nativeEngine, boolean paused);
Expand Down
19 changes: 19 additions & 0 deletions filament/include/filament/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,25 @@ class UTILS_PUBLIC Engine {
*/
void flushAndWait();

/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
* all commands to this point are executed. Note that does guarantee that the
* hardware is actually finished.
*
* A timeout can be specified, if for some reason this flushAndWait doesn't complete before the timeout, it will
* return false, true otherwise.
*
* <p>This is typically used right after destroying the <code>SwapChain</code>,
* in cases where a guarantee about the <code>SwapChain</code> destruction is needed in a
* timely fashion, such as when responding to Android's
* <code>android.view.SurfaceHolder.Callback.surfaceDestroyed</code></p>
*
* @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.
*/
bool flushAndWait(uint64_t timeout);

/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait
* for commands to be either executed or the hardware finished.
Expand Down
4 changes: 4 additions & 0 deletions filament/src/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ void Engine::flushAndWait() {
downcast(this)->flushAndWait();
}

bool Engine::flushAndWait(uint64_t timeout) {
return downcast(this)->flushAndWait(timeout);
}

void Engine::flush() {
downcast(this)->flush();
}
Expand Down
42 changes: 10 additions & 32 deletions filament/src/details/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -665,50 +665,28 @@ void FEngine::flush() {
}

void FEngine::flushAndWait() {
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused())
<< "Cannot call flushAndWait() when rendering thread is paused!";
flushAndWait(FENCE_WAIT_FOR_EVER);
}

#if defined(__ANDROID__)
bool FEngine::flushAndWait(uint64_t timeout) {
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused())
<< "Cannot call Engine::flushAndWait() when rendering thread is paused!";

// first make sure we've not terminated filament
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isExitRequested())
<< "calling Engine::flushAndWait() after Engine::shutdown()!";

#endif
<< "Calling Engine::flushAndWait() after Engine::shutdown()!";

// enqueue finish command -- this will stall in the driver until the GPU is done
getDriverApi().finish();

#if defined(__ANDROID__) && !defined(NDEBUG)

// then create a fence that will trigger when we're past the finish() above
size_t tryCount = 8;
FFence* fence = FEngine::createFence();
UTILS_NOUNROLL
do {
FenceStatus status = fence->wait(FFence::Mode::FLUSH,250000000u);
// if the fence didn't trigger after 250ms, check that the command queue thread is still
// running (otherwise indicating a precondition violation).
if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) {
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isExitRequested())
<< "called Engine::shutdown() WHILE in Engine::flushAndWait()!";
tryCount--;
FILAMENT_CHECK_POSTCONDITION(tryCount) << "flushAndWait() failed inexplicably after 2s";
// if the thread is still running, maybe we just need to give it more time
continue;
}
break;
} while (true);
FFence* fence = createFence();
FenceStatus const status = fence->wait(FFence::Mode::FLUSH, timeout);
destroy(fence);

#else

FFence::waitAndDestroy(createFence(), FFence::Mode::FLUSH);

#endif

// finally, execute callbacks that might have been scheduled
getDriver().purge();

return status == FenceStatus::CONDITION_SATISFIED;
}

// -----------------------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions filament/src/details/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ class FEngine : public Engine {
void setPaused(bool paused);

void flushAndWait();
bool flushAndWait(uint64_t timeout);

// flush the current buffer
void flush();
Expand Down

0 comments on commit 41f7dc3

Please sign in to comment.