Skip to content

Commit bcec155

Browse files
authored
fix(⚛️): ship an Expo config plugin that disables Metal API validation (#412)
Running WebGPU on the iOS Simulator requires disabling Metal API validation: Dawn triggers false-positive validation errors that do not occur on device. With Expo's Continuous Native Generation, unchecking the setting manually in Xcode does not survive `expo prebuild --clean`, so a config plugin has to re-apply it on every prebuild. Following Expo's guidance that packages should ship their own config plugin (so plugin and package versions stay aligned), the plugin lives in this package rather than in expo/config-plugins. Usage: { "expo": { "plugins": ["react-native-webgpu"] } } The plugin sets enableGPUValidationMode="1" on the LaunchAction of all shared Xcode schemes of the iOS project - the same mechanism Flutter uses (flutter/flutter#159228) and the equivalent of unchecking Product > Scheme > Edit Scheme > Run > Diagnostics > Metal API Validation. - plugin/src is compiled to plugin/build by `yarn build:plugin`, which is part of `yarn build`, so the existing release workflow (publish-npm.yml) ships the plugin with every package release - app.plugin.js is the entry point Expo resolves; it and plugin/build are added to the published files - unit tests (yarn test:plugin) run standalone from the e2e harness and are wired into CI - documented in the docs app (Expo guide + installation troubleshooting), the package README, and CONTRIBUTING.md
1 parent 3038294 commit bcec155

20 files changed

Lines changed: 481 additions & 11 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ jobs:
5252
- name: Build packages
5353
run: yarn turbo run build --filter='./packages/*'
5454

55+
- name: Test Expo config plugin
56+
working-directory: packages/webgpu
57+
run: yarn test:plugin
58+
5559
# Validate the semantic-release config without publishing, so a broken
5660
# .releaserc or publish script is caught on PRs rather than at release time.
5761
- name: Release dry run

apps/docs/content/docs/getting-started/expo.mdx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,21 @@ The template includes `react-native-webgpu`, Reanimated, and Metro settings comp
1616
For a walkthrough of the template, watch the video below.
1717

1818
<Video src="/videos/expo-template.mp4" />
19+
20+
## Config plugin
21+
22+
`react-native-webgpu` ships its own [Expo config plugin](https://docs.expo.dev/config-plugins/introduction/). Add it to the `plugins` array of your app config:
23+
24+
```json title="app.json"
25+
{
26+
"expo": {
27+
"plugins": ["react-native-webgpu"]
28+
}
29+
}
30+
```
31+
32+
The plugin disables **Metal API Validation** in the shared Xcode scheme of your iOS project - the equivalent of unchecking Product → Scheme → Edit Scheme → Run → Diagnostics → Metal API Validation in Xcode. This is required to run WebGPU on the **iOS Simulator**: Metal API validation flags false positives in Dawn that do not occur on a physical device and would otherwise crash the app (see the [troubleshooting section](/docs/getting-started/installation#troubleshooting)).
33+
34+
With [Continuous Native Generation](https://docs.expo.dev/workflow/continuous-native-generation/), the `ios` directory is regenerated by `npx expo prebuild --clean`, so unchecking the setting manually in Xcode does not stick - the config plugin re-applies it on every prebuild.
35+
36+
The plugin only affects the iOS Xcode scheme; it is a no-op on Android and it changes nothing for release builds on physical devices. Since the plugin ships with the package, its version is always aligned with the `react-native-webgpu` version you have installed.

apps/docs/content/docs/getting-started/installation.mdx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,17 @@ On Android emulators, WebGPU may fall back to a **software adapter**. Rendering
4848
<Callout type="warn" title="iOS Simulator - Metal API Validation">
4949
When running on the **iOS Simulator**, disable **Metal API Validation** in Xcode or you may hit false-positive errors and crashes that do not occur on device.
5050

51-
**Steps:** Product → Scheme → Edit Scheme → Run → Diagnostics → uncheck **Metal API Validation**.
51+
**Expo:** add the [config plugin](/docs/getting-started/expo#config-plugin) that ships with `react-native-webgpu` to your app config - it disables the setting automatically on every prebuild:
52+
53+
```json title="app.json"
54+
{
55+
"expo": {
56+
"plugins": ["react-native-webgpu"]
57+
}
58+
}
59+
```
60+
61+
**Bare React Native:** Product → Scheme → Edit Scheme → Run → Diagnostics → uncheck **Metal API Validation**.
5262

5363
Metal API Validation is a debug mode that checks every Metal call against the API specification. The simulator's Metal stack is not identical to real hardware, and validation can flag legitimate Dawn/WebGPU usage as invalid. Disabling it for local development is standard practice; keep validation enabled when debugging raw Metal code on device if needed.
5464

packages/webgpu/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ android/keystores/debug.keystore
7171

7272
# generated by bob
7373
lib/
74+
75+
# compiled Expo config plugin
76+
plugin/build/

packages/webgpu/CONTRIBUTING.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,14 @@ Example app (from `apps/example/`): `yarn start` · `yarn ios` · `yarn android`
1313

1414
From the repo root: `yarn lint` · `yarn tsc` · `yarn build:docs`
1515

16-
Tests (from `packages/webgpu/`): `yarn test:ref` (Chrome reference) · `yarn test` (E2E — open the example app on the E2E screen)
16+
Tests (from `packages/webgpu/`): `yarn test:ref` (Chrome reference) · `yarn test` (E2E — open the example app on the E2E screen) · `yarn test:plugin` (Expo config plugin unit tests)
1717

1818
Other `packages/webgpu` scripts: `yarn codegen` · `yarn clean-dawn` · `yarn build-dawn`
1919

20+
## Expo config plugin
21+
22+
The Expo config plugin lives in `plugin/src` and is compiled to `plugin/build` by `yarn build:plugin` (also part of `yarn build`, so releases always ship it). `app.plugin.js` at the package root is the entry point Expo resolves when apps list `"react-native-webgpu"` in their `plugins`. The plugin disables Metal API validation in the generated iOS Xcode scheme (required on the iOS Simulator); it is released together with the package by the regular release workflow, so plugin and package versions are always aligned.
23+
2024
## Upgrading Dawn
2125

2226
The Dawn version is pinned in two places that must stay in sync:

packages/webgpu/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ WebGPU for React Native, powered by Dawn.
88

99
[Installation instructions](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/installation)
1010

11+
## Expo config plugin
12+
13+
The package ships with an Expo config plugin that disables Metal API validation in the iOS Xcode scheme, which is required to run WebGPU on the iOS Simulator (Metal API validation reports false positives for Dawn that do not occur on device). Because the plugin is part of the package, plugin and package versions are always aligned.
14+
15+
```json
16+
{
17+
"expo": {
18+
"plugins": ["react-native-webgpu"]
19+
}
20+
}
21+
```
22+
23+
See the [Expo guide](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/expo#config-plugin) for details.
24+
1125
## Contributing
1226

1327
For detailed information on library development, building, testing, and contributing guidelines, please see [CONTRIBUTING.md](packages/webgpu/CONTRIBUTING.md).

packages/webgpu/app.plugin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./plugin/build").default;

packages/webgpu/eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import wcandillon from "eslint-config-react-native-wcandillon";
33

44
export default [
55
{
6-
ignores: ["**/*/components/meshes", "node_modules/**", "lib/**", "android/**", "ios/**", "*.config.js"]
6+
ignores: ["**/*/components/meshes", "node_modules/**", "lib/**", "android/**", "ios/**", "*.config.js", "plugin/build/**", "plugin/jest.config.js", "app.plugin.js"]
77
},
88
...wcandillon,
99
{

packages/webgpu/jest.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ module.exports = {
1111
// modulePathIgnorePatterns only affects the module registry, not test
1212
// discovery. Without this, the .d.ts files emitted into lib/ by `bob build`
1313
// are picked up as test suites and fail with "must contain at least one test".
14-
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/lib/'],
14+
// The Expo config plugin has its own standalone jest config
15+
// (plugin/jest.config.js, run via `yarn test:plugin`) so its unit tests do
16+
// not go through the e2e globalSetup below.
17+
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/lib/', '<rootDir>/plugin/'],
1518
globalSetup: './src/__tests__/globalSetup.ts',
1619
globalTeardown: './src/__tests__/globalTeardown.ts',
1720
transform: {

packages/webgpu/package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@
1818
"cpp/**/*.{h,cpp}",
1919
"apple/**",
2020
"libs/**",
21-
"*.podspec"
21+
"*.podspec",
22+
"app.plugin.js",
23+
"plugin/build/**"
2224
],
2325
"dawn": "chromium/7849",
2426
"scripts": {
2527
"test": "NODE_OPTIONS='--experimental-require-module' jest -i",
2628
"test:ref": "REFERENCE=true NODE_OPTIONS='--experimental-require-module' jest -i",
29+
"test:plugin": "jest -c plugin/jest.config.js",
2730
"lint": "eslint . --ext .ts,.tsx --max-warnings 0 --cache --fix",
2831
"tsc": "tsc --noEmit",
29-
"build": "bob build",
32+
"build": "bob build && yarn build:plugin",
33+
"build:plugin": "tsc -p plugin/tsconfig.build.json",
3034
"release": "semantic-release",
3135
"build-dawn": "tsx scripts/build/dawn.ts",
3236
"clean-dawn": "rimraf ./libs && rimraf ../../externals/dawn/out",
@@ -41,7 +45,9 @@
4145
"keywords": [
4246
"react-native",
4347
"ios",
44-
"android"
48+
"android",
49+
"expo",
50+
"expo-config-plugin"
4551
],
4652
"repository": {
4753
"type": "git",
@@ -59,6 +65,7 @@
5965
"provenance": true
6066
},
6167
"devDependencies": {
68+
"@expo/config-plugins": "^57.0.3",
6269
"@semantic-release/commit-analyzer": "^13.0.0",
6370
"@semantic-release/exec": "^7.0.3",
6471
"@semantic-release/github": "^10.3.3",

0 commit comments

Comments
 (0)