React Native Skia
Share one Dawn, its GPU device and textures with Skia's Graphite renderer.
react-native-webgpu and react-native-skia work alongside each other nicely.
Starting with v3, react-native-skia renders with Graphite, on top of Dawn, the same WebGPU implementation react-native-webgpu is built on, and the two packages share it:
- One Dawn, shared. The app contains a single copy of Dawn. Skia's device and every device you create through
navigator.gpulive side by side, so devices and textures can move between the two libraries without a copy. - The WebGPU API lives here. react-native-skia does not install
navigator.gpuor any WebGPU JS API. If you want to write WebGPU code in an app that uses Skia, install react-native-webgpu alongside it.
With react-native-skia v2 (published as @shopify/react-native-skia up to 2.14) there is no coupling at all: any version of either package can be installed together, and they simply don't interact.
Compatibility
Because the two packages share Dawn, react-native-skia v3 and react-native-webgpu must be built against the same Dawn release, which means installing them at matching versions. Pick one row and install versions from it:
| react-native-webgpu | react-native-skia |
|---|---|
| 0.13.1 and later | 3.4.0 and later, or 3.0.8 |
| 0.13.0 | 3.1.0 to 3.3.0, or 3.0.8 |
| 0.12.1 | 3.0.6 to 3.0.8, or 3.1.0 and later |
| 0.10.0 to 0.11.2 | 3.0.0 to 3.0.5 |
If the versions don't match, the native build fails with a Dawn version mismatch error rather than misbehaving at runtime. Both packages get Dawn from the react-native-webgpu-dawn npm package: make sure a single version of it is installed (npm ls react-native-webgpu-dawn, or yarn why react-native-webgpu-dawn).
The Graphite previews of v2 (@shopify/react-native-skia@next) follow the same rule. Regular v2 releases don't use Dawn, so they work alongside any version of react-native-webgpu.
Rendering with Skia's device
Skia.getNativeDevice() returns Skia's GPU device as a BigInt handle, and importDevice() wraps it into a regular GPUDevice:
import { Skia } from "react-native-skia";
import { Canvas, importDevice } from "react-native-webgpu";
const device = importDevice(Skia.getNativeDevice());
// Use it like any GPUDevice: create pipelines, submit work,
// render to a react-native-webgpu <Canvas />.Because the device is shared with Graphite, GPU resources you create with it live on the same device Skia renders with, which is the foundation for zero-copy interop between the two worlds.
A few things to know:
- Lifetime. The device is owned by Skia's Graphite context and is valid for the lifetime of the process.
importDevicetakes its own reference. - Device loss. Skia treats the loss of its device as fatal. Treat an imported device accordingly: there is no point requesting a replacement from
navigator.gpuon loss. - You can still create your own devices.
navigator.gpu.requestAdapter()andrequestDevice()work as usual; they coexist with Skia's device. Use a dedicated device when you want your GPU work isolated from Skia's command queue. - Threading. The imported device follows the same threading model as any other device. Skia submits its own work on the same device concurrently; Dawn synchronizes device access for you.
Zero-copy texture sharing
Textures cross the package boundary the same way the device does, without a copy.
WebGPU texture as a Skia image. Skia.Image.MakeImageFromGPUTexture wraps a GPUTexture of the shared device into an SkImage without copying. The image keeps its own reference, so the GPUTexture may be garbage collected but must not be destroyed while the image is in use:
const texture = device.createTexture({ ... });
// render into it with WebGPU, then draw it with Skia:
const image = Skia.Image.MakeImageFromGPUTexture(texture);Skia image as a WebGPU texture. Skia.Image.MakeGPUTextureFromImage uploads an SkImage into a texture on the shared device and returns a texture handle; adoptTexture() wraps it into a GPUTexture:
import { adoptTexture } from "react-native-webgpu";
const texture = adoptTexture(Skia.Image.MakeGPUTextureFromImage(image));
// sample it in a render pipeline via texture.createView()Adopt each handle exactly once: the returned GPUTexture owns it and releases it when destroyed.
Skia drawing into a WebGPU texture. Skia.Surface.MakeFromGPUTexture wraps a texture of the shared device into an SkSurface that draws straight into it. Create the texture with the RENDER_ATTACHMENT usage, plus TEXTURE_BINDING to sample the result:
const surface = Skia.Surface.MakeFromGPUTexture(texture);
surface.getCanvas().drawCircle(128, 128, 64, paint);
// Submit Skia's commands ahead of the WebGPU pass that samples the texture
surface.flush();The surface holds its own reference to the texture. Dispose it before calling texture.destroy().
Platform surfaces (CVPixelBuffer / AHardwareBuffer) stay on the WebGPU side: sample them through importSharedTextureMemory or importExternalTexture, or copy them into a texture for Skia as described below.
Video and camera frames
Starting with v3, react-native-skia has no video player and no native buffer API of its own: react-native-webgpu produces and owns the frames.
copyExternalImageToTexture renders a frame into a texture of the shared device, with the YUV conversion and the rotation done on the GPU, and Skia.Image.MakeImageFromGPUTexture wraps that texture into an SkImage:
import { createVideoPlayer, importDevice } from "react-native-webgpu";
const device = importDevice(Skia.getNativeDevice());
const player = createVideoPlayer(url);
player.play();
// on every frame:
const frame = player.copyLatestFrame();
if (frame) {
device.queue.copyExternalImageToTexture(
{ source: frame, rotation: player.rotation },
{ texture },
[texture.width, texture.height],
);
frame.release();
const image = Skia.Image.MakeImageFromGPUTexture(texture);
}This is the one path for video and camera frames on both platforms: Skia has no native buffer import of its own. The complete example, with the lifetime rules, is in the React Native Skia documentation.
See also
- WebGPU in the React Native Skia documentation - complete examples, including Three.js in both directions
- Native APIs -
importDeviceand the threading model - Native Extensions - native texture interop on
GPUDevice