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.
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
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.