Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
04f6e00
Add web support
scottmas Jun 3, 2025
0ee0559
Merge branch 'main' into main
scottmas Jun 3, 2025
05e7ba9
Better zero height/width behavior and warning
scottmas Jun 4, 2025
a9fc71d
Merge branch 'main' into main
scottmas Jun 4, 2025
293fd99
Make web example not clutter
scottmas Jun 5, 2025
f24e5d7
Merge branch 'main' of github.com:scottmas/react-native-webgpu
scottmas Jun 5, 2025
fec88a3
More robust global type augmentation
scottmas Jun 5, 2025
63a2c45
Merge branch 'main' into main
scottmas Jun 5, 2025
2ce5e40
Merge branch 'main' into add-web-support
blazejkustra Oct 23, 2025
adc1857
Remove unrelated change
blazejkustra Oct 23, 2025
a73efac
Fix readme
blazejkustra Oct 23, 2025
ee44f3f
Clean the code
blazejkustra Oct 23, 2025
38ed84f
refactor: clean up metro.config.js
blazejkustra Oct 23, 2025
d467713
feat: bring back single Canvas file
blazejkustra Oct 23, 2025
6cb87e1
Implement global methods on web
blazejkustra Oct 23, 2025
f663f90
feat: bring back single impl for WebGPUModule
blazejkustra Oct 23, 2025
eb84ff0
Bring back old style
blazejkustra Oct 23, 2025
cb69c06
Remove unused WebGPUModule
blazejkustra Oct 23, 2025
24880dc
fix: restore r3fPath in metro.config.js and remove unused declaration…
blazejkustra Oct 23, 2025
d68d390
refactor: clean up global navigator declaration and improve userAgent…
blazejkustra Oct 23, 2025
05c90f9
Fix native builds
blazejkustra Oct 23, 2025
0c9e4c6
chore: update dependencies in example
blazejkustra Oct 23, 2025
5cf6a05
feat: add CORS proxy support for shader and uniforms URLs in ComputeToy
blazejkustra Oct 23, 2025
ba13063
feat: create RNWebGPU global object for web integration
blazejkustra Oct 23, 2025
72b705c
chore: add react-native-web dependency
blazejkustra Oct 23, 2025
439ca8a
Move getNativeSurface, makeWebGPUCanvasContext to utils
blazejkustra Oct 23, 2025
0a98e38
Fix displaying canvas on ios
blazejkustra Oct 23, 2025
f1f1f6e
chore: Add web polyfill for resolveAssetSource
blazejkustra Oct 23, 2025
fecf307
fix: Import React to ensure proper JSX handling in older environments
blazejkustra Oct 25, 2025
7b7b2ff
refactor: polyfill native module on web (fixes crashes when WebGPU mo…
blazejkustra Oct 25, 2025
722ad9b
Merge branch 'main' into add-web-support
wcandillon Nov 3, 2025
ae1c0d3
:wrench:
wcandillon Nov 3, 2025
19ac30a
:green_heart:
wcandillon Nov 3, 2025
411de81
Refactor MNISTInference component for clarity
wcandillon Nov 3, 2025
a9cc1da
:arrow_up:
wcandillon Nov 3, 2025
28d1c61
:green_heart:
wcandillon Nov 3, 2025
7ca4563
:wrench:
wcandillon Nov 3, 2025
e35af50
:green_heart:
wcandillon Nov 3, 2025
579b858
:green_heart:
wcandillon Nov 3, 2025
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
32 changes: 32 additions & 0 deletions apps/example/getWebMetroConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const path = require("path");
const root = path.resolve(__dirname, "../..");
const rnwPath = path.resolve(root, "node_modules/react-native-web");
const assetRegistryPath = path.resolve(
root,
"node_modules/react-native-web/dist/modules/AssetRegistry/index",
);

module.exports = function (metroConfig) {
metroConfig.resolver.platforms = ["ios", "android", "web"];
const origResolveRequest = metroConfig.resolver.resolveRequest;
metroConfig.resolver.resolveRequest = (contextRaw, moduleName, platform) => {
const context = {
...contextRaw,
preferNativePlatform: false,
};

if (moduleName === "react-native") {
return {
filePath: path.resolve(rnwPath, "dist/index.js"),
type: "sourceFile",
};
}

// Let default config handle other modules
return origResolveRequest(context, moduleName, platform);
};

metroConfig.transformer.assetRegistryPath = assetRegistryPath;

return metroConfig;
};
37 changes: 37 additions & 0 deletions apps/example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="icon" href="/src/assets/react.png" type="image/png">
<script src="https://unpkg.com/canvaskit-wasm/bin/full/canvaskit.js"></script>

<title>Example</title>
<!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element -->
<style id="react-native-web-reset">
/* These styles make the body full-height */
html,
body {
height: 100%;
}
/* These styles disable body scrolling if you are using <ScrollView> */
body {
overflow: hidden;
}
/* These styles make the root element full-height */
#root {
display: flex;
height: 100%;
flex: 1;
}
</style>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script src="index.bundle?platform=web&dev=true&hot=false&lazy=true&transform.engine=hermes&transform.routerRoot=app&unstable_transformProfile=hermes-stable" defer></script>
</body>
</html>
22 changes: 22 additions & 0 deletions apps/example/index.web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AppRegistry } from "react-native";

import App from "./src/App";
import { name as appName } from "./app.json";

AppRegistry.registerComponent(appName, () => App);

const rootTag = document.getElementById("root");
if (process.env.NODE_ENV !== "production") {
if (!rootTag) {
throw new Error(
'Required HTML element with id "root" was not found in the document HTML.',
);
}
}

CanvasKitInit({
locateFile: (file) => `https://unpkg.com/canvaskit-wasm/bin/full/${file}`,
}).then((CanvasKit) => {
window.CanvasKit = global.CanvasKit = CanvasKit;
AppRegistry.runApplication(appName, { rootTag });
});
4 changes: 2 additions & 2 deletions apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-wgpu (0.3.2):
- react-native-wgpu (0.4.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2903,7 +2903,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
react-native-skia: 5bf2b2107cd7f2d806fd364f5e16b1c7554ed3cd
react-native-wgpu: 15ebc049194b0d06082ab00fb2e5d21ec871c2f4
react-native-wgpu: 5abf760f89d1517255e48de1b5208d2a307109a0
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510
Expand Down
13 changes: 11 additions & 2 deletions apps/example/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const path = require('path');
const getWebMetroConfig = require('./getWebMetroConfig');

const root = path.resolve(__dirname, '../..');
const threePackagePath = path.resolve(root, 'node_modules/three');

const r3fPath = path.resolve(root, "node_modules/@react-three/fiber");
const defaultConfig = getDefaultConfig(__dirname);

const customConfig = {
Expand Down Expand Up @@ -32,6 +34,14 @@ const customConfig = {
type: 'sourceFile',
};
}

if (moduleName === "@react-three/fiber") {
//Just use the vanilla web build of react three fiber, not the stale "native" code path which has not been kept up to date.
return {
filePath: path.resolve(r3fPath, "dist/react-three-fiber.esm.js"),
type: "sourceFile",
};
}
// Let Metro handle other modules
return context.resolveRequest(context, moduleName, platform);
},
Expand All @@ -51,5 +61,4 @@ const customConfig = {

const metroConfig = mergeConfig(defaultConfig, customConfig);


module.exports = metroConfig;
module.exports = !!process.env.IS_WEB_BUILD ? getWebMetroConfig(metroConfig) : metroConfig;
4 changes: 4 additions & 0 deletions apps/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"tsc": "tsc --noEmit",
"android": "react-native run-android",
"ios": "react-native run-ios",
"web": "IS_WEB_BUILD=true react-native start",
"start": "react-native start",
"pod:install:ios": "pod install --project-directory=ios",
"pod:install:macos": "pod install --project-directory=macos",
Expand All @@ -28,11 +29,13 @@
"async-mutex": "^0.5.0",
"fast-text-encoding": "^1.0.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.4",
"react-native-gesture-handler": "^2.28.0",
"react-native-macos": "^0.79.0",
"react-native-reanimated": "3.19.1",
"react-native-safe-area-context": "^5.4.0",
"react-native-web": "^0.21.2",
"react-native-wgpu": "*",
"teapot": "^1.0.0",
"three": "0.172.0",
Expand All @@ -50,6 +53,7 @@
"@rnx-kit/metro-config": "^2.0.0",
"@types/node": "^20.14.7",
"@types/react": "^18.2.6",
"@types/react-dom": "^19.2.2",
"@types/react-test-renderer": "^18.0.0",
"@types/three": "0.172.0",
"@webgpu/types": "0.1.65",
Expand Down
7 changes: 6 additions & 1 deletion apps/example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "./resolveAssetSourcePolyfill";

import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { GestureHandlerRootView } from "react-native-gesture-handler";
Expand Down Expand Up @@ -48,7 +50,10 @@ function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Navigator
initialRouteName="Home"
screenOptions={{ cardStyle: { flex: 1 } }}
>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="HelloTriangle" component={HelloTriangle} />
<Stack.Screen
Expand Down
17 changes: 14 additions & 3 deletions apps/example/src/ComputeToys/ComputeToy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { CanvasRef } from "react-native-wgpu";
import { Canvas } from "react-native-wgpu";
import { useWindowDimensions } from "react-native";
import { Platform, useWindowDimensions } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";

Expand All @@ -12,13 +12,24 @@ export interface ComputeToy {
uniforms: Record<string, number>;
}

// Use a CORS proxy for web to bypass CORS restrictions
const getCorsProxyUrl = (url: string) => {
return Platform.OS === "web"
? `https://corsproxy.io/?${encodeURIComponent(url)}`
: url;
};

export const useComputeToy = (toyId: number) => {
const [props, setProps] = useState<ComputeToy | null>(null);

useEffect(() => {
(async () => {
const shaderURL = `https://compute.toys/view/${toyId}/wgsl`;
const uniformsURL = `https://compute.toys/view/${toyId}/json`;
const shaderURL = getCorsProxyUrl(
`https://compute.toys/view/${toyId}/wgsl`,
);
const uniformsURL = getCorsProxyUrl(
`https://compute.toys/view/${toyId}/json`,
);

// Execute both fetch requests in parallel
const [shaderResponse, uniformsResponse] = await Promise.all([
Expand Down
6 changes: 4 additions & 2 deletions apps/example/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ export const examples = [
];

const styles = StyleSheet.create({
container: {},
container: {
flex: 1,
},
content: {
paddingBottom: 32,
marginBottom: 32,
},
thumbnail: {
backgroundColor: "white",
Expand Down
3 changes: 3 additions & 0 deletions apps/example/src/MNISTInference/MNISTInference.web.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const MNISTInference = () => {
return null;
};
Binary file added apps/example/src/assets/helvetica.ttf
Binary file not shown.
57 changes: 57 additions & 0 deletions apps/example/src/resolveAssetSourcePolyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Image, PixelRatio, Platform } from "react-native";
import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";

// react-native-web does not support resolveAssetSource out of the box
// https://github.com/necolas/react-native-web/issues/1666
if (Platform.OS == "web") {
function resolveAssetUri(source) {
let uri = null;
if (typeof source === "number") {
// get the URI from the packager
const asset = getAssetByID(source);
if (asset == null) {
throw new Error(
`Image: asset with ID "${source}" could not be found. Please check the image source or packager.`,
);
}
// eslint-disable-next-line prefer-destructuring
let scale = asset.scales[0];
if (asset.scales.length > 1) {
const preferredScale = PixelRatio.get();
// Get the scale which is closest to the preferred scale
scale = asset.scales.reduce((prev, curr) =>
Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale)
? curr
: prev,
);
}
const scaleSuffix = scale !== 1 ? `@${scale}x` : "";
uri = asset
? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}`
: "";
} else if (typeof source === "string") {
uri = source;
} else if (source && typeof source.uri === "string") {
// eslint-disable-next-line prefer-destructuring
uri = source.uri;
}

if (uri) {
const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
const match = uri.match(svgDataUriPattern);
// inline SVG markup may contain characters (e.g., #, ") that need to be escaped
if (match) {
const [, prefix, svg] = match;
const encodedSvg = encodeURIComponent(svg);
return `${prefix}${encodedSvg}`;
}
}

return uri;
}

Image.resolveAssetSource = (source) => {
const uri = resolveAssetUri(source) || "";
return { uri };
};
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"pod:install": "turbo run pod:install"
},
"devDependencies": {
"@types/react-native-web": "^0.19.2",
"turbo": "^2.1.0"
}
}
12 changes: 7 additions & 5 deletions packages/webgpu/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# React Native WebGPU

React Native implementation of WebGPU using [Dawn](https://dawn.googlesource.com/dawn).
This is currently a technical preview for early adopters.
React Native implementation of WebGPU using [Dawn](https://dawn.googlesource.com/dawn).
This is currently a technical preview for early adopters.

React Native WebGPU requires React Native 0.81 or newer and doesn't run on legacy architecture.

Expand All @@ -13,6 +13,8 @@ Please note that the package name is `react-native-wgpu`.
npm install react-native-wgpu
```

Note that if you use pnpm, you MUST use a `node-linker = hoisted` so that the external reference to the Dawn webgpu library can successfully link it.

Below are some examples from the [example app](/apps/example/).

https://github.com/user-attachments/assets/116a41b2-2cf8-49f1-9f16-a5c83637c198
Expand Down Expand Up @@ -156,8 +158,8 @@ From there you will be able to run the example app properly.

## Similarities and Differences with the Web

The API has been designed to be completely symmetric with the Web.
For instance, you can access the WebGPU context synchronously, as well as the canvas size.
The API has been designed to be completely symmetric with the Web.
For instance, you can access the WebGPU context synchronously, as well as the canvas size.
Pixel density and canvas resizing are handled exactly like on the Web as well.

```tsx
Expand All @@ -171,7 +173,7 @@ ctx.canvas.height = ctx.canvas.clientHeight * PixelRatio.get();

### Frame Scheduling

In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific.
In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific.
This means that when you are ready to present a frame, you need to call `present` on the context.

```tsx
Expand Down
3 changes: 2 additions & 1 deletion packages/webgpu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-wgpu",
"version": "0.3.2",
"version": "0.4.0",
"description": "React Native WebGPU",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down Expand Up @@ -81,6 +81,7 @@
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-builder-bob": "^0.23.2",
"react-native-web": "^0.21.2",
"rimraf": "^5.0.7",
"seedrandom": "^3.0.5",
"teapot": "^1.0.0",
Expand Down
Loading