Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3beab8c
feat(materials): split builds into _native and _webgpu platform variants
nmfisher Jun 8, 2026
d7ed303
feat(materials): build split _native/_webgpu variants with WGSL support
nmfisher Jun 8, 2026
f2f89d8
feat(materials): 4-way backend variant build system
nmfisher Jun 8, 2026
2a13a37
feat(web): add WebGPU backend alongside WebGL2
nmfisher Jun 5, 2026
d247627
feat(native): opt-in WebGPU/Dawn scaffolding for local testing
nmfisher Jun 5, 2026
ec09c8b
build(macos): add --webgpu, --upload, and latest-tag resolution
nmfisher Jun 5, 2026
43aa8b3
ci: add webgpu input + latest-tag resolution to Build Filament workflow
nmfisher Jun 5, 2026
fafc5cf
ci(macos): suffix webgpu zip artifacts so they don't clobber canonica…
nmfisher Jun 5, 2026
e9605e8
build(linux): add --webgpu, --upload, and latest-tag resolution
nmfisher Jun 6, 2026
61115ed
ci(linux): install libx11-xcb-dev for Dawn X11 backend
nmfisher Jun 6, 2026
126d9b5
ci(linux): preemptively install full xcb extension set for Dawn
nmfisher Jun 6, 2026
14a6511
fix for dawn build
nmfisher Jun 7, 2026
4b01243
feat(webgpu): test infra + readPixels investigation
nmfisher Jun 7, 2026
b859059
chore: update Filament to latest main for WebGPU readPixels
nmfisher Jun 7, 2026
ee134b0
feat(webgpu): working readPixels + UBYTE workaround for blitter blend…
nmfisher Jun 8, 2026
fe2c250
fix(webgpu): mirror FLOAT-to-UBYTE workaround in test helper
nmfisher Jun 8, 2026
bd327f7
test(webgpu): WebGPU asset test suite
nmfisher Aug 15, 2026
9b42b79
chore: remove vendored Filament header tree from native/include/filament
nmfisher Aug 15, 2026
2a2d5c0
fix(webgpu): libc++ for imageio/tinyexr + webgpu-suffixed R2 URL
nmfisher Jun 7, 2026
eb010db
fix(linux): repair broken sed quoting that disabled FILAMENT_SKIP_SAM…
nmfisher Aug 15, 2026
865e978
ci(webgpu): bump macOS runner to macos-15 for Dawn/Tint on asb/webgpu
nmfisher Aug 15, 2026
bba0c5a
ci(macos): free runner disk space before Filament build
nmfisher Aug 15, 2026
27748ca
ci(webgpu): stage webgpu matc/resgen in R2 artifacts, use them for ma…
nmfisher Aug 15, 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
116 changes: 99 additions & 17 deletions .github/workflows/build-filament.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ on:
required: false
default: true
type: boolean
webgpu:
description: 'Build Filament with FILAMENT_SUPPORTS_WEBGPU=ON (macOS, Linux)'
required: false
default: false
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.platform || 'all' }}
Expand Down Expand Up @@ -67,6 +72,21 @@ jobs:
# Override individually if provided
[ -n "${{ inputs.filament_repo }}" ] && REPO="${{ inputs.filament_repo }}"
[ -n "${{ inputs.filament_version }}" ] && VERSION="${{ inputs.filament_version }}"
# Resolve VERSION=latest to the newest v*.* tag on the chosen repo.
# The per-platform jobs do `git clone --branch $VERSION` so we must
# produce a concrete tag here, not the literal string "latest".
if [ "$VERSION" = "latest" ]; then
echo "Resolving latest tag on $REPO..."
VERSION=$(git ls-remote --tags --refs --sort=-version:refname \
"$REPO" 'v*' \
| awk -F/ '{print $NF}' \
| head -1)
if [ -z "$VERSION" ]; then
echo "Error: could not resolve latest tag on $REPO"
exit 1
fi
echo "Resolved latest -> $VERSION"
fi
echo "repo=$REPO" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building Filament repo: $REPO ref: $VERSION"
Expand All @@ -84,7 +104,10 @@ jobs:

build-macos:
name: Build macOS
runs-on: macos-14
# Xcode 16 (Clang 16+) is required for Dawn's use of C++20 parenthesized
# aggregate initialization (P0960). macos-14 ships Xcode 15.4 / Clang 15,
# which lacks P0960 and fails compiling third_party/dawn/src/dawn/platform.
runs-on: macos-15
needs: setup
if: contains(needs.setup.outputs.matrix, 'macos')
env:
Expand All @@ -100,24 +123,57 @@ jobs:
- name: Setup Ninja
uses: seanmiddleditch/gha-setup-ninja@master

- name: Compute zip suffix
run: |
if [ "${{ inputs.webgpu }}" = "true" ]; then
echo "ZIP_SUFFIX=-webgpu" >> $GITHUB_ENV
else
echo "ZIP_SUFFIX=" >> $GITHUB_ENV
fi

- name: Free disk space
# The WebGPU build zips huge Dawn debug artifacts and ran out of disk
# (zip I/O error - No space left on device). jlumbroso/free-disk-space
# is Ubuntu-only, so delete the large preinstalled toolchains this
# build does not need: extra Xcode versions (keep the selected one),
# Android SDK/NDK, simulator runtimes, dotnet and ghcup.
run: |
df -h / | tail -1
SELECTED_XCODE=$(basename "$(dirname "$(dirname "$(xcode-select -p)")")")
echo "Keeping $SELECTED_XCODE"
for xcode in /Applications/Xcode*.app; do
if [ "$(basename "$xcode")" != "$SELECTED_XCODE" ]; then
echo "Removing $xcode"
sudo rm -rf "$xcode"
fi
done
sudo rm -rf /usr/local/lib/android /Users/runner/Library/Android
sudo rm -rf /Library/Developer/CoreSimulator /Users/runner/Library/Developer/CoreSimulator
sudo rm -rf /usr/local/share/dotnet /usr/local/.ghcup /usr/local/share/boost
df -h / | tail -1

- name: Build Filament
run: |
mkdir -p output
chmod +x scripts/build_macos.sh
scripts/build_macos.sh filament ${{ env.FILAMENT_VERSION }} output --clean
EXTRA_FLAGS=""
if [ "${{ inputs.webgpu }}" = "true" ]; then
EXTRA_FLAGS="--webgpu"
fi
scripts/build_macos.sh filament ${{ env.FILAMENT_VERSION }} output --clean $EXTRA_FLAGS

- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: filament-macos-release
path: output/filament-${{ env.FILAMENT_VERSION }}-macos-release.zip
name: filament-macos-release${{ env.ZIP_SUFFIX }}
path: output/filament-${{ env.FILAMENT_VERSION }}-macos-release${{ env.ZIP_SUFFIX }}.zip
retention-days: 7

- name: Upload debug artifact
uses: actions/upload-artifact@v4
with:
name: filament-macos-debug
path: output/filament-${{ env.FILAMENT_VERSION }}-macos-debug.zip
name: filament-macos-debug${{ env.ZIP_SUFFIX }}
path: output/filament-${{ env.FILAMENT_VERSION }}-macos-debug${{ env.ZIP_SUFFIX }}.zip
retention-days: 7

- name: Upload to R2
Expand All @@ -126,8 +182,8 @@ jobs:
pip3 install --break-system-packages awscli
for variant in release debug; do
aws s3 cp \
"output/filament-${{ env.FILAMENT_VERSION }}-macos-${variant}.zip" \
"s3://${{ env.R2_BUCKET_NAME }}/filament-${{ env.FILAMENT_VERSION }}-macos-${variant}.zip" \
"output/filament-${{ env.FILAMENT_VERSION }}-macos-${variant}${{ env.ZIP_SUFFIX }}.zip" \
"s3://${{ env.R2_BUCKET_NAME }}/filament-${{ env.FILAMENT_VERSION }}-macos-${variant}${{ env.ZIP_SUFFIX }}.zip" \
--endpoint-url "${{ env.R2_ENDPOINT }}"
done
env:
Expand Down Expand Up @@ -293,35 +349,61 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang libc++-dev libc++abi-dev ninja-build libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxxf86vm-dev
# Base X11 + Mesa for Filament's own OpenGL/Vulkan paths.
# The libxcb-* set is for Dawn's X11/XCB backend (DAWN_USE_X11)
# — Dawn pulls in dri3, present, randr, sync, shape, xfixes,
# shm-fence and friends. Adding them all up front to avoid the
# one-at-a-time CI ping-pong.
sudo apt-get install -y \
clang libc++-dev libc++abi-dev ninja-build \
libx11-dev libx11-xcb-dev \
libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev \
libxxf86vm-dev libgl1-mesa-dev libglu1-mesa-dev \
libxcb1-dev libxcb-dri3-dev libxcb-present-dev \
libxcb-randr0-dev libxcb-sync-dev libxcb-shape0-dev \
libxcb-xfixes0-dev libxcb-image0-dev libxcb-render0-dev \
libxcb-keysyms1-dev libxcb-util-dev libxcb-xkb-dev \
libxshmfence-dev libxkbcommon-dev libxkbcommon-x11-dev

- name: Compute zip suffix
run: |
if [ "${{ inputs.webgpu }}" = "true" ]; then
echo "ZIP_SUFFIX=-webgpu" >> $GITHUB_ENV
else
echo "ZIP_SUFFIX=" >> $GITHUB_ENV
fi

- name: Build Filament
run: |
mkdir -p output
chmod +x scripts/build_linux.sh
scripts/build_linux.sh filament ${{ env.FILAMENT_VERSION }} output --clean
EXTRA_FLAGS=""
if [ "${{ inputs.webgpu }}" = "true" ]; then
EXTRA_FLAGS="--webgpu"
fi
scripts/build_linux.sh filament ${{ env.FILAMENT_VERSION }} output --clean $EXTRA_FLAGS

- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: filament-linux-release
path: output/filament-${{ env.FILAMENT_VERSION }}-linux-release.zip
name: filament-linux-release${{ env.ZIP_SUFFIX }}
path: output/filament-${{ env.FILAMENT_VERSION }}-linux-release${{ env.ZIP_SUFFIX }}.zip
retention-days: 7

- name: Upload debug artifact
uses: actions/upload-artifact@v4
with:
name: filament-linux-debug
path: output/filament-${{ env.FILAMENT_VERSION }}-linux-debug.zip
name: filament-linux-debug${{ env.ZIP_SUFFIX }}
path: output/filament-${{ env.FILAMENT_VERSION }}-linux-debug${{ env.ZIP_SUFFIX }}.zip
retention-days: 7

- name: Upload to R2
if: inputs.upload_to_r2
run: |
for variant in release debug; do
aws s3 cp \
"output/filament-${{ env.FILAMENT_VERSION }}-linux-${variant}.zip" \
"s3://${{ env.R2_BUCKET_NAME }}/filament-${{ env.FILAMENT_VERSION }}-linux-${variant}.zip" \
"output/filament-${{ env.FILAMENT_VERSION }}-linux-${variant}${{ env.ZIP_SUFFIX }}.zip" \
"s3://${{ env.R2_BUCKET_NAME }}/filament-${{ env.FILAMENT_VERSION }}-linux-${variant}${{ env.ZIP_SUFFIX }}.zip" \
--endpoint-url "${{ env.R2_ENDPOINT }}"
done
env:
Expand Down Expand Up @@ -522,4 +604,4 @@ jobs:
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_DEFAULT_REGION: auto
47 changes: 47 additions & 0 deletions docs/upstream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Upstream Issues

## Filament WebGPU Blitter: blending enabled on non-blendable formats

**Repository:** google/filament
**Status:** Unfiled
**Date:** 2026-06-07

### Summary

The WebGPU `WebGPUBlitter::createRenderPipeline` creates render pipelines with blending enabled unconditionally. When the destination texture format is non-blendable (e.g. `RGBA32Float`), Dawn rejects the pipeline with:

```
VALIDATION Blending is enabled but color format (TextureFormat::RGBA32Float) is not blendable.
- While validating targets[0] framebuffer output.
- While validating fragment state.
- While calling [Device "graphics_device"].CreateRenderPipeline([RenderPipelineDescriptor ""blitLow""]).
```

This invalidates the command buffer, causing any subsequent operations in the same encoder (including `CopyTextureToBuffer` for `readPixels`) to be silently discarded.

### Affected Code

- `filament/backend/src/webgpu/WebGPUBlitter.cpp` — `createRenderPipeline()` constructs `wgpu::ColorTargetState` without disabling blending for non-blendable destination formats.

### Reproduction

1. Create a headless WebGPU swapchain (format: `RGBA8Unorm`).
2. Request `readPixels` with `PixelDataType::FLOAT` (maps to `RGBA32Float`).
3. `conversionNecessary()` detects format mismatch (`RGBA8Unorm` != `RGBA32Float`).
4. Blitter creates an intermediate `RGBA32Float` staging texture and attempts to create a render pipeline with blending enabled against it.
5. Dawn validation rejects the pipeline → command encoder becomes invalid → staging buffer stays empty → pixel buffer is all zeros.

Also reproducible when reading from an `RGBA32Float` render target (e.g. a user-created `TextureFormat.RGBA32F` used as a color attachment), since the blit destination inherits the non-blendable format.

### Suggested Fix

In `WebGPUBlitter::createRenderPipeline()`, check whether the destination format supports blending using `wgpu::TextureFormat::supportsBlend()` (or check `wgpu::TextureFormat` capabilities). When the format is not blendable, set `colorTargetState.blend = nullptr` (no blending) instead of the default blended state.

Alternatively, the readback path in `readTextureToBuffer` could avoid the blit entirely when the only difference is the channel type (e.g., normalized vs. float) by using `CopyTextureToBuffer` directly and handling the type conversion on the CPU side.

### Workaround

Match the pixel data format/type to the source texture format to avoid triggering the conversion blit:
- For headless swapchains (RGBA8Unorm): use `PixelDataFormat::RGBA` + `PixelDataType::UBYTE`.
- Avoid `PixelDataType::FLOAT` when reading from non-float textures.
- Avoid RGBA32Float render targets when readback is needed.
Binary file modified examples/assets/customattributes.filamat
Binary file not shown.
Binary file modified examples/assets/solidcolor.filamat
Binary file not shown.
Binary file modified examples/assets/viewspace.filamat
Binary file not shown.
24 changes: 23 additions & 1 deletion examples/dart/js_wasm/web/example.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:math';
import 'package:web/web.dart';
import 'package:logging/logging.dart';
Expand All @@ -9,6 +10,25 @@ import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.d
import 'web_input_handler.dart';
import 'package:thermion_dart/src/bindings/src/thermion_dart_js_interop.g.dart';

bool _webGpuAvailable() {
final nav = globalContext.getProperty('navigator'.toJS);
if (nav == null) return false;
return (nav as JSObject).getProperty('gpu'.toJS) != null;
}

Backend _resolveBackend() {
final query = Uri.parse(window.location.href).queryParameters['backend'];
if (query == 'webgpu') {
if (!_webGpuAvailable()) {
print('?backend=webgpu requested but navigator.gpu unavailable; '
'falling back to OPENGL.');
return Backend.OPENGL;
}
return Backend.WEBGPU;
}
return Backend.OPENGL;
}

void main(List<String> arguments) async {
Logger.root.onRecord.listen((record) {
print(record);
Expand All @@ -25,7 +45,9 @@ void main(List<String> arguments) async {
print(err.toString());
}

final config = FFIFilamentConfig(backend: Backend.OPENGL);
final backend = _resolveBackend();
print('Using backend: $backend');
final config = FFIFilamentConfig(backend: backend);

await FFIFilamentApp.create(config: config);

Expand Down
8 changes: 8 additions & 0 deletions examples/flutter/quickstart/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
_sun = DirectLight.sun(direction: Vector3(0.7, -1, -0.8).normalized());
if (kIsWeb) {
final wantsWebGpu = Uri.base.queryParameters['backend'] == 'webgpu';
final backend = wantsWebGpu && WebGpu.isSupported()
? Backend.WEBGPU
: Backend.OPENGL;
ThermionFlutterPlugin.instance.setOptions(ThermionFlutterOptions(
webOptions: WebOptions(backend: backend)));
}
}

/// Applies the batch: mounts `_batch` viewers at once when the grid is
Expand Down
Loading
Loading