Skip to content

Commit c51c688

Browse files
authored
chore(🪀): add computetoys example (#213)
1 parent 69463a1 commit c51c688

19 files changed

Lines changed: 3234 additions & 1244 deletions

File tree

‎apps/example/ios/Podfile.lock‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1748,7 +1748,7 @@ PODS:
17481748
- React-RCTFBReactNativeSpec
17491749
- ReactCommon/turbomodule/core
17501750
- SocketRocket
1751-
- react-native-safe-area-context (5.4.0):
1751+
- react-native-safe-area-context (5.6.1):
17521752
- boost
17531753
- DoubleConversion
17541754
- fast_float
@@ -1766,8 +1766,8 @@ PODS:
17661766
- React-graphics
17671767
- React-ImageManager
17681768
- React-jsi
1769-
- react-native-safe-area-context/common (= 5.4.0)
1770-
- react-native-safe-area-context/fabric (= 5.4.0)
1769+
- react-native-safe-area-context/common (= 5.6.1)
1770+
- react-native-safe-area-context/fabric (= 5.6.1)
17711771
- React-NativeModulesApple
17721772
- React-RCTFabric
17731773
- React-renderercss
@@ -1778,7 +1778,7 @@ PODS:
17781778
- ReactCommon/turbomodule/core
17791779
- SocketRocket
17801780
- Yoga
1781-
- react-native-safe-area-context/common (5.4.0):
1781+
- react-native-safe-area-context/common (5.6.1):
17821782
- boost
17831783
- DoubleConversion
17841784
- fast_float
@@ -1806,7 +1806,7 @@ PODS:
18061806
- ReactCommon/turbomodule/core
18071807
- SocketRocket
18081808
- Yoga
1809-
- react-native-safe-area-context/fabric (5.4.0):
1809+
- react-native-safe-area-context/fabric (5.6.1):
18101810
- boost
18111811
- DoubleConversion
18121812
- fast_float
@@ -1865,7 +1865,7 @@ PODS:
18651865
- ReactCommon/turbomodule/core
18661866
- SocketRocket
18671867
- Yoga
1868-
- react-native-wgpu (0.2.7):
1868+
- react-native-wgpu (0.2.8):
18691869
- boost
18701870
- DoubleConversion
18711871
- fast_float
@@ -2901,9 +2901,9 @@ SPEC CHECKSUMS:
29012901
React-logger: a3cb5b29c32b8e447b5a96919340e89334062b48
29022902
React-Mapbuffer: 9d2434a42701d6144ca18f0ca1c4507808ca7696
29032903
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
2904-
react-native-safe-area-context: 84f35326241e8a61b9e3b6f69e1bf098da4c5642
2904+
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
29052905
react-native-skia: 5bf2b2107cd7f2d806fd364f5e16b1c7554ed3cd
2906-
react-native-wgpu: cf92463dcf22589d51d1e314d40732bb2c5fc9d0
2906+
react-native-wgpu: fa319a78b8773740fd2247cff8054f4bb3cd341e
29072907
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
29082908
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
29092909
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510

‎apps/example/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@tensorflow/tfjs": "^4.22.0",
2525
"@tensorflow/tfjs-backend-webgpu": "^4.22.0",
2626
"@tensorflow/tfjs-vis": "^1.5.1",
27+
"async-mutex": "^0.5.0",
2728
"fast-text-encoding": "^1.0.6",
2829
"react": "19.1.0",
2930
"react-native": "0.81.4",
@@ -50,7 +51,7 @@
5051
"@types/react": "^18.2.6",
5152
"@types/react-test-renderer": "^18.0.0",
5253
"@types/three": "0.172.0",
53-
"@webgpu/types": "0.1.51",
54+
"@webgpu/types": "0.1.65",
5455
"babel-jest": "^29.6.3",
5556
"eslint": "9.35.0",
5657
"eslint-config-react-native-wcandillon": "4.0.1",

‎apps/example/src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { ThreeJS } from "./ThreeJS";
2929
import { GradientTiles } from "./GradientTiles";
3030
import { CanvasAPI } from "./CanvasAPI";
3131
import { Tensorflow } from "./Tensorflow";
32+
import { ComputeToys } from "./ComputeToys";
3233

3334
// The two lines below are needed by three.js
3435
import "fast-text-encoding";
@@ -51,6 +52,7 @@ function App() {
5152
name="HelloTriangleMSAA"
5253
component={HelloTriangleMSAA}
5354
/>
55+
<Stack.Screen name="ComputeToys" component={ComputeToys} />
5456
<Stack.Screen name="ThreeJS" component={ThreeJS} />
5557
<Stack.Screen name="Tensorflow" component={Tensorflow} />
5658
<Stack.Screen name="CanvasAPI" component={CanvasAPI} />
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { useCallback, useEffect, useRef, useState } from "react";
2+
import { Canvas, useCanvasEffect } from "react-native-wgpu";
3+
import { useWindowDimensions } from "react-native";
4+
import { useSharedValue } from "react-native-reanimated";
5+
import { Gesture, GestureDetector } from "react-native-gesture-handler";
6+
7+
import { ComputeEngine } from "./engine";
8+
9+
export interface ComputeToy {
10+
shader: string;
11+
uniforms: Record<string, number>;
12+
}
13+
14+
export const useComputeToy = (toyId: number) => {
15+
const [props, setProps] = useState<ComputeToy | null>(null);
16+
17+
useEffect(() => {
18+
(async () => {
19+
const shaderURL = `https://compute.toys/view/${toyId}/wgsl`;
20+
const uniformsURL = `https://compute.toys/view/${toyId}/json`;
21+
22+
// Execute both fetch requests in parallel
23+
const [shaderResponse, uniformsResponse] = await Promise.all([
24+
fetch(shaderURL),
25+
fetch(uniformsURL),
26+
]);
27+
28+
// Process the responses in parallel
29+
const [shader, uniformsJSON] = await Promise.all([
30+
shaderResponse.text(),
31+
uniformsResponse.json(),
32+
]);
33+
const uniforms: Record<string, number> = {};
34+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
35+
uniformsJSON.body.uniforms.forEach((uniform: any) => {
36+
uniforms[uniform.name] = uniform.value;
37+
});
38+
setProps({ shader, uniforms });
39+
})();
40+
}, [toyId]);
41+
return props;
42+
};
43+
44+
export interface ComputeToyProps {
45+
toy: ComputeToy;
46+
}
47+
48+
export const ComputeToy = ({ toy: { shader, uniforms } }: ComputeToyProps) => {
49+
const mouse = useSharedValue({ pos: { x: 0, y: 0 }, click: false });
50+
const { width, height } = useWindowDimensions();
51+
const [engine, setEngine] = useState<ComputeEngine | null>(null);
52+
const animationRef = useRef<number | null>(null);
53+
const lastTimeRef = useRef<number>(0);
54+
55+
// Initialize WebGPU and the compute engine
56+
const canvasRef = useCanvasEffect(() => {
57+
const initWebGPU = async () => {
58+
try {
59+
// Create the compute engine
60+
await ComputeEngine.create();
61+
const eng = ComputeEngine.getInstance();
62+
63+
// Set the canvas surface
64+
if (canvasRef.current) {
65+
eng.setSurface(canvasRef.current!);
66+
67+
// Set the canvas size based on your CANVAS constants
68+
// TODO: use PixelRatio.get()?
69+
eng.resize(width, height, 1);
70+
71+
// Initialize render state
72+
eng.reset();
73+
74+
// Set callbacks for shader compilation
75+
eng.onSuccess((entryPoints) => {
76+
console.log(
77+
"Shader compiled successfully with entry points:",
78+
entryPoints,
79+
);
80+
});
81+
82+
eng.onError((message, row, col) => {
83+
console.error(`Shader error at ${row}:${col} - ${message}`);
84+
});
85+
eng.setCustomFloats(
86+
Object.keys(uniforms),
87+
new Float32Array(Object.values(uniforms)),
88+
);
89+
// Process and compile the shader
90+
const preprocessed = await eng.preprocess(shader);
91+
if (preprocessed) {
92+
await eng.compile(preprocessed);
93+
}
94+
95+
setEngine(eng);
96+
}
97+
} catch (error) {
98+
console.error("Failed to initialize WebGPU:", error);
99+
}
100+
};
101+
102+
initWebGPU();
103+
104+
return () => {
105+
if (animationRef.current !== null) {
106+
cancelAnimationFrame(animationRef.current);
107+
}
108+
};
109+
});
110+
111+
// Animation/render loop
112+
const renderLoop = useCallback(
113+
(timestamp: number) => {
114+
if (!engine) {
115+
return;
116+
}
117+
118+
// Calculate time delta
119+
const delta = lastTimeRef.current
120+
? (timestamp - lastTimeRef.current) / 1000
121+
: 0;
122+
lastTimeRef.current = timestamp;
123+
124+
// Update time uniforms
125+
engine.setTimeElapsed(timestamp / 1000);
126+
engine.setTimeDelta(delta);
127+
if (mouse) {
128+
engine.setMousePos(mouse.value.pos.x, mouse.value.pos.y);
129+
engine.setMouseClick(mouse.value.click);
130+
}
131+
// Render frame
132+
133+
engine.render();
134+
// Schedule next frame
135+
animationRef.current = requestAnimationFrame(renderLoop);
136+
},
137+
[engine, mouse],
138+
);
139+
140+
useEffect(() => {
141+
renderLoop(new Date().getTime());
142+
}, [renderLoop]);
143+
144+
const gesture = Gesture.Pan()
145+
.onChange((e) => {
146+
mouse.value = {
147+
pos: {
148+
x: e.absoluteX / width,
149+
y: e.absoluteY / height,
150+
},
151+
click: true,
152+
};
153+
})
154+
.onEnd((e) => {
155+
mouse.value = {
156+
pos: {
157+
x: e.absoluteX / width,
158+
y: e.absoluteY / height,
159+
},
160+
click: false,
161+
};
162+
});
163+
164+
return (
165+
<GestureDetector gesture={gesture}>
166+
<Canvas ref={canvasRef} style={{ width, height }} />
167+
</GestureDetector>
168+
);
169+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ComputeToy, useComputeToy } from "./ComputeToy";
2+
3+
export const ComputeToys = () => {
4+
//1806
5+
const toy = useComputeToy(537);
6+
if (!toy) {
7+
return null;
8+
}
9+
return <ComputeToy toy={toy} />;
10+
};

0 commit comments

Comments
 (0)