Skip to content

fix(🍏): don't talk to the GPU when the app is in the background - #580

Merged
wcandillon merged 5 commits into
mainfrom
background-gpu-work
Nov 2, 2026
Merged

wcandillon merged 5 commits into
mainfrom
background-gpu-work

Conversation

@wcandillon

@wcandillon wcandillon commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

iOS refuses GPU work from an app in the background, and Dawn 157 turns the refused Metal command buffer into a device loss (wcandillon/react-native-skia#4290). This PR enables Dawn's metal_suspend_submissions_in_background device toggle on every device requestDevice creates on Apple platforms: Dawn applies it on iOS and tvOS only, drops the name when it does not know it, and the caller opts out through dawnToggles.disabledToggles. A device imported from Skia already carries it.

The toggle ships in react-native-webgpu-dawn 157.1.0, built by react-native-skia from its Dawn patch (scripts/dawn-patches/metal-suspend-submissions-in-background.patch, wcandillon/react-native-skia#4373). Under it, Dawn commits the pending work and waits for it to be scheduled when the app enters the background (Apple's guidance), then encodes every submission without committing it, drops the presents, and commits the held work when the app comes back. Nothing is lost, reordered or refused; a promise on held work settles once the app is back, from the waiter thread that sleeps on it.

The gate this branch first put in front of Dawn (GpuWorkGate) is gone: Dawn commits from inside WaitAny and ProcessEvents too, so only a hold inside Dawn sees every commit, and a drain from outside raced with the hold the same notification sets inside Dawn.

The bump to react-native-webgpu-dawn 157.1.0 is part of the PR (published by wcandillon/react-native-skia run 41422364356). The example's react-native-skia 3.7.0 pins 157.0.0 exactly, so the workspace resolves the package to 157.1.0 for both until a react-native-skia release depends on it, as react-native-skia did at the 157.0.0 bump (wcandillon/react-native-skia#4268, dropped in #4271).

Verification (iPhone 17 simulator, Dawn 157.1.0 from that run's artifact): the full e2e suite passes (45 suites, 231 tests); a submit loop on a navigator.gpu device driven over the inspector runs at about 65 iterations per second, holds at zero while the app is in the background and resumes on return, with no error and no device loss.

RuntimeContext takes main's side: #598 replaced the ProcessEvents pump the
gate paused with a waiter thread, so the tick hold has no place any more,
and onSubmittedWorkDone keeps main's WaitAnyOnly registration, which
returns the future the waiter sleeps on.
iOS refuses GPU work from an app in the background, and Dawn 157 turns the
refused Metal command buffer into a device loss. The gate this branch put
in front of Dawn (GpuWorkGate) could not see every commit: Dawn commits
from inside WaitAny and ProcessEvents too, and a drain from outside races
with the hold the same notification sets inside Dawn. react-native-skia
now ships a Dawn with a device toggle for exactly this,
metal_suspend_submissions_in_background (react-native-webgpu-dawn 157.1.0,
scripts/dawn-patches in react-native-skia): while the app is in the
background Dawn encodes the submissions but commits nothing, drops the
presents, and commits the held work when the app comes back; on entering
the background the pending work is committed and waited on to be
scheduled, as Apple asks. Nothing is lost, reordered or refused, and a
promise on held work settles once the app is back, from the waiter thread
that sleeps on it.

Every device requestDevice makes on Apple platforms asks for the toggle
(Dawn applies it on iOS and tvOS only, a Dawn without it drops the name),
unless the caller disables it through dawnToggles. A device imported from
Skia already has it. The gate, its drain, the held call sites and the
pump hold are gone; the docs describe the hold and the Dawn version it
needs.
react-native-webgpu-dawn 157.1.0 is the same Dawn commit (ada567700e)
with react-native-skia's hold of command buffer commits while the app is
in the background, behind the metal_suspend_submissions_in_background
toggle requestDevice now asks for (published by react-native-skia's
binaries workflow, run 41422364356).

The example installs react-native-skia 3.7.0, which pins
react-native-webgpu-dawn 157.0.0 exactly; its podspec and Gradle refuse an
app whose two packages resolve different versions. Until a react-native-skia
release depends on 157.1.0 (its binaries link unchanged against it), the
workspace resolves the package to 157.1.0 for both. Drop the resolution
when the example moves to that release.
@wcandillon
wcandillon marked this pull request as ready for review November 2, 2026 11:33
…fest

The manifest refused a react-native-skia whose package.json pins another
react-native-webgpu-dawn than this package, which fails the SwiftPM example
on every Dawn bump until a react-native-skia release pins the new version:
here 157.1.0 against react-native-skia 3.7.0's 157.0.0, although the
workspace resolves the package to 157.1.0 for both and the app links one
Dawn. The podspec compares the versions the two packages resolve, not the
ones they declare; the manifest now does the same: the copy nested under
react-native-skia when the package manager could not hoist it, else the one
next to it. A nested copy of another version still fails the evaluation,
with both the resolved and the pinned version in the message.

Verified with `swift package --manifest-cache none dump-package`: the old
manifest fails on this tree, the new one passes, and a nested 157.0.0 under
react-native-skia fails it; the SwiftPM example builds for the simulator.
@wcandillon
wcandillon merged commit 939a94c into main Nov 2, 2026
8 of 9 checks passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 0.20.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

wcandillon added a commit that referenced this pull request Nov 2, 2026
….0 (#605)

* chore(⬆️): move the example and the docs app to react-native-skia 3.9.0

react-native-skia 3.9.0 depends on react-native-webgpu-dawn 157.1.0, the
version this package pins since #580, so the workspace no longer needs to
resolve the package for both: the example resolves one Dawn on its own.
The compatibility table gets the 0.20.0 / 3.9.0 row.

Verified on the iPhone 17 simulator: pod install reports one Dawn
(157.1.0) without the resolution, check-dawn and the Swift manifest pass,
the example builds and the full e2e suite passes (45 suites, 231 tests).

* fix(🧠): await the MNIST demo's pixel readback

react-native-skia 3.9 reads pixels asynchronously: readPixels() returns a
promise, which settles on the runtime that asked, the UI runtime here.
The inference is scheduled from its callback, as it was from the value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant