React Native WebGPU

Native Extensions

Native interop extensions to GPUDevice and the WebGPU globals.

React Native WebGPU adds a handful of native extensions on top of the WebGPU spec.

importSharedTextureMemory

Imports a native buffer (IOSurface / AHardwareBuffer) for zero-copy sampling. Nothing has to be requested: requestDevice() enables the Dawn features behind native frame import whenever the adapter supports them (see Native textures).

const  = await .();

// frame from createVideoPlayer(..., "bgra8") or Vision Camera
const  = .({
  : .,
  : "camera-frame",
});

const  = .();

.(, /* initialized */ true);
// sample texture in render pass…
.();

.();
.();

Use beginAccess(texture, true) when the producer has already written pixels (typical for video/camera).

importSharedFence

Import native GPU sync fences for producer/consumer handoff:

// Producer side: export a fence after GPU work
const exported = sharedFence.export();

// Consumer side: import on another device or queue
const imported = device.importSharedFence({
  handle: exported.handle,
  label: "frame-ready",
});

// Wait for producer before sampling imported texture
imported.wait(0);
// … render using imported texture …
imported.signal(0);
imported.destroy();

See packages/webgpu/src/__tests__/SharedFence.spec.ts for a full producer/consumer test.

dawnToggles

Non-standard Dawn device toggles at creation:

const  = await .({
  : {
    : ["allow_unsafe_apis"],
    : ["use_dxc"],
  },
});

Prop

Type

implicitDeviceSynchronization

Dawn's implicit-device-synchronization 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). 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.

const  = await .({
  : false,
});
..("implicit-device-synchronization" as ); // false

The flag only controls the default injection: listing "implicit-device-synchronization" in requiredFeatures still enables the feature.

GPUExternalTexture extensions

Field / MethodDescription
sourceA NativeVideoFrame (from a video player, a camera buffer or createTestVideoFrame) instead of the web's HTMLVideoElement / VideoFrame
rotation0, 90, 180, or 270 degrees (camera orientation)
mirroredHorizontal flip for front camera
destroy()Release native surface access after the queue.submit() that sampled it

Usage with importExternalTexture (NV12 camera/video):

const externalTexture = device.importExternalTexture({
  source: nativeVideoFrame,
});

// Read orientation metadata in bind group / shader setup
console.log(externalTexture.rotation, externalTexture.mirrored);

// After submit that sampled the texture:
externalTexture.destroy();
nativeVideoFrame.release();

On Android, a YUV frame (what the video decoder and the camera produce) needs the opaque-ycbcr-android-for-external-texture device feature. requestDevice() enables it whenever the adapter supports it; a device created by another library and wrapped with importDevice() has to request it itself, as react-native-skia's does. The same applies to copyExternalImageToTexture() with a native frame.

copyExternalImageToTexture with a native frame

queue.copyExternalImageToTexture() also accepts a NativeVideoFrame as its source, the way the web API accepts a VideoFrame. The frame is rendered into the destination on the GPU through an external texture, so YUV frames come out as RGB, and the non-spec rotation and mirrored options of importExternalTexture apply. The destination needs GPUTextureUsage.RENDER_ATTACHMENT and an rgba8unorm, bgra8unorm (or -srgb), rgba16float or rgb10a2unorm format; the frame can be released as soon as the call returns.

const  = .({
  : [., .],
  : "rgba8unorm",
  : . | .,
});
..(
  { : ,  },
  {  },
  [., .],
);
.();

origin, flipY and the copy size work as on the web, in the upright frame: a frame rotated by 90 or 270 degrees has its width and height swapped. This is the path to hand a video or camera frame to a library that samples regular textures, such as React Native Skia; a shader of your own can sample the frame directly with importExternalTexture() and skip the copy.

Prop

Type

createImageBitmap

createImageBitmap mirrors its Web counterpart and also accepts a Uint8Array or ArrayBuffer as input.

const  = await ().(() => .());
const  = await ();

..(
  { :  },
  {  },
  { : ., : . },
);
.();

getPreferredHighBitDepthCanvasFormat

Returns the canvas texture format most likely to reach the display with more than 8 bits per channel on the current platform: "rgba16float" on iOS, "rgb10a2unorm" on Android. Unlike getPreferredCanvasFormat(), this helper is not part of the WebGPU standard, so it is exported as a regular function instead of a navigator.gpu method.

const  = ();
.({ , , : "opaque" });

The values you write stay sRGB-encoded exactly like on an 8-bit canvas: this is about precision, not HDR (no extended range). See High bit depth for the platform details and banding comparison.