Skip to content

Commit e4f07ab

Browse files
authored
fix(πŸ“€): accept shorthand syntax for buffer bindings (#320)
1 parent 0949339 commit e4f07ab

10 files changed

Lines changed: 336 additions & 3 deletions

File tree

β€Žapps/example/ios/Podfile.lockβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,7 +1865,7 @@ PODS:
18651865
- ReactCommon/turbomodule/core
18661866
- SocketRocket
18671867
- Yoga
1868-
- react-native-wgpu (0.5.3):
1868+
- react-native-wgpu (0.5.4):
18691869
- boost
18701870
- DoubleConversion
18711871
- fast_float
@@ -2938,7 +2938,7 @@ SPEC CHECKSUMS:
29382938
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
29392939
react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460
29402940
react-native-skia: 5bf2b2107cd7f2d806fd364f5e16b1c7554ed3cd
2941-
react-native-wgpu: 27d4c1aaa89ba015e8c02d5dbf8abeaa83c4d523
2941+
react-native-wgpu: 5528610fabc9eb435d4ee578b4d6f7c1e133bf56
29422942
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
29432943
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
29442944
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510

β€Žapps/example/src/App.tsxβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ComputeToys } from "./ComputeToys";
3535
import { Reanimated } from "./Reanimated";
3636
import { AsyncStarvation } from "./Diagnostics/AsyncStarvation";
3737
import { DeviceLostHang } from "./Diagnostics/DeviceLostHang";
38+
import { StorageBufferVertices } from "./StorageBufferVertices";
3839

3940
// The two lines below are needed by three.js
4041
import "fast-text-encoding";
@@ -91,6 +92,10 @@ function App() {
9192
<Stack.Screen name="Reanimated" component={Reanimated} />
9293
<Stack.Screen name="AsyncStarvation" component={AsyncStarvation} />
9394
<Stack.Screen name="DeviceLostHang" component={DeviceLostHang} />
95+
<Stack.Screen
96+
name="StorageBufferVertices"
97+
component={StorageBufferVertices}
98+
/>
9499
</Stack.Navigator>
95100
</NavigationContainer>
96101
</GestureHandlerRootView>

β€Žapps/example/src/Home.tsxβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ export const examples = [
123123
screen: "DeviceLostHang",
124124
title: "⚠️ Device Lost Hang",
125125
},
126+
{
127+
screen: "StorageBufferVertices",
128+
title: "πŸ’Ύ Storage Buffer Vertices",
129+
},
126130
];
127131

128132
const styles = StyleSheet.create({

β€Žapps/example/src/Route.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export type Routes = {
2828
Reanimated: undefined;
2929
AsyncStarvation: undefined;
3030
DeviceLostHang: undefined;
31+
StorageBufferVertices: undefined;
3132
};
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React from "react";
2+
import { StyleSheet, View } from "react-native";
3+
import { Canvas } from "react-native-wgpu";
4+
5+
import { useWebGPU } from "../components/useWebGPU";
6+
7+
import { shaderCode } from "./shaders";
8+
9+
const rand = (min?: number, max?: number) => {
10+
if (min === undefined) {
11+
min = 0;
12+
max = 1;
13+
} else if (max === undefined) {
14+
max = min;
15+
min = 0;
16+
}
17+
return min + Math.random() * (max - min);
18+
};
19+
20+
function createCircleVertices({
21+
radius = 1,
22+
numSubdivisions = 24,
23+
innerRadius = 0,
24+
startAngle = 0,
25+
endAngle = Math.PI * 2,
26+
} = {}) {
27+
const numVertices = numSubdivisions * 3 * 2;
28+
const vertexData = new Float32Array(numSubdivisions * 2 * 3 * 2);
29+
30+
let offset = 0;
31+
const addVertex = (x: number, y: number) => {
32+
vertexData[offset++] = x;
33+
vertexData[offset++] = y;
34+
};
35+
36+
for (let i = 0; i < numSubdivisions; ++i) {
37+
const angle1 =
38+
startAngle + ((i + 0) * (endAngle - startAngle)) / numSubdivisions;
39+
const angle2 =
40+
startAngle + ((i + 1) * (endAngle - startAngle)) / numSubdivisions;
41+
42+
const c1 = Math.cos(angle1);
43+
const s1 = Math.sin(angle1);
44+
const c2 = Math.cos(angle2);
45+
const s2 = Math.sin(angle2);
46+
47+
// first triangle
48+
addVertex(c1 * radius, s1 * radius);
49+
addVertex(c2 * radius, s2 * radius);
50+
addVertex(c1 * innerRadius, s1 * innerRadius);
51+
52+
// second triangle
53+
addVertex(c1 * innerRadius, s1 * innerRadius);
54+
addVertex(c2 * radius, s2 * radius);
55+
addVertex(c2 * innerRadius, s2 * innerRadius);
56+
}
57+
58+
return {
59+
vertexData,
60+
numVertices,
61+
};
62+
}
63+
64+
export function StorageBufferVertices() {
65+
const ref = useWebGPU(({ context, device, presentationFormat, canvas }) => {
66+
const module = device.createShaderModule({
67+
code: shaderCode,
68+
});
69+
70+
const pipeline = device.createRenderPipeline({
71+
label: "storage buffer vertices",
72+
layout: "auto",
73+
vertex: {
74+
module,
75+
},
76+
fragment: {
77+
module,
78+
targets: [{ format: presentationFormat }],
79+
},
80+
});
81+
82+
const kNumObjects = 100;
83+
const objectInfos: { scale: number }[] = [];
84+
85+
// create 2 storage buffers
86+
const staticUnitSize =
87+
4 * 4 + // color is 4 32bit floats (4bytes each)
88+
2 * 4 + // offset is 2 32bit floats (4bytes each)
89+
2 * 4; // padding
90+
const changingUnitSize = 2 * 4; // scale is 2 32bit floats (4bytes each)
91+
const staticStorageBufferSize = staticUnitSize * kNumObjects;
92+
const changingStorageBufferSize = changingUnitSize * kNumObjects;
93+
94+
const staticStorageBuffer = device.createBuffer({
95+
label: "static storage for objects",
96+
size: staticStorageBufferSize,
97+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
98+
});
99+
100+
const changingStorageBuffer = device.createBuffer({
101+
label: "changing storage for objects",
102+
size: changingStorageBufferSize,
103+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
104+
});
105+
106+
// offsets to the various uniform values in float32 indices
107+
const kColorOffset = 0;
108+
const kOffsetOffset = 4;
109+
const kScaleOffset = 0;
110+
111+
const staticStorageValues = new Float32Array(staticStorageBufferSize / 4);
112+
for (let i = 0; i < kNumObjects; ++i) {
113+
const staticOffset = i * (staticUnitSize / 4);
114+
115+
// These are only set once so set them now
116+
staticStorageValues.set(
117+
[rand(), rand(), rand(), 1],
118+
staticOffset + kColorOffset,
119+
); // set the color
120+
staticStorageValues.set(
121+
[rand(-0.9, 0.9), rand(-0.9, 0.9)],
122+
staticOffset + kOffsetOffset,
123+
); // set the offset
124+
125+
objectInfos.push({
126+
scale: rand(0.2, 0.5),
127+
});
128+
}
129+
device.queue.writeBuffer(staticStorageBuffer, 0, staticStorageValues);
130+
131+
// a typed array we can use to update the changingStorageBuffer
132+
const storageValues = new Float32Array(changingStorageBufferSize / 4);
133+
134+
// setup a storage buffer with vertex data
135+
const { vertexData, numVertices } = createCircleVertices({
136+
radius: 0.5,
137+
innerRadius: 0.25,
138+
});
139+
const vertexStorageBuffer = device.createBuffer({
140+
label: "storage buffer vertices",
141+
size: vertexData.byteLength,
142+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
143+
});
144+
device.queue.writeBuffer(vertexStorageBuffer, 0, vertexData);
145+
146+
const bindGroup = device.createBindGroup({
147+
label: "bind group for objects",
148+
layout: pipeline.getBindGroupLayout(0),
149+
entries: [
150+
{ binding: 0, resource: staticStorageBuffer },
151+
{ binding: 1, resource: changingStorageBuffer },
152+
{ binding: 2, resource: vertexStorageBuffer },
153+
],
154+
});
155+
156+
const renderPassDescriptor: GPURenderPassDescriptor = {
157+
label: "our basic canvas renderPass",
158+
colorAttachments: [
159+
{
160+
view: context.getCurrentTexture().createView(),
161+
clearValue: [0.3, 0.3, 0.3, 1],
162+
loadOp: "clear",
163+
storeOp: "store",
164+
},
165+
],
166+
};
167+
168+
// Set the uniform values in our JavaScript side Float32Array
169+
const aspect = canvas.width / canvas.height;
170+
171+
// set the scales for each object
172+
objectInfos.forEach(({ scale }, ndx) => {
173+
const offset = ndx * (changingUnitSize / 4);
174+
storageValues.set([scale / aspect, scale], offset + kScaleOffset);
175+
});
176+
// upload all scales at once
177+
device.queue.writeBuffer(changingStorageBuffer, 0, storageValues);
178+
179+
const encoder = device.createCommandEncoder();
180+
const pass = encoder.beginRenderPass(renderPassDescriptor);
181+
pass.setPipeline(pipeline);
182+
pass.setBindGroup(0, bindGroup);
183+
pass.draw(numVertices, kNumObjects);
184+
pass.end();
185+
186+
const commandBuffer = encoder.finish();
187+
device.queue.submit([commandBuffer]);
188+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
189+
(context as any).present();
190+
});
191+
192+
return (
193+
<View style={style.container}>
194+
<Canvas ref={ref} style={style.webgpu} />
195+
</View>
196+
);
197+
}
198+
199+
const style = StyleSheet.create({
200+
container: {
201+
flex: 1,
202+
},
203+
webgpu: {
204+
flex: 1,
205+
},
206+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./StorageBufferVertices";
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export const shaderCode = /* wgsl */ `
2+
struct OurStruct {
3+
color: vec4f,
4+
offset: vec2f,
5+
};
6+
7+
struct OtherStruct {
8+
scale: vec2f,
9+
};
10+
11+
struct Vertex {
12+
position: vec2f,
13+
};
14+
15+
struct VSOutput {
16+
@builtin(position) position: vec4f,
17+
@location(0) color: vec4f,
18+
};
19+
20+
@group(0) @binding(0) var<storage, read> ourStructs: array<OurStruct>;
21+
@group(0) @binding(1) var<storage, read> otherStructs: array<OtherStruct>;
22+
@group(0) @binding(2) var<storage, read> pos: array<Vertex>;
23+
24+
@vertex fn vs(
25+
@builtin(vertex_index) vertexIndex : u32,
26+
@builtin(instance_index) instanceIndex: u32
27+
) -> VSOutput {
28+
let otherStruct = otherStructs[instanceIndex];
29+
let ourStruct = ourStructs[instanceIndex];
30+
31+
var vsOut: VSOutput;
32+
vsOut.position = vec4f(
33+
pos[vertexIndex].position * otherStruct.scale + ourStruct.offset, 0.0, 1.0);
34+
vsOut.color = ourStruct.color;
35+
return vsOut;
36+
}
37+
38+
@fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f {
39+
return vsOut.color;
40+
}
41+
`;

β€Žpackages/webgpu/cpp/rnwgpu/api/descriptors/GPUBindGroupEntry.hβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ template <> struct JSIConverter<std::shared_ptr<rnwgpu::GPUBindGroupEntry>> {
4646
} else if (obj.hasNativeState<rnwgpu::GPUTextureView>(runtime)) {
4747
result->textureView =
4848
obj.getNativeState<rnwgpu::GPUTextureView>(runtime);
49+
} else if (obj.hasNativeState<rnwgpu::GPUBuffer>(runtime)) {
50+
// Support passing GPUBuffer directly as resource (auto-wrap in
51+
// GPUBufferBinding)
52+
auto binding = std::make_shared<rnwgpu::GPUBufferBinding>();
53+
binding->buffer = obj.getNativeState<rnwgpu::GPUBuffer>(runtime);
54+
result->buffer = binding;
4955
} else {
5056
result->buffer = JSIConverter<
5157
std::shared_ptr<rnwgpu::GPUBufferBinding>>::fromJSI(runtime,

β€Žpackages/webgpu/package.jsonβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-wgpu",
3-
"version": "0.5.4",
3+
"version": "0.5.5",
44
"description": "React Native WebGPU",
55
"main": "lib/commonjs/index",
66
"module": "lib/module/index",

0 commit comments

Comments
Β (0)