Skip to content

Commit 7984942

Browse files
committed
Add web bindings + simple example web app
1 parent a1c9b69 commit 7984942

26 files changed

+9898
-246
lines changed

apps/web-example/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.pbxproj -text

apps/web-example/.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2+
3+
# dependencies
4+
node_modules/
5+
6+
# Expo
7+
.expo/
8+
dist/
9+
web-build/
10+
11+
# Native
12+
*.orig.*
13+
*.jks
14+
*.p8
15+
*.p12
16+
*.key
17+
*.mobileprovision
18+
19+
# Metro
20+
.metro-health-check*
21+
22+
# debug
23+
npm-debug.*
24+
yarn-debug.*
25+
yarn-error.*
26+
27+
# macOS
28+
.DS_Store
29+
*.pem
30+
31+
# local env files
32+
.env*.local
33+
34+
# typescript
35+
*.tsbuildinfo
36+
37+
.yarn
38+
ios
39+
android

apps/web-example/App.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from "react";
2+
import { StyleSheet, View, PixelRatio } from "react-native";
3+
import { Canvas, useCanvasEffect } from "react-native-wgpu";
4+
5+
import { redFragWGSL, triangleVertWGSL } from "./triangle";
6+
7+
export default function App() {
8+
const ref = useCanvasEffect(async () => {
9+
const adapter = await navigator.gpu.requestAdapter();
10+
if (!adapter) {
11+
throw new Error("No adapter");
12+
}
13+
const device = await adapter.requestDevice();
14+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
15+
16+
const context = ref.current.getContext("webgpu");
17+
const canvas = context.canvas;
18+
canvas.width = canvas.clientWidth * PixelRatio.get();
19+
canvas.height = canvas.clientHeight * PixelRatio.get();
20+
21+
if (!context) {
22+
throw new Error("No context");
23+
}
24+
25+
context.configure({
26+
device,
27+
format: presentationFormat,
28+
alphaMode: "opaque",
29+
});
30+
31+
const pipeline = device.createRenderPipeline({
32+
layout: "auto",
33+
vertex: {
34+
module: device.createShaderModule({
35+
code: triangleVertWGSL,
36+
}),
37+
entryPoint: "main",
38+
},
39+
fragment: {
40+
module: device.createShaderModule({
41+
code: redFragWGSL,
42+
}),
43+
entryPoint: "main",
44+
targets: [
45+
{
46+
format: presentationFormat,
47+
},
48+
],
49+
},
50+
primitive: {
51+
topology: "triangle-list",
52+
},
53+
});
54+
55+
const commandEncoder = device.createCommandEncoder();
56+
57+
const textureView = context.getCurrentTexture().createView();
58+
59+
const renderPassDescriptor = {
60+
colorAttachments: [
61+
{
62+
view: textureView,
63+
clearValue: [0, 0, 0, 1],
64+
loadOp: "clear",
65+
storeOp: "store",
66+
},
67+
],
68+
};
69+
70+
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
71+
passEncoder.setPipeline(pipeline);
72+
passEncoder.draw(3);
73+
passEncoder.end();
74+
75+
device.queue.submit([commandEncoder.finish()]);
76+
77+
context.present();
78+
});
79+
80+
return (
81+
<View style={style.container}>
82+
<Canvas ref={ref} style={style.webgpu} />
83+
</View>
84+
);
85+
}
86+
87+
const style = StyleSheet.create({
88+
container: {
89+
flex: 1,
90+
},
91+
webgpu: {
92+
flex: 1,
93+
},
94+
});

apps/web-example/app.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"expo": {
3+
"name": "web-example",
4+
"slug": "web-example",
5+
"version": "1.0.0",
6+
"platforms": ["web"]
7+
}
8+
}

apps/web-example/babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = function(api) {
2+
api.cache(true);
3+
return {
4+
presets: ['babel-preset-expo']
5+
};
6+
};

apps/web-example/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { registerRootComponent } from 'expo';
2+
3+
import App from './App';
4+
5+
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6+
// It also ensures that whether you load the app in Expo Go or in a native build,
7+
// the environment is set up appropriately
8+
registerRootComponent(App);

apps/web-example/metro.config.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const { getDefaultConfig } = require("expo/metro-config");
2+
const path = require("path");
3+
4+
const config = getDefaultConfig(__dirname);
5+
6+
config.watchFolders = [
7+
path.resolve(__dirname, "../.."),
8+
path.resolve(__dirname, "../../packages/webgpu"),
9+
];
10+
11+
config.resolver.resolveRequest = (context, moduleName, platform) => {
12+
if (moduleName === "react-native-wgpu") {
13+
//For monorepo convenience only, use the "react-native" field in package.json
14+
context.mainFields = ["react-native"];
15+
}
16+
17+
try {
18+
//Force monorepo dependencies to first use THIS node_modules when resolving their packages.
19+
return context.resolveRequest(
20+
{
21+
...context,
22+
originModulePath: __dirname + "/shim.js",
23+
},
24+
moduleName,
25+
platform
26+
);
27+
} catch (e) {
28+
//Do nothing. Just trying this node_modules first
29+
}
30+
31+
return context.resolveRequest(context, moduleName, platform);
32+
};
33+
34+
module.exports = config;

apps/web-example/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "web-example",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"start": "expo start --dev-client",
7+
"android": "expo run:android",
8+
"ios": "expo run:ios",
9+
"web": "expo start --web"
10+
},
11+
"dependencies": {
12+
"@expo/metro-runtime": "~3.2.3",
13+
"expo": "~51.0.28",
14+
"expo-status-bar": "~1.12.1",
15+
"react": "18.2.0",
16+
"react-dom": "18.2.0",
17+
"react-native": "0.74.5",
18+
"react-native-web": "~0.19.10",
19+
"react-native-wgpu": "link:../../packages/webgpu"
20+
},
21+
"devDependencies": {
22+
"@babel/core": "^7.20.0",
23+
"@rnx-kit/metro-config": "^2.0.1"
24+
},
25+
"private": true
26+
}

apps/web-example/shim.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

apps/web-example/triangle.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const triangleVertWGSL = `@vertex
2+
fn main(
3+
@builtin(vertex_index) VertexIndex : u32
4+
) -> @builtin(position) vec4f {
5+
var pos = array<vec2f, 3>(
6+
vec2(0.0, 0.5),
7+
vec2(-0.5, -0.5),
8+
vec2(0.5, -0.5)
9+
);
10+
11+
return vec4f(pos[VertexIndex], 0.0, 1.0);
12+
}`;
13+
14+
export const redFragWGSL = `@fragment
15+
fn main() -> @location(0) vec4f {
16+
return vec4(1.0, 0.0, 0.0, 1.0);
17+
}`;

apps/web-example/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"compilerOptions": {},
3+
"extends": "expo/tsconfig.base"
4+
}

0 commit comments

Comments
 (0)