Skip to content

fix(🍏): hold GPU work while the app is in the background - #4373

Open
wcandillon wants to merge 2 commits into
mainfrom
background-submissions
Open

wcandillon wants to merge 2 commits into
mainfrom
background-submissions

Conversation

@wcandillon

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 (#4290): every canvas froze until the device was replaced, and everything made on it had to be made again.

A Dawn patch (scripts/dawn-patches/metal-suspend-submissions-in-background, to ship with the next react-native-webgpu-dawn) adds a device toggle under which Dawn holds the commit of command buffers while the app is in the background, from UIApplication's own notifications: the work is encoded, committed when the app comes back, and presents meanwhile are dropped. On entering the background the pending work is committed and waited on to be scheduled, as Apple asks. Nothing is lost, reordered or refused, so Dawn's bookkeeping, Graphite's completion tracking and JS promises stay right, which neither ignoring the refusal (Dawn PR 91, a glyph atlas page refused would stay garbage) nor rejecting the submit (Graphite ignores the result) could offer. Dawn commits from inside WaitAny too, so only a gate inside Dawn sees every commit. Every device Skia makes requests the toggle; a Dawn without it drops the name. The secondary device enables ImplicitDeviceSynchronization: each device has a lock of its own, and the hold is only atomic against an in-flight submit through it.

Held frames keep what they use alive until the return, so a producer that keeps drawing in the background (a video player under background audio) should stop: Skia.gpu.foreground says whether the app is in the foreground and active, synchronously on every runtime, from
UIApplicationWillResignActive to UIApplicationDidBecomeActive (the host activity's resume and pause on Android, the document's visibility on the web). Documented in webgpu.md.

Verification: Dawn white-box tests for the hold (held work completes once resumed, pending work is committed on suspend, a waiter thread wakes on resume, MapAsync resolves once resumed, repeated calls and a destroyed device) pass on macOS with and without implicit device synchronization, and a regression subset of the queue and buffer tests passes; the iOS-only observer compiles against the iPhoneOS SDK. The touched headers and RNSkApplePlatformContext.mm pass a clang syntax check for the iOS simulator; tsc, eslint, cpplint and clang-format are clean; the Gpu unit tests pass. Not run on a device: the simulator does not enforce the rule, and the toggle does nothing until react-native-webgpu-dawn ships the patch.

… when it is

iOS refuses GPU work from an app in the background, and Dawn 157 turns the
refused Metal command buffer into a device loss (#4290): every canvas
froze until the device was replaced, and everything made on it had to be
made again.

A Dawn patch (scripts/dawn-patches/metal-suspend-submissions-in-background,
to ship with the next react-native-webgpu-dawn) adds a device toggle under
which Dawn holds the commit of command buffers while the app is in the
background, from UIApplication's own notifications: the work is encoded,
committed when the app comes back, and presents meanwhile are dropped. On
entering the background the pending work is committed and waited on to be
scheduled, as Apple asks. Nothing is lost, reordered or refused, so Dawn's
bookkeeping, Graphite's completion tracking and JS promises stay right,
which neither ignoring the refusal (Dawn PR 91, a glyph atlas page refused
would stay garbage) nor rejecting the submit (Graphite ignores the result)
could offer. Dawn commits from inside WaitAny too, so only a gate inside
Dawn sees every commit. Every device Skia makes requests the toggle; a Dawn
without it drops the name. The secondary device enables
ImplicitDeviceSynchronization: each device has a lock of its own, and the
hold is only atomic against an in-flight submit through it.

Held frames keep what they use alive until the return, so a producer that
keeps drawing in the background (a video player under background audio)
should stop: Skia.gpu.foreground says whether the app is in the foreground
and active, synchronously on every runtime, from
UIApplicationWillResignActive to UIApplicationDidBecomeActive (the host
activity's resume and pause on Android, the document's visibility on the
web). Documented in webgpu.md.

Verification: Dawn white-box tests for the hold (held work completes once
resumed, pending work is committed on suspend, a waiter thread wakes on
resume, MapAsync resolves once resumed, repeated calls and a destroyed
device) pass on macOS with and without implicit device synchronization, and
a regression subset of the queue and buffer tests passes; the iOS-only
observer compiles against the iPhoneOS SDK. The touched headers and
RNSkApplePlatformContext.mm pass a clang syntax check for the iOS
simulator; tsc, eslint, cpplint and clang-format are clean; the Gpu unit
tests pass. Not run on a device: the simulator does not enforce the rule,
and the toggle does nothing until react-native-webgpu-dawn ships the patch.
@wcandillon
wcandillon marked this pull request as ready for review October 30, 2026 12:44

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant