Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions apps/docs/content/api/gpu-device-extensions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ const device = await adapter.requestDevice({

On Android, the `ignore_imported_ahardwarebuffer_vulkan_image_size` toggle is enabled by default, so native buffers import on every driver. Pass it in `disabledToggles` to turn it off.

On iOS and tvOS, the `metal_suspend_submissions_in_background` toggle is enabled by default, so GPU work submitted while the app is in the background is held rather than refused (see [Background and foreground](/docs/getting-started/native-api#background-and-foreground)). Pass it in `disabledToggles` to turn it off.

## implicitDeviceSynchronization

By default, `requestDevice()` enables Dawn's [`implicit-device-synchronization`](https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/implicit_device_synchronization.md) feature, which lets a device created on the JS thread be used from the UI thread or a dedicated worklet runtime (see [Thread safety](/docs/integrations/worklets#thread-safety)).
Expand Down
11 changes: 11 additions & 0 deletions apps/docs/content/docs/getting-started/native-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ Dawn's implicit device synchronization, enabled by default, lets a device be use

See [Worklets](/docs/integrations/worklets) for the full integration guide.

## Background and foreground

iOS refuses GPU work from an app in the background, including work still queued when the app got there, and Dawn loses the device over a refused command buffer.
React Native WebGPU holds that work instead, with Dawn's `metal_suspend_submissions_in_background` toggle, enabled on every device it creates on iOS and tvOS: when the app goes to the background, what was submitted is committed before the app is suspended, and from then on every submission is encoded but not committed, in order, until the app comes back to the foreground.
Presenting a canvas does nothing meanwhile; the canvas catches up on return.
Nothing changes for your code: a held `onSubmittedWorkDone()` or `mapAsync()` promise settles once the app is back.
A render loop should still pause while the app is in the background (React Native's `AppState`), if only to spare the battery: what it submits meanwhile is kept in memory until the app returns.
Android and macOS do not refuse GPU work from a background app, so nothing is held there.
The hold needs `react-native-webgpu-dawn` 157.1.0 or later; an older Dawn drops the toggle, and the device is lost over the first refused command buffer (its `lost` promise then resolves with the reason `unknown`).
Pass the toggle in `disabledToggles` (see [dawnToggles](/docs/api/gpu-device-extensions#dawntoggles)) to opt out.

## Loading images

This module provides a `createImageBitmap` function you can use with `copyExternalImageToTexture`.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"resolutions": {
"@types/react": "^18.2.44",
"@webgpu/types": "^0.1.42",
"react-native-webgpu-dawn": "157.1.0",
"@react-three/fiber@^9.4.0": "patch:@react-three/fiber@npm%3A9.4.0#./.yarn/patches/@react-three-fiber-npm-9.4.0-77fe8ee9bd.patch"
},
"packageManager": "yarn@3.6.1",
Expand Down
5 changes: 4 additions & 1 deletion packages/webgpu/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ sibling in `node_modules`, or this monorepo's root) and links its
headers, so no header search path is needed for them. A Graphite build of
react-native-skia installed alongside must link the same Dawn, which the
manifest checks at evaluation time: a react-native-skia that depends on
`react-native-webgpu-dawn` must pin the same version, and the 3.0.x line, which
`react-native-webgpu-dawn` must resolve the same version (the copy nested under
it, else the one next to it, as its podspec resolves it; a workspace that
resolves the package to one version for both passes while react-native-skia's
own pin lags behind a Dawn bump), and the 3.0.x line, which
vendors its own Dawn, must record this package's `dawn` tag at
`react-native-skia-graphite-apple-ios/libs/.dawn-version` (hoisted or nested
under `react-native-skia/node_modules`, also checked for macOS and for the v2
Expand Down
26 changes: 19 additions & 7 deletions packages/webgpu/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,33 @@ let dawnVersion =
// changing either package's Dawn reset the package caches (File > Packages >
// Reset Package Caches) if this does not re-run.
//
// A react-native-skia that depends on react-native-webgpu-dawn itself must pin
// the same version as this package. Older Graphite builds (react-native-skia
// 3.0.x) vendor their own Dawn and record its release tag at
// libs/.dawn-version in their binary packages; this package's `dawn` field
// A react-native-skia that depends on react-native-webgpu-dawn itself must
// resolve the same version as this package: the copy nested under it when the
// package manager could not hoist it, else the one next to it (Node's
// resolution, which react-native-skia's podspec compares too). A workspace
// that resolves the package to one version for both (yarn `resolutions`, npm
// `overrides`) passes while react-native-skia's own pin lags behind a Dawn
// bump: the app then contains that one Dawn. Older Graphite builds
// (react-native-skia 3.0.x) vendor their own Dawn and record its release tag
// at libs/.dawn-version in their binary packages; this package's `dawn` field
// names the release that Dawn is interchangeable with.
if let skiaPath = nodePackage("react-native-skia"),
let skiaPackage = readJSON("\(skiaPath)/package.json"),
let skiaDawn = (skiaPackage["dependencies"] as? [String: String])?["react-native-webgpu-dawn"]
let skiaDawnPin = (skiaPackage["dependencies"] as? [String: String])?["react-native-webgpu-dawn"]
{
let skiaDawnPath = [
"\(skiaPath)/node_modules/react-native-webgpu-dawn",
"\(skiaPath)/../react-native-webgpu-dawn",
].first { FileManager.default.fileExists(atPath: "\($0)/package.json") }
let skiaDawn =
skiaDawnPath.flatMap { readJSON("\($0)/package.json")?["version"] as? String } ?? skiaDawnPin
if skiaDawn != dawnVersion {
fatalError(
"""
react-native-webgpu: Dawn version mismatch. This package links \
react-native-webgpu-dawn \(dawnVersion) but react-native-skia depends on \
\(skiaDawn). Align the two packages so the app contains exactly one Dawn.
react-native-webgpu-dawn \(dawnVersion) but react-native-skia resolves \
\(skiaDawn) (it depends on \(skiaDawnPin)). Align the two packages so the \
app contains exactly one Dawn.
""")
}
} else {
Expand Down
26 changes: 26 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,32 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
enabledToggles.push_back(name);
}
}
#endif
#if defined(__APPLE__)
// iOS refuses GPU work from an app in the background, and Dawn loses
// the device over a refused command buffer. With this toggle Dawn
// holds the commits while the app is in the background, from
// UIApplication's own notifications, and commits them when it comes
// back: nothing is lost, nothing is refused, and a promise on the
// held work settles once the app is back (the same toggle Skia
// requests on the device it shares, see react-native-skia's
// DawnUtils::requestDevice). Dawn applies it on iOS and tvOS only, and
// a Dawn without it (react-native-webgpu-dawn before 157.1.0) drops
// the name. The caller opts out through disabledToggles.
{
static const char *const kName =
"metal_suspend_submissions_in_background";
const auto named = [](const char *t) {
return std::strcmp(t, kName) == 0;
};
const bool explicitlyDisabled = std::any_of(
disabledToggles.begin(), disabledToggles.end(), named);
const bool alreadyEnabled =
std::any_of(enabledToggles.begin(), enabledToggles.end(), named);
if (!explicitlyDisabled && !alreadyEnabled) {
enabledToggles.push_back(kName);
}
}
#endif
if (!enabledToggles.empty() || !disabledToggles.empty()) {
toggles.enabledToggleCount = enabledToggles.size();
Expand Down
2 changes: 1 addition & 1 deletion packages/webgpu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"yargs": "^17.7.2"
},
"dependencies": {
"react-native-webgpu-dawn": "157.0.0"
"react-native-webgpu-dawn": "157.1.0"
},
"peerDependencies": {
"react": "*",
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20880,10 +20880,10 @@ __metadata:
languageName: node
linkType: hard

"react-native-webgpu-dawn@npm:157.0.0":
version: 157.0.0
resolution: "react-native-webgpu-dawn@npm:157.0.0"
checksum: 2fbebac05e083a4d0ecd258e459ddb6a41c1755f1720b9361a43af31c37d8e67aac60904f2731535b52e9fb19dfb0ced05d3011861635786a449e8a3835cbb20
"react-native-webgpu-dawn@npm:157.1.0":
version: 157.1.0
resolution: "react-native-webgpu-dawn@npm:157.1.0"
checksum: c938c4d0c1f1b5bc9eb8b8f2f3580df1babb7e02151262a3ff1cd4dfe4954c7ee6e56408403e715730eed5b00c33189095639f990befe9ad759632c82605ed1a
languageName: node
linkType: hard

Expand Down Expand Up @@ -20948,7 +20948,7 @@ __metadata:
react-native-builder-bob: ^0.23.2
react-native-reanimated: 4.6.0
react-native-web: ^0.21.2
react-native-webgpu-dawn: 157.0.0
react-native-webgpu-dawn: 157.1.0
react-native-worklets: 0.12.2
rimraf: ^5.0.7
seedrandom: ^3.0.5
Expand Down
Loading