Skip to content
Open
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: 1 addition & 1 deletion apps/docs/content/api/canvas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ context.present(); // React Native only

`present()` is a React Native only method (it is not part of the Web WebGPU spec). It runs **synchronously** on the calling thread, so the frame is presented from whichever thread did the rendering. This works the same on every runtime: the main JS runtime, the UI thread, and dedicated worklet runtimes (`createWorkletRuntime` / `runOnRuntime`, or a Vision Camera frame processor).

Always call `present()` after `submit()`, never before. See [Frame presentations](/docs/getting-started/canvas#frame-presentations) for the full frame loop and [Native APIs](/docs/getting-started/native-api#threading-model) for threading details.
Always call `present()` after `submit()`, never before. See [Frame presentations](/docs/getting-started/canvas#frame-presentations) for the full frame loop and [Threading](/docs/getting-started/threading) for running the loop off the JS thread.

## Pixel density

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/api/gpu-device-extensions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const device = await adapter.requestDevice({

## implicitDeviceSynchronization

Dawn's [`implicit-device-synchronization`](https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/implicit_device_synchronization.md) feature is requested by default by `requestDevice()`, so a device created on the JS thread can be used from the UI thread or a dedicated worklet runtime (see [Threading model](/docs/getting-started/native-api#threading-model)). Dawn wraps the device and child-object entry points in a per-device mutex; command encoding is not covered, so a `GPUCommandEncoder` must stay on the thread that created it.
Dawn's [`implicit-device-synchronization`](https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/implicit_device_synchronization.md) feature is requested by default by `requestDevice()`, so a device created on the JS thread can be used from the UI runtime or a dedicated worklet runtime (see [Threading](/docs/getting-started/threading)). Dawn wraps the device and child-object entry points in a per-device mutex; command encoding is not covered, so a `GPUCommandEncoder` must stay on the thread that created it.

Set `implicitDeviceSynchronization: false` on the device descriptor to opt out and skip the per-call locking. Only do this for a device that is never touched from another runtime. The flag is a non-standard extension and is ignored on the web.

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/api/gpu-device-provider.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,5 @@ function DebugOverlay() {
## See also

- [React APIs](/docs/getting-started/react-api) - hooks for requesting and sharing a device
- [Native APIs](/docs/getting-started/native-api#threading-model) - threading and `device.lost`
- [Threading](/docs/getting-started/threading) - threading and `device.lost`
- [Native Extensions](/api/gpu-device-extensions) - camera and video import
12 changes: 6 additions & 6 deletions apps/docs/content/api/install-webgpu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ title: installWebGPU
description: Install navigator.gpu and the WebGPU flag constants on a worklet runtime.
---

On a worklet runtime, neither `navigator.gpu` nor the WebGPU flag constants are available by default.
Call `installWebGPU()` once at the top of a worklet to install the needed globals for that runtime: `navigator.gpu` plus `GPUBufferUsage`, `GPUTextureUsage`, `GPUShaderStage`, `GPUColorWrite`, and `GPUMapMode`.
On a worklet runtime, neither `navigator.gpu`, nor the `RNWebGPU` object, nor the WebGPU flag constants are available by default.
Call `installWebGPU()` once at the top of a worklet to install the needed globals for that runtime: `navigator.gpu`, `RNWebGPU` (for the native helpers such as `createVideoFrameFromNativeBuffer`), plus `GPUBufferUsage`, `GPUTextureUsage`, `GPUShaderStage`, `GPUColorWrite`, and `GPUMapMode`.

On the main JS thread these are already installed when the native module loads; calling `installWebGPU()` there is a safe no-op.
On the JS thread these are already installed when the native module loads; calling `installWebGPU()` there is a safe no-op. See [Threading](/docs/getting-started/threading) for the rules on what runs where.

## Usage

Expand Down Expand Up @@ -46,7 +46,7 @@ const renderOnUI = (
context.present();
};

// Create device on the main thread, pass handles to the worklet
// Create the device on the JS thread, pass the handles to the worklet
runOnUI(renderOnUI)(device, context, pipeline);
```

Expand Down Expand Up @@ -96,8 +96,8 @@ runOnUI(() => {
On `react-native-worklets` versions before 0.13.0, write `globalThis.navigator` instead of a bare `navigator`. Older versions of the Worklets babel plugin capture the main runtime's `navigator` object by closure instead of reading the one installed on the worklet runtime.
</Callout>

If you create a device on the worklet runtime, beware that spontaneous device events are only delivered for devices created on the main JS runtime:
Creating a device on a worklet runtime works, but is not recommended: spontaneous device events are only delivered for devices created on the main JS runtime.
- `device.lost` read on a worklet runtime returns a promise that never settles, unless the device is already lost at that point, in which case it resolves normally.
- `uncapturederror` listeners registered on a device created on a worklet runtime never fire.

If you need to observe device loss or uncaptured errors, create the device on the main JS thread, attach the handlers there, and pass the device into the worklet.
Create the device on the JS thread, attach the handlers there, and pass the device into the worklet. Request-style promises (`requestAdapter`, `requestDevice`, `mapAsync`, ...) always settle on the runtime that called them.
2 changes: 1 addition & 1 deletion apps/docs/content/docs/getting-started/canvas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ device.queue.submit([encoder.finish()]);
context.present(); // React Native only
```

`present()` is a React Native only method (it is not part of the Web WebGPU spec). It runs **synchronously** on the calling thread, so the frame is presented from whichever thread did the rendering. This works the same on every runtime: the main JS runtime, the UI thread, and dedicated worklet runtimes. Always call it after `submit()`, never before.
`present()` is a React Native only method (it is not part of the Web WebGPU spec). It runs **synchronously** on the calling thread, so the frame is presented from whichever runtime did the rendering: the JS thread, the UI runtime, or a dedicated worklet runtime (see [Threading](/docs/getting-started/threading)). Always call it after `submit()`, never before.

## Pixel density

Expand Down
3 changes: 2 additions & 1 deletion apps/docs/content/docs/getting-started/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ npm i react-native-webgpu
| Package | Version | Required |
| ------- | ------- | -------- |
| `react-native` | >= 0.81.0 | Yes |
| `react-native-worklets` | >= 0.7.2 | Optional - off-js-thread rendering |
| `react-native-worklets` | >= 0.7.2 | Optional - rendering off the JS thread ([Threading](/docs/getting-started/threading)) |

Please note that this module does not work on the legacy architecture.

Expand Down Expand Up @@ -101,5 +101,6 @@ Learn more in [Apple's documentation on validating Metal API usage](https://deve

- [Expo](/docs/getting-started/expo) - scaffold the hosted WebGPU examples template
- [Canvas](/docs/getting-started/canvas) - your rendering surface
- [Threading](/docs/getting-started/threading) - where your code runs, and how to move it off the JS thread
- [Native APIs](/docs/getting-started/native-api) - the React Native specific differences
- [Learn WebGPU](/docs/getting-started/learn-webgpu) - learn the low-level API
1 change: 1 addition & 0 deletions apps/docs/content/docs/getting-started/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"expo",
"canvas",
"offscreen-canvas",
"threading",
"native-api",
"react-api",
"learn-webgpu"
Expand Down
46 changes: 4 additions & 42 deletions apps/docs/content/docs/getting-started/native-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,14 @@ The most substantial differences live on the [Canvas](/docs/getting-started/canv

## Threading model

react-native-webgpu can drive WebGPU from more than one JavaScript runtime: the main JS runtime, the UI thread, and dedicated worklet runtimes (createWorkletRuntime / runOnRuntime, or a Vision Camera frame processor). Running off the JS thread is powered by [React Native Worklets](https://github.com/margelo/react-native-worklets); Reanimated is not required.
This module also works well with [Bundle Mode](https://docs.swmansion.com/react-native-worklets/docs/bundleMode/) and lets you run complex Three.js scenes on the UI thread or dedicated worklet threads.
Your WebGPU code runs on the JS thread by default. With [React Native Worklets](https://github.com/margelo/react-native-worklets) the same code can run on the UI runtime or on a dedicated worklet runtime instead; Reanimated is not required. The API is identical on every runtime, so you write the frame once and choose where it runs.

You can run WebGPU code on the main thread, on the [Worklets](/docs/integrations/worklets) UI thread, or on a dedicated worklet thread. The API is symmetric across all threads, so you can run your rendering loop wherever you want.
The recommended pattern is to mount the `Canvas`, get its context and create the device on the JS thread, then hand them to a worklet that encodes, submits and presents. [Threading](/docs/getting-started/threading) covers the rules, a complete worklet render loop, thread safety, and which runtime to pick.

<Callout type="warn" title="device.lost and uncapturederror">
`device.lost` and `uncapturederror` are only delivered on the **main JS runtime**. This is usually fine because the GPU device is typically created on the main JS thread and then sent to the UI or a dedicated worklet thread. If you create the device outside the main JS thread, those events won't fire.
`device.lost` and `uncapturederror` are only delivered for devices created on the JS thread. Create the device there and pass it to the worklet; a device created on a worklet runtime never reports these events.
</Callout>

See [Worklets](/docs/integrations/worklets) for the full UI-thread integration guide.
In a Worklet, call `installWebGPU()` to install the WebGPU API on that thread.

### Implicit device synchronization

Dawn's native API is not thread-safe on its own. To make the "create the device on the JS thread, render from a worklet" pattern safe, `requestDevice()` enables Dawn's [`implicit-device-synchronization`](https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/implicit_device_synchronization.md) feature by default. It guards the device and its child objects (buffers, textures, queue, pipelines) with a per-device mutex, so one device can be used from several runtimes.

This is coarse-grained locking, not a license for arbitrary concurrency. Command encoding is excluded from the synchronization, so never share a `GPUCommandEncoder` across threads, and keep a given canvas context on a single runtime.
The feature is reported in `device.features`, and it can be disabled per device with the non-standard `implicitDeviceSynchronization` descriptor flag when the device is only ever used from one thread:

```tsx twoslash
import type { RNCanvasContext } from "react-native-webgpu";
declare const adapter: GPUAdapter;
// ---cut---
// Single-thread only: never hand this device to a worklet runtime.
const device = await adapter.requestDevice({
implicitDeviceSynchronization: false,
});
```

See [Native Extensions](/api/gpu-device-extensions#implicitdevicesynchronization) for details.

```tsx
import { installWebGPU } from "react-native-webgpu";

const renderFrame = (device: GPUDevice, context: GPUCanvasContext) => {
"worklet";
installWebGPU();
// GPUBufferUsage, GPUTextureUsage, etc. are now available on this thread
const buffer = device.createBuffer({
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});
// ... render and present on this thread ...
};
```

## Loading images

This module provides a `createImageBitmap` function you can use with `copyExternalImageToTexture`.
Expand Down Expand Up @@ -124,5 +86,5 @@ This is only sound when the exporting library links the exact same Dawn build as

- [Canvas](/docs/getting-started/canvas) - the rendering surface and frame loop
- [React APIs](/docs/getting-started/react-api) - hooks and the device provider
- [Worklets](/docs/integrations/worklets) - UI-thread rendering
- [Threading](/docs/getting-started/threading) - running WebGPU off the JS thread
- [Native Extensions](/api/gpu-device-extensions) - native interop on `GPUDevice`
2 changes: 1 addition & 1 deletion apps/docs/content/docs/getting-started/react-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ warnIfNotHardwareAccelerated(adapter);

- [Canvas](/docs/getting-started/canvas) - the rendering surface and frame loop
- [Native APIs](/docs/getting-started/native-api) - the React Native specific differences
- [Worklets](/docs/integrations/worklets) - running these on the UI thread
- [Threading](/docs/getting-started/threading) - running the render loop off the JS thread
Loading