Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
021859b
fix(linux): bootstrap Filament on Flutter EGL display
nmfisher Aug 20, 2026
a2cac9d
fix(linux): isolate desktop EGL operations
nmfisher Aug 20, 2026
fd36a99
Merge a2cac9db7dfb48ceacc470500e3846b76d7207d4 into b8abd924451597f11…
nmfisher Aug 21, 2026
eb75510
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 21, 2026
e81a6c6
fix!: caller-managed ColorGrading lifecycle, builder-only API (remove…
nmfisher Aug 21, 2026
e87873c
chore: update web.version
github-actions[bot] Aug 21, 2026
db9f8b4
CI: update golden image baseline (#270)
nmfisher Aug 21, 2026
7975fe2
ci(linux): smoke test quickstart on X11 and Wayland
nmfisher Aug 21, 2026
4d0fba2
fix(linux): capture Flutter GL version during bootstrap
nmfisher Aug 21, 2026
46737d1
ci(linux): expose llvmpipe OpenGL 4.5
nmfisher Aug 21, 2026
97040e7
ci(linux): run display smoke on GPU runner
nmfisher Aug 21, 2026
4d850a0
ci(linux): make display smoke runnable on the Fedora GPU runner
nmfisher Aug 21, 2026
ee0909f
feat: align skybox API with Filament headers, return Skybox from crea…
nmfisher Aug 21, 2026
4cda42e
chore: update web.version
github-actions[bot] Aug 21, 2026
3899baf
refactor(flutter): keep widget-building out of the plugin API
nmfisher Aug 21, 2026
e500629
Merge 3899baf77a8ea64626bb5398a84716efa8f32232 into 4cda42ea005912123…
nmfisher Aug 21, 2026
7217dc9
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 21, 2026
e6f1b5b
ci(linux): drop temporary push trigger from display smoke
nmfisher Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions .github/workflows/linux-display-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
name: Linux Display Smoke

on:
workflow_dispatch:
inputs:
ref:
description: Git ref to test
required: false
type: string
x11-display:
description: X11 display exposed to the runner
required: false
default: ':0'
type: string

jobs:
quickstart:
name: Quickstart viewer (X11 + Wayland)
runs-on: [self-hosted, linux, x64]
timeout-minutes: 30
env:
# Fall back to :0 (the runner's desktop session display) when the input
# is not provided.
DISPLAY: ${{ inputs.x11-display || ':0' }}
defaults:
run:
working-directory: examples/flutter/quickstart
shell: bash
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.8'
channel: stable
architecture: X64
cache: true
pub-cache: true

- name: Install Linux dependencies
working-directory: .
run: |
if command -v apt-get >/dev/null; then
sudo apt-get update -y
sudo apt-get install -y \
clang cmake ninja-build pkg-config \
libgtk-3-dev liblzma-dev libdrm-dev \
libegl1 libegl1-mesa-dev libc++-dev libc++abi-dev \
weston x11-utils
elif command -v dnf >/dev/null; then
# Fedora self-hosted runner. Dependencies are preinstalled on the
# image; only fetch missing ones, and only with passwordless sudo.
if ! sudo -n true 2>/dev/null; then
echo '::notice::No passwordless sudo; assuming dependencies are preinstalled'
exit 0
fi
sudo dnf install -y \
clang cmake ninja-build pkgconf-pkg-config \
gtk3-devel libdrm-devel mesa-libEGL-devel xz-devel \
libcxx-devel libcxxabi-devel \
weston xdpyinfo
else
echo '::warning::No apt-get or dnf found; assuming dependencies are preinstalled'
fi

# Prime generated headers before the example's CMake configure step.
- name: Run thermion_flutter unit tests
working-directory: thermion_flutter/thermion_flutter
run: |
flutter pub get
flutter test

- name: Build quickstart
run: |
flutter pub get
flutter build linux

- name: Verify GPU and X11 session
run: |
if [[ ! -r /dev/dri/renderD128 || ! -w /dev/dri/renderD128 ]]; then
echo '::error::The runner user needs read/write access to /dev/dri/renderD128'
exit 1
fi
if ! xdpyinfo >/dev/null; then
echo "::error::Start the runner from the logged-in X11 session or provide a usable x11-display input (currently ${DISPLAY})"
exit 1
fi

- name: Run viewer smoke test (X11)
run: |
set -o pipefail
GDK_BACKEND=x11 timeout 8m flutter test \
integration_test/display_server_smoke_test.dart \
-d linux 2>&1 | tee "${RUNNER_TEMP}/display-smoke-x11.log"

- name: Run viewer smoke test (Wayland)
run: |
set -o pipefail
export XDG_RUNTIME_DIR="${RUNNER_TEMP}/wayland-runtime"
export WAYLAND_DISPLAY=wayland-ci
mkdir -p "${XDG_RUNTIME_DIR}"
chmod 700 "${XDG_RUNTIME_DIR}"

weston \
--backend=headless-backend.so \
--use-gl \
--no-config \
--socket="${WAYLAND_DISPLAY}" \
--width=1280 \
--height=720 \
--idle-time=0 \
--log="${RUNNER_TEMP}/weston.log" &
weston_pid=$!
trap 'kill "${weston_pid}" 2>/dev/null || true' EXIT

for _ in {1..50}; do
if [[ -S "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}" ]]; then
break
fi
if ! kill -0 "${weston_pid}" 2>/dev/null; then
cat "${RUNNER_TEMP}/weston.log"
exit 1
fi
sleep 0.1
done
test -S "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}"

unset DISPLAY
GDK_BACKEND=wayland timeout 8m flutter test \
integration_test/display_server_smoke_test.dart \
-d linux 2>&1 | tee "${RUNNER_TEMP}/display-smoke-wayland.log"

- name: Upload display-server logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: linux-display-smoke-logs
path: |
${{ runner.temp }}/display-smoke-*.log
${{ runner.temp }}/weston.log
retention-days: 5
104 changes: 73 additions & 31 deletions .github/workflows/run-dart-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
description: Git ref (SHA or branch) to check out
required: false
type: string

workflow_dispatch:
inputs:
ref:
Expand Down Expand Up @@ -108,11 +109,13 @@ jobs:
test/all_axes_test.dart \
test/bone_picking_tests.dart \
test/all_materials_smoke_test.dart \
test/skybox_tests.dart \
--concurrency=1

- name: Run tests (Windows)
if: matrix.name == 'Windows'
working-directory: thermion_dart
shell: pwsh
env:
VK_ICD_FILENAMES: C:\swiftshader\vk_swiftshader_icd.json
run: |
Expand All @@ -134,41 +137,14 @@ jobs:
test/gizmo_tests_new.dart `
test/all_axes_test.dart `
test/all_materials_smoke_test.dart `
test/skybox_tests.dart `
--concurrency=1

- name: Zip output
if: matrix.name == 'Linux'
run: zip -r output.zip ./thermion_dart/test/output

- name: Upload test output
if: matrix.name == 'Linux'
- name: Upload Dart test output candidate
uses: actions/upload-artifact@v4
with:
name: golden-images-${{ github.sha }}
path: output.zip

- name: Download golden images from previous run
if: matrix.name == 'Linux'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh run download 32373595042 \
--name golden-images-7d723559f07771f8bb83c85824fcb2516af22940 \
--dir ./thermion_dart/test/golden-downloads
- name: Unzip golden images
if: matrix.name == 'Linux'
run: |
cd thermion_dart/test/golden-downloads && unzip output.zip

- name: Install Python dependencies
if: matrix.name == 'Linux'
run: |
python -m pip install --upgrade pip
pip install Pillow numpy

- name: Compare golden images
if: matrix.name == 'Linux'
run: cd thermion_dart/test && python compare_goldens.py
name: dart-test-output-${{ matrix.name }}
path: thermion_dart/test/output

- name: Upload logs
if: failure()
Expand Down Expand Up @@ -243,8 +219,15 @@ jobs:
test/overlay_tests.dart \
test/wireframe_renderable_test.dart \
test/gizmo_tests_new.dart \
test/skybox_tests.dart \
--concurrency=1

- name: Upload Dart test output candidate
uses: actions/upload-artifact@v4
with:
name: dart-test-output-macOS
path: thermion_dart/test/output

- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -352,6 +335,12 @@ jobs:
VK_ICD_FILENAMES: C:\swiftshader\vk_swiftshader_icd.json
run: dart pub get; dart test -j1 test/image_tests.dart test/overlay_tests.dart test/wireframe_renderable_test.dart test/gizmo_tests_new.dart test/all_axes_test.dart test/all_materials_smoke_test.dart --concurrency=1

- name: Upload material-variant test output candidate
uses: actions/upload-artifact@v4
with:
name: dart-material-output-${{ matrix.name }}
path: thermion_dart/test/output

- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
Expand All @@ -360,3 +349,56 @@ jobs:
path: |
${{ github.workspace }}/thermion_dart/.dart_tool/thermion_dart/log/build.log
retention-days: 5

compare-dart-test-output:
name: Compare Dart Test Output (${{ matrix.name }})
needs:
- dart-tests
- dart-tests-macos
- dart-tests-material-variants
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include:
- name: Linux
artifact: dart-test-output-Linux
- name: Windows
artifact: dart-test-output-Windows
- name: macOS
artifact: dart-test-output-macOS
- name: Linux opengl-only
artifact: dart-material-output-Linux opengl-only
- name: Windows vulkan-only
artifact: dart-material-output-Windows vulkan-only
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.head_ref || github.ref }}

- name: Download current test output
uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact }}
path: thermion_dart/test/golden-candidate

- name: Download reviewed platform baseline
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
# To refresh every baseline, first push a capture-only commit,
# inspect its dart-*-output-* artifacts, then update this run ID.
run-id: 32477413084
name: ${{ matrix.artifact }}
path: thermion_dart/test/golden-baseline

- name: Install golden comparison dependencies
run: |
python -m pip install --upgrade pip
python -m pip install Pillow numpy

- name: Compare all captured test output
run: >-
python thermion_dart/test/compare_goldens.py
--golden-dir thermion_dart/test/golden-baseline
--output-dir thermion_dart/test/golden-candidate
35 changes: 27 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,33 @@

### Changes
- `FilamentApp` exposes the native engine handle as a public
`Pointer<TEngine> get engine` (thermion_dart is FFI-backed on every
supported target, including web/WASM). `ToneMapper` factory methods take
the abstract `FilamentApp` instead of `FFIFilamentApp`, so callers no
longer need to downcast (`ToneMapper.aces(FilamentApp.instance!)` just
works).
- `TranslationAxisMaterial.createMaterialInstance` takes the abstract
`FilamentApp` too, and `FFIMaterial`/`FFIMaterialInstance` hold the
abstract type internally (they only ever needed the engine handle).
- `TranslationAxisMaterial.createMaterialInstance` and `ToneMapper` factory methods
now take the abstract `FilamentApp` instead of
`FFIFilamentApp`
- `ColorGrading` follows Filament's ownership model (caller
manages lifecycle). A `ColorGrading` must be dissociated from every view
(`setColorGrading` with a replacement or null) BEFORE calling
`ColorGrading.dispose()`. Attaching one grading to multiple views
remains supported, but its lifetime is entirely yours.
- `ColorGrading` and `ColorGradingBuilder` expose `dispose()` through the
public interface (previously FFI-only / missing).
- `Skybox` is now exported from the public API with methods for layer mask, intensity, etc.
- `ThermionViewer.setBackgroundColor` and `ThermionViewer.loadSkybox` now
return the created `Skybox`, and
`ThermionViewer.getSkybox` returns whatever skybox is currently attached
to the viewer's scene. The viewer no longer caches the skybox internally:
`removeSkybox` derives it from the scene, detaches it, and returns it without
destroying caller-owned resources.
Native library rebuild required (C API signatures for
the skybox builders changed).

### Breaking changes
- remove the unused `FilamentApp.createColorGrading` — it returned a raw
pointer nobody could destroy; use `View.createColorGradingBuilder().build()`
instead.
- remove `View.setToneMapper` and `ThermionViewer.setToneMapper` - use
`view.createColorGradingBuilder().toneMapper(...).build()` followed by
`view.setColorGrading()` instead.

## 0.6.0

Expand Down
4 changes: 3 additions & 1 deletion examples/dart/cli_headless/bin/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ void main() async {
await FilamentApp.instance!.register(sc, viewer.view);

await viewer.view.setFrustumCullingEnabled(false);
await viewer.setBackgroundColor(1, 0, 1, 1);
await (await viewer.view.getScene()).setSkybox(
await FilamentApp.instance!.createColoredSkybox(r: 1, g: 0, b: 1, a: 1),
);
await viewer.setViewport(width, height);
final result = await FilamentApp.instance!.capture(
sc,
Expand Down
7 changes: 6 additions & 1 deletion examples/dart/cli_headless/bin/render_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,12 @@ Future<void> main(List<String> argv) async {
await viewer.view.setFrustumCullingEnabled(false);
await viewer.setViewport(width, height);
// ACES tone mapping for filmic, non-blow-out highlights.
await viewer.setToneMapper(await ToneMapper.aces(app));
final builder = await viewer.view.createColorGradingBuilder();
final toneMapper = await ToneMapper.aces(app);
final colorGrading = await builder.toneMapper(toneMapper).build();
await builder.dispose();
await toneMapper.dispose(); // builder disposed; grading holds a copy
await viewer.view.setColorGrading(colorGrading);

// Environment: HDRI skybox + image-based lighting. `--env=<name>` selects one.
// `--no-skybox` skips the visible skybox (background goes black) but keeps the
Expand Down
5 changes: 4 additions & 1 deletion examples/dart/cli_windows/bin/cli_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ void main(List<String> arguments) async {
await camera.setLensProjection();
await FilamentApp.instance!.renderManager.attach(view, swapChain);

await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await (await viewer.view.getScene()).setSkybox(
await FilamentApp.instance!
.createColoredSkybox(r: 1.0, g: 0.0, b: 0.0, a: 1.0),
);

var skyboxPath = File("../../assets/default_env_skybox.ktx").absolute;
await viewer.loadSkybox(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ Future<void> setupMaterialsAndLighting(
final camera = await viewer.getActiveCamera();
await camera.lookAt(Vector3(0, 4.0, 11), focus: Vector3(0, 0, 0));

await viewer.setBackgroundColor(0.18, 0.18, 0.18, 1.0);
await (await viewer.view.getScene()).setSkybox(
await FilamentApp.instance!
.createColoredSkybox(r: 0.18, g: 0.18, b: 0.18, a: 1.0),
);
// Dimmed IBL so the three coloured point lights read clearly against the
// ambient fill — at higher intensities the image-based lighting washes out
// their orbiting contribution.
Expand Down
Loading
Loading