A WebGPU port for react native to provide direct access to Metal and Vulkan for iOS and Android via the WebGPU api.
- Running the examples
- Getting started
- Converting a WebGPU sample
- Resizing WebGpuView
- Production usage
- Supported react-native versions
The best way to learn WebGPU is to check out the examples. You can find instructions to run the examples here.
- Install packages
yarn add react-native-webgpu
- Install pods
cd ios
pod install
cd ..
- Import
react-native-webgpu
in your rootindex.js
file so it can install on app startup
// src/index.js
import {install} from 'react-native-webgpu';
install();
- Add to
metro.config.js
to support.wgsl
files
// metro.config.js
const defaultConfig = getDefaultConfig(__dirname);
const webGpuConfig = require('react-native-webgpu/metro');
const config = {
resolver: {
sourceExts: [
...defaultConfig.resolver.sourceExts,
...webGpuConfig.resolver.sourceExts,
],
},
transformer: {
babelTransformerPath: require.resolve('react-native-webgpu/wgsl-babel-transformer'),
},
};
- TypeScript only, add global types to
tsconfig.json
{
"include": [
"node_modules/react-native-webgpu/lib/typescript/react-native-webgpu.d.ts"
]
}
The Android emulator does not support Vulkan unless your machine is capable of running it. It is recommended to develop using an Android device, but you can try a workaround if that's not available to you.
Expand for macOS workaround
If you're using a Mac and you need to run your app on an emulator you can try these experimental apis.
ANDROID_EMU_VK_ICD=moltenvk emulator "@My_AVD_Name"
- Either set the
backends
prop:
<WebGpuView backends={Platform.OS === android ? Backends.Vulkan : Backends.All} />
- Or set the default
backends
prop globally.
defaultBackends.current =
Platform.OS === 'android' ? Backends.Vulkan : Backends.All;
Please note, it's not safe to assume that the emulated backend will be identical to a real one. Be sure to test fully on devices before releasing to production.
There are a few small changes you will need to make to get your project working. Below is a simple example taken from WebGPU Samples. It has TODO:
s marking the places we need to change.
Expand to view the code
import triangleVertWGSL from '../../shaders/triangle.vert.wgsl';
import redFragWGSL from '../../shaders/red.frag.wgsl';
import { quitIfWebGPUNotAvailable } from '../util';
const canvas = document.querySelector('canvas') as HTMLCanvasElement; // TODO: Remove web api
const adapter = await navigator.gpu?.requestAdapter(); // TODO: Use the navigator from `react-native-webgpu` instead of `global`
const device = await adapter?.requestDevice();
quitIfWebGPUNotAvailable(adapter, device); // TODO: Remove since web gpu is always supported 🎉
const context = canvas.getContext('webgpu') as GPUCanvasContext; // TODO: Use the context from `react-native-webgpu`
const devicePixelRatio = window.devicePixelRatio; // TODO: Remove sizing as we use React to layout our views
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: 'premultiplied',
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
// TODO: `entryPoint` must be specified in `react-native-webgpu`
module: device.createShaderModule({
code: triangleVertWGSL,
}),
},
fragment: {
// TODO: `entryPoint` must be specified in `react-native-webgpu`
module: device.createShaderModule({
code: redFragWGSL,
}),
targets: [
{
format: presentationFormat,
},
],
},
primitive: {
topology: 'triangle-list',
},
});
function frame() {
const commandEncoder = device.createCommandEncoder();
const textureView = context.getCurrentTexture().createView();
const renderPassDescriptor: GPURenderPassDescriptor = {
colorAttachments: [
{
view: textureView,
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(pipeline);
passEncoder.draw(3);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
// TODO: We need to tell the surface to present itself onscreen
requestAnimationFrame(frame);
}
// TODO: Use `requestAnimationFrame` from `react-native-webgpu` so it is called in sync with the screen refresh rate, and automatically cancels on unmount
requestAnimationFrame(frame);
Here is a working (TypeScript) example. It has FIXED:
comments to show where the changes were made.
Expand to view the code
import React from 'react';
import { WebGpuView, type WebGpuViewProps } from 'react-native-webgpu';
import triangleVertWGSL from '../../shaders/triangle.vert.wgsl';
import redFragWGSL from '../../shaders/red.frag.wgsl';
export function HelloTriangle() {
// FIXED: get context, navigator and requestAnimationFrame from `react-native-webgpu` callback
const onCreateSurface: WebGpuViewProps['onCreateSurface'] = async ({context, navigator, requestAnimationFrame}) => {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter!.requestDevice();
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: "premultiplied",
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
// FIXED: The shader function in `triangleVertWGSL` is called `main` so that's our entry point
entryPoint: 'main',
module: device.createShaderModule({
code: triangleVertWGSL,
}),
},
fragment: {
// FIXED: The shader function in `redFragWGSL` is also called `main` so that's our entry point
entryPoint: 'main',
module: device.createShaderModule({
code: redFragWGSL,
}),
targets: [
{
format: presentationFormat,
},
],
},
primitive: {
topology: 'triangle-list',
},
});
function frame() {
// FIXED: `getCurrentTexture()` can return `null` in `react-native-webgpu`
const framebuffer = context.getCurrentTexture();
if (!framebuffer) {
requestAnimationFrame(frame);
return;
}
const commandEncoder = device.createCommandEncoder();
const textureView = framebuffer.createView();
const renderPassDescriptor: GPURenderPassDescriptor = {
colorAttachments: [
{
view: textureView,
clearValue: [0, 0, 0, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
}
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(pipeline);
passEncoder.draw(3);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
// FIXED: Add context.presentSurface() to display the surface
context.presentSurface();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};
return <WebGpuView onCreateSurface={onCreateSurface} style={{flex: 1}} />;
}
If you expect WebGpuView
to change size, you need to call context.configure()
whenever the size changes.
Expand to view the code
let previousWidth = context.width;
let previousHeight = context.height;
// ...
function frame() {
if (context.width !== previousWidth || context.height !== previousHeight) {
context.configure({device, format});
}
previousWidth = context.width;
previousHeight = context.height;
const framebuffer = context.getCurrentTexture(); // Now returns updated texture
// ...
}
If you want to smoothly change the size of WebGpuView
, set the pollSize
prop to true
. This only affects iOS and
polls the surface size every frame to ensure it is correct. Setting this to true
on Android has no effect because
animations are supported without polling the size.
<WebGpuView onCreateSurface={onCreateSurface} pollSize />
Like any third party code you introduce into your app, ensure that you thoroughly test on your supported platforms.
Running loop-based, resource-heavy code in JavaScript environments can be challenging. The library is profiled for memory usage, but you will need to test your app to make sure you're not accidentally introducing memory leaks.
Xcode Instruments and Android Studio Profiler are strongly recommended for profiling your app before releasing it to production.
The library is built and tested against 0.75 and 0.76. Other versions may work but are not supported.
react-native-webgpu | react-native | Hermes | JSC | New architecture | Old architecture |
---|---|---|---|---|---|
0.1.1 | 0.75-0.76 | ✅ | ❌ | ✅ | ✅ |