Skip to content

Android: Filament 'SwapChain must remain valid until endFrame is called' — quickstart example crashes immediately on launch #166

@mushogenshin

Description

@mushogenshin

Summary

`thermion_flutter`'s own `quickstart` example crashes on launch with `SIGABRT` on Android (both emulator and physical device, ARM64). The Filament error is unambiguous:

```
E/Filament: Precondition
E/Filament: in endFrame:490
E/Filament: reason: SwapChain must remain valid until endFrame is called.
F/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE)
```

The Dart-side log right before the crash is the smoking gun:

```
D/thermion_flutter: Creating SurfaceTexture 1080x2424
D/thermion_flutter: Loading library
[INFO] FFIFilamentApp: Created swapchain from window
[INFO] FFIFilamentApp: Destroying swapchain ← destroySwapChain called moments after creation
[INFO] FFIFilamentApp: Destroyed swapchain
E/Filament: Precondition in endFrame:490 — SwapChain must remain valid until endFrame is called.
```

Diagnosis

`FFIFilamentApp.destroySwapChain` at ffi_filament_app.dart:255-266:

```dart
Future destroySwapChain(SwapChain swapChain) async {
_logger.info("Destroying swapchain");
await renderManager.detachAll(swapChain);
await withVoidCallback((requestId, callback) {
Engine_destroySwapChainRenderThread(
engine, swapChain.getNativeHandle(), requestId, callback);
});
_swapChains.remove(swapChain);
_logger.info("Destroyed swapchain");
}
```

`detachAll` removes the swap chain from the render-manager bookkeeping but doesn't wait for any in-flight `endFrame` to complete. `Engine_destroySwapChainRenderThread` then destroys the underlying `filament::SwapChain`. If a frame's `beginFrame` was issued before `detachAll` ran, its `endFrame` will execute against a destroyed pointer and trip the assertion.

The trigger seems to be Android-specific: Flutter's `SurfaceTextureEntry` lifecycle results in the swapchain being destroyed and re-created early during widget mount, while a frame is mid-flight. iOS, macOS, and Windows don't reproduce — different surface lifecycle.

Reproduction

```bash
git clone https://github.com/nmfisher/thermion
cd thermion/examples/flutter/quickstart
flutter pub get
flutter run -d
```

Crashes during initial widget mount, before any user interaction.

Environment

  • Thermion: latest `develop` head (and confirmed on a fork pinned several commits behind, mushogenshin/thermion@1c0864d8).
  • Flutter: master, native-assets enabled.
  • Filament backend: OpenGL ES 3.0.
  • Android emulator: `sdk gphone64 arm64` API 35, host = Apple M4 Max.
  • Physical Android device: also reproduces (different sized surface, same crash).

Filament identifies as `[Google (Apple)], [Android Emulator OpenGL ES Translator (Apple M4 Max)]` in the emulator case but the physical device shows a real GPU — both crash identically.

Reproducer logs

Full `flutter run` stderr including the Filament backtrace:

```
[IMPORTANT:flutter/shell/platform/android/android_context_gl_impeller.cc(104)] Using the Impeller rendering backend (OpenGLES).
... [boot]
I/Filament: FEngine (64 bits) created at 0xb4000075e10c9c60 (threading is enabled)
I/Filament: FEngine resolved backend: OpenGL
I/Filament: [Google (Apple)], [Android Emulator OpenGL ES Translator (Apple M4 Max)], [OpenGL ES 3.0 (4.1 Metal - 90.5)], [OpenGL ES GLSL ES 3.00]
I/flutter: [INFO] FFIFilamentApp: Created engine with feature level 1
D/ThermionFlutter: No material provider specified, using default ubershader provider
I/flutter: [INFO] FFIFilamentApp: Initialization complete
I/flutter: [INFO] ThermionFlutterPluginImpl: Frame scheduler started in port mode (hot restart safe)
I/flutter: [INFO] FFIFilamentApp: Loading glTF from buffer (1732 bytes) with resourceUri assets/
I/flutter: [INFO] FFIFilamentApp: glTF resources loaded
D/ThermionFlutter: Error: invalid renderable
I/flutter: [INFO] ThermionViewerFFI: IBL texture upload complete
I/flutter: Specify skyboxPath or background, not both
I/flutter: [INFO] FFILightManager: Created light of type LightType.DIRECTIONAL with entity ID 7
D/ThermionFlutter: Set light color to 1.000000 1.000000 1.000000 (RGB)
D/thermion_flutter: Creating SurfaceTexture 1080x2424
D/thermion_flutter: Loading library
I/flutter: [INFO] FFIFilamentApp: Created swapchain from window
I/flutter: [INFO] FFIFilamentApp: Destroying swapchain
I/flutter: [INFO] FFIFilamentApp: Destroyed swapchain
E/Filament: Precondition
E/Filament: in endFrame:490
E/Filament: reason: SwapChain must remain valid until endFrame is called.
F/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE)
```

Backtrace tip:

```
backtrace:
#00 pc 00000000000707b0 /apex/com.android.runtime/lib64/bionic/libc.so (abort+156)
#1 pc 000000000038afb0 /data/app/.../base.apk (offset 0x35d4000)
(utils::TPanicutils::PreconditionPanic::panic(...)+176)
```

Suggested fix direction

Make `destroySwapChain` wait for any pending `Renderer_endFrame` to drain before calling `Engine_destroySwapChainRenderThread`. One option: tag `detachAll` with a render-thread fence and `await` it. Another: keep destroyed swap-chains in a "pending free" queue and only release after the next vsync. Happy to test fixes.

Related upstream patches I'm carrying

I have a fork (mushogenshin/thermion) with three Android/iOS/Windows fixes filed at #160, #163, #164. None of those touch swapchain lifecycle, so this issue is independent of those PRs and the crash repros against an unmodified `develop` head as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions