Native APIs
Integrations with the native platforms
On Android and iOS, you will find platform differences from the Web. Where a platform forces a difference, we re-surface it rather than hide it.
The most substantial differences live on the Canvas API: namely the manual frame presentation, and transparency handling.
Threading model
You can run WebGPU code on the main JS thread, on the UI thread, or on a dedicated worklet runtime (createWorkletRuntime, or a Vision Camera frame processor). Running off the JS thread is powered by React Native Worklets; Reanimated is not required. It also works with Worklets' Bundle Mode, which lets you run complex Three.js scenes off the JS thread.
The API is the same on every thread. The typical pattern is to create the device on the main JS thread and hand it to a worklet, where you call installWebGPU() once to install the WebGPU globals on that runtime:
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 ...
};Dawn's implicit device synchronization, enabled by default, lets a device be used from several threads, with two rules: never share a command encoder across threads, and drive each canvas context from a single runtime. See Thread safety.
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.
See Worklets for the full integration guide.
Loading images
This module provides a createImageBitmap function you can use with copyExternalImageToTexture.
Resolve a bundled asset to a URI, fetch it, and decode the blob.
In React Native createImageBitmap also accepts an ArrayBuffer as input.
const url = Image.resolveAssetSource(require("./assets/image.png")).uri;
const response = await fetch(url);
const imageBitmap = await createImageBitmap(await response.blob());
device.queue.copyExternalImageToTexture(
{ source: imageBitmap },
{ texture },
[imageBitmap.width, imageBitmap.height],
);See copyExternalImageToTexture on MDN.
The same call also takes a native video or camera frame as its source.
High bit depth
By default a canvas is configured with an 8-bit format (bgra8unorm or rgba8unorm, the value returned by navigator.gpu.getPreferredCanvasFormat() depending on the platform).
With only 256 levels per channel, subtle gradients quantize into visible bands, especially in dark tones and on OLED displays.
To configure the canvas with a higher-precision format, use getPreferredHighBitDepthCanvasFormat() (currently it will return rgba16float on iOS and rgb10a2unorm on Android):
// "rgba16float" on iOS, "rgb10a2unorm" on Android
const = ();
.({ , , : "opaque" });Native textures
Beyond the standard API, react-native-webgpu exposes Dawn's native buffer import, so you can sample camera and video frames without a copy through the CPU:
importSharedTextureMemoryimports a native pixel surface (anIOSurface-backedCVPixelBufferon iOS, anAHardwareBufferon Android) as a sampleableGPUTextureviadevice.importSharedTextureMemory(...).importExternalTextureis the higher-level path:device.importExternalTexture(...)hands you aGPUExternalTexturewith hardware YUV→RGB color conversion and lifecycle management, plus non-specrotationandmirroredoptions for camera orientation.createVideoPlayerdecodes a video into such frames, with the playback controls of anHTMLMediaElement.
Nothing has to be requested: requestDevice() enables the Dawn features behind native frame import whenever the adapter supports them. On the rare drivers and emulators that cannot import native surfaces, the import throws an error naming what is missing; catch it to degrade.
A device created by another library and wrapped with importDevice() has to request these Dawn features itself: shared-texture-memory-iosurface, shared-fence-mtl-shared-event and dawn-multi-planar-formats on Apple platforms; shared-texture-memory-ahardware-buffer, shared-fence-sync-fd and opaque-ycbcr-android-for-external-texture on Android.
See Vision Camera for an end-to-end example applying WGSL effects to live camera frames, and Native Extensions for the full API.
Importing an external device
importDevice() wraps a GPU device created by another native library into a regular GPUDevice. The device handle is passed as a BigInt, and the original owner keeps its own reference.
import { importDevice } from "react-native-webgpu";
const device = importDevice(pointer);This only works with a library that links the same Dawn build as react-native-webgpu. The main one is React Native Skia (v3 and above), which shares react-native-webgpu's Dawn.
Rendering to a native window
RNWebGPU.MakeWebGPUCanvasContextFromNativeWindow() makes a canvas context that presents to a native window other than a Canvas, for instance the input Surface of a MediaCodec encoder on Android. The window is an ANativeWindow* on Android or a CAMetalLayer* on Apple platforms, passed as a BigInt; width and height size the drawing buffer, like canvas.width and canvas.height. The context retains the window for as long as it lives, so the pointer stays valid; whatever the window belongs to (the encoder, the layer's view) is yours to keep alive while you present to it. Unlike a Canvas, nothing drives the context for you: call it from one thread at a time.
const context = RNWebGPU.MakeWebGPUCanvasContextFromNativeWindow(
window,
width,
height,
);
context.configure({
device,
format: navigator.gpu.getPreferredCanvasFormat(),
});
// For each frame
const texture = context.getCurrentTexture();
// ...render into texture and submit...
context.present();
// Once done. On Android, this also hands the window its buffers back right
// away: a MediaCodec input surface keeps them until the surface is gone,
// which would otherwise wait for the context to be garbage collected. The
// context can be configured again.
context.unconfigure();Each present() queues the frame to the window once the GPU is done rendering it. On Android, to give an encoder the presentation time of each frame, set it on the window right before present(), as eglPresentationTimeANDROID does with EGL: the window keeps it for the next frame queued.
See also
- Canvas - the rendering surface and frame loop
- React APIs - hooks and the device provider
- Worklets - UI-thread rendering
- Native Extensions - native interop on
GPUDevice