react-native-wgpu 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +43 -42
  2. package/android/cpp/AndroidPlatformContext.h +2 -1
  3. package/cpp/jsi/RNFJSIConverter.h +10 -0
  4. package/cpp/rnwgpu/SurfaceRegistry.h +1 -2
  5. package/cpp/rnwgpu/api/Canvas.h +3 -3
  6. package/cpp/rnwgpu/api/GPUBuffer.h +1 -1
  7. package/ios/WebGPUView.mm +14 -1
  8. package/lib/commonjs/Canvas.js.map +1 -1
  9. package/lib/commonjs/utils.js +2 -2
  10. package/lib/commonjs/utils.js.map +1 -1
  11. package/lib/module/Canvas.js.map +1 -1
  12. package/lib/module/utils.js +2 -2
  13. package/lib/module/utils.js.map +1 -1
  14. package/lib/typescript/lib/commonjs/utils.d.ts +1 -1
  15. package/lib/typescript/lib/commonjs/utils.d.ts.map +1 -1
  16. package/lib/typescript/lib/module/utils.d.ts +1 -1
  17. package/lib/typescript/lib/module/utils.d.ts.map +1 -1
  18. package/lib/typescript/src/Canvas.d.ts +3 -2
  19. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  20. package/lib/typescript/src/__tests__/ArrayBuffer.spec.d.ts +2 -0
  21. package/lib/typescript/src/__tests__/ArrayBuffer.spec.d.ts.map +1 -0
  22. package/lib/typescript/src/__tests__/Buffer.spec.d.ts +2 -0
  23. package/lib/typescript/src/__tests__/Buffer.spec.d.ts.map +1 -0
  24. package/lib/typescript/src/__tests__/ComputeShader.spec.d.ts +2 -0
  25. package/lib/typescript/src/__tests__/ComputeShader.spec.d.ts.map +1 -0
  26. package/lib/typescript/src/__tests__/Constants.spec.d.ts +2 -0
  27. package/lib/typescript/src/__tests__/Constants.spec.d.ts.map +1 -0
  28. package/lib/typescript/src/__tests__/Device.spec.d.ts +2 -0
  29. package/lib/typescript/src/__tests__/Device.spec.d.ts.map +1 -0
  30. package/lib/typescript/src/__tests__/ExternalTexture.spec.d.ts +2 -0
  31. package/lib/typescript/src/__tests__/ExternalTexture.spec.d.ts.map +1 -0
  32. package/lib/typescript/src/__tests__/GPU.spec.d.ts +2 -0
  33. package/lib/typescript/src/__tests__/GPU.spec.d.ts.map +1 -0
  34. package/lib/typescript/src/__tests__/ImageData.spec.d.ts +2 -0
  35. package/lib/typescript/src/__tests__/ImageData.spec.d.ts.map +1 -0
  36. package/lib/typescript/src/__tests__/Shaders.spec.d.ts +2 -0
  37. package/lib/typescript/src/__tests__/Shaders.spec.d.ts.map +1 -0
  38. package/lib/typescript/src/__tests__/Texture.spec.d.ts +2 -0
  39. package/lib/typescript/src/__tests__/Texture.spec.d.ts.map +1 -0
  40. package/lib/typescript/src/__tests__/components/DrawingContext.d.ts +12 -0
  41. package/lib/typescript/src/__tests__/components/DrawingContext.d.ts.map +1 -0
  42. package/lib/typescript/src/__tests__/components/Wireframe/Shaders.d.ts +3 -0
  43. package/lib/typescript/src/__tests__/components/Wireframe/Shaders.d.ts.map +1 -0
  44. package/lib/typescript/src/__tests__/components/Wireframe/models.d.ts +29 -0
  45. package/lib/typescript/src/__tests__/components/Wireframe/models.d.ts.map +1 -0
  46. package/lib/typescript/src/__tests__/components/Wireframe/utils.d.ts +5 -0
  47. package/lib/typescript/src/__tests__/components/Wireframe/utils.d.ts.map +1 -0
  48. package/lib/typescript/src/__tests__/components/cube.d.ts +7 -0
  49. package/lib/typescript/src/__tests__/components/cube.d.ts.map +1 -0
  50. package/lib/typescript/src/__tests__/components/meshes/mesh.d.ts +22 -0
  51. package/lib/typescript/src/__tests__/components/meshes/mesh.d.ts.map +1 -0
  52. package/lib/typescript/src/__tests__/components/meshes/sphere.d.ts +12 -0
  53. package/lib/typescript/src/__tests__/components/meshes/sphere.d.ts.map +1 -0
  54. package/lib/typescript/src/__tests__/components/meshes/stanfordDragon.d.ts +7 -0
  55. package/lib/typescript/src/__tests__/components/meshes/stanfordDragon.d.ts.map +1 -0
  56. package/lib/typescript/src/__tests__/components/meshes/stanfordDragonData.d.ts +6 -0
  57. package/lib/typescript/src/__tests__/components/meshes/stanfordDragonData.d.ts.map +1 -0
  58. package/lib/typescript/src/__tests__/components/meshes/teapot.d.ts +6 -0
  59. package/lib/typescript/src/__tests__/components/meshes/teapot.d.ts.map +1 -0
  60. package/lib/typescript/src/__tests__/components/meshes/utils.d.ts +10 -0
  61. package/lib/typescript/src/__tests__/components/meshes/utils.d.ts.map +1 -0
  62. package/lib/typescript/src/__tests__/components/triangle.d.ts +3 -0
  63. package/lib/typescript/src/__tests__/components/triangle.d.ts.map +1 -0
  64. package/lib/typescript/src/__tests__/config.d.ts +3 -0
  65. package/lib/typescript/src/__tests__/config.d.ts.map +1 -0
  66. package/lib/typescript/src/__tests__/demos/ABuffer.spec.d.ts +2 -0
  67. package/lib/typescript/src/__tests__/demos/ABuffer.spec.d.ts.map +1 -0
  68. package/lib/typescript/src/__tests__/demos/Blur.spec.d.ts +2 -0
  69. package/lib/typescript/src/__tests__/demos/Blur.spec.d.ts.map +1 -0
  70. package/lib/typescript/src/__tests__/demos/Cube.spec.d.ts +2 -0
  71. package/lib/typescript/src/__tests__/demos/Cube.spec.d.ts.map +1 -0
  72. package/lib/typescript/src/__tests__/demos/FractalCube.spec.d.ts +2 -0
  73. package/lib/typescript/src/__tests__/demos/FractalCube.spec.d.ts.map +1 -0
  74. package/lib/typescript/src/__tests__/demos/OcclusionQuery.spec.d.ts +2 -0
  75. package/lib/typescript/src/__tests__/demos/OcclusionQuery.spec.d.ts.map +1 -0
  76. package/lib/typescript/src/__tests__/demos/RenderBundles.spec.d.ts +2 -0
  77. package/lib/typescript/src/__tests__/demos/RenderBundles.spec.d.ts.map +1 -0
  78. package/lib/typescript/src/__tests__/demos/Triangle.spec.d.ts +2 -0
  79. package/lib/typescript/src/__tests__/demos/Triangle.spec.d.ts.map +1 -0
  80. package/lib/typescript/src/__tests__/demos/Wireframe.spec.d.ts +2 -0
  81. package/lib/typescript/src/__tests__/demos/Wireframe.spec.d.ts.map +1 -0
  82. package/lib/typescript/src/__tests__/globalSetup.d.ts +10 -0
  83. package/lib/typescript/src/__tests__/globalSetup.d.ts.map +1 -0
  84. package/lib/typescript/src/__tests__/globalTeardown.d.ts +3 -0
  85. package/lib/typescript/src/__tests__/globalTeardown.d.ts.map +1 -0
  86. package/lib/typescript/src/__tests__/setup.d.ts +62 -0
  87. package/lib/typescript/src/__tests__/setup.d.ts.map +1 -0
  88. package/lib/typescript/src/utils.d.ts +2 -1
  89. package/lib/typescript/src/utils.d.ts.map +1 -1
  90. package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
  91. package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
  92. package/libs/android/x86/libwebgpu_dawn.so +0 -0
  93. package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
  94. package/libs/{ios/libwebgpu_dawn.xcframework/ios-arm64 → apple/arm64_iphoneos}/libwebgpu_dawn.a +0 -0
  95. package/libs/{ios → apple}/arm64_iphonesimulator/libwebgpu_dawn.a +0 -0
  96. package/libs/{ios/libwebgpu_dawn.xcframework/xros-arm64 → apple/arm64_xros}/libwebgpu_dawn.a +0 -0
  97. package/libs/{ios → apple}/arm64_xrsimulator/libwebgpu_dawn.a +0 -0
  98. package/libs/{ios → apple}/libwebgpu_dawn.a +0 -0
  99. package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +44 -0
  100. package/libs/{ios/arm64_iphoneos → apple/libwebgpu_dawn.xcframework/ios-arm64}/libwebgpu_dawn.a +0 -0
  101. package/libs/{ios → apple}/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
  102. package/libs/apple/libwebgpu_dawn_macosx.xcframework/Info.plist +28 -0
  103. package/libs/apple/libwebgpu_dawn_macosx.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
  104. package/libs/{ios/libwebgpu_dawn.xcframework/xros-arm64_x86_64-simulator → apple}/libwebgpu_dawn_visionos.a +0 -0
  105. package/libs/{ios/libwebgpu_dawn.xcframework → apple/libwebgpu_dawn_visionos.xcframework}/Info.plist +0 -31
  106. package/libs/{ios/arm64_xros → apple/libwebgpu_dawn_visionos.xcframework/xros-arm64}/libwebgpu_dawn.a +0 -0
  107. package/libs/{ios → apple/libwebgpu_dawn_visionos.xcframework/xros-arm64_x86_64-simulator}/libwebgpu_dawn_visionos.a +0 -0
  108. package/libs/apple/universal_macosx/libwebgpu_dawn.a +0 -0
  109. package/libs/{ios → apple}/x86_64_iphonesimulator/libwebgpu_dawn.a +0 -0
  110. package/libs/{ios → apple}/x86_64_xrsimulator/libwebgpu_dawn.a +0 -0
  111. package/package.json +24 -24
  112. package/src/Canvas.tsx +4 -2
  113. package/src/__tests__/ArrayBuffer.spec.ts +76 -0
  114. package/src/__tests__/Buffer.spec.ts +357 -0
  115. package/src/__tests__/ComputeShader.spec.ts +375 -0
  116. package/src/__tests__/Constants.spec.ts +91 -0
  117. package/src/__tests__/Device.spec.ts +35 -0
  118. package/src/__tests__/ExternalTexture.spec.ts +284 -0
  119. package/src/__tests__/GPU.spec.ts +229 -0
  120. package/src/__tests__/ImageData.spec.ts +26 -0
  121. package/src/__tests__/Shaders.spec.ts +232 -0
  122. package/src/__tests__/Texture.spec.ts +191 -0
  123. package/src/__tests__/assets/Di-3d.png +0 -0
  124. package/src/__tests__/components/DrawingContext.ts +11 -0
  125. package/src/__tests__/components/Wireframe/Shaders.ts +138 -0
  126. package/src/__tests__/components/Wireframe/models.ts +113 -0
  127. package/src/__tests__/components/Wireframe/utils.ts +22 -0
  128. package/src/__tests__/components/cube.ts +51 -0
  129. package/src/__tests__/components/meshes/mesh.ts +96 -0
  130. package/src/__tests__/components/meshes/sphere.ts +103 -0
  131. package/src/__tests__/components/meshes/stanfordDragon.ts +44 -0
  132. package/src/__tests__/components/meshes/stanfordDragonData.ts +5 -0
  133. package/src/__tests__/components/meshes/teapot.ts +13 -0
  134. package/src/__tests__/components/meshes/utils.ts +235 -0
  135. package/src/__tests__/components/triangle.ts +17 -0
  136. package/src/__tests__/config.ts +2 -0
  137. package/src/__tests__/demos/ABuffer.spec.ts +885 -0
  138. package/src/__tests__/demos/Blur.spec.ts +397 -0
  139. package/src/__tests__/demos/Cube.spec.ts +925 -0
  140. package/src/__tests__/demos/FractalCube.spec.ts +239 -0
  141. package/src/__tests__/demos/OcclusionQuery.spec.ts +376 -0
  142. package/src/__tests__/demos/RenderBundles.spec.ts +579 -0
  143. package/src/__tests__/demos/Triangle.spec.ts +248 -0
  144. package/src/__tests__/demos/Wireframe.spec.ts +190 -0
  145. package/src/__tests__/globalSetup.ts +54 -0
  146. package/src/__tests__/globalTeardown.ts +11 -0
  147. package/src/__tests__/setup.ts +409 -0
  148. package/src/__tests__/snapshots/abuffer.png +0 -0
  149. package/src/__tests__/snapshots/asteroid.png +0 -0
  150. package/src/__tests__/snapshots/blur.png +0 -0
  151. package/src/__tests__/snapshots/buffer.png +0 -0
  152. package/src/__tests__/snapshots/constant-triangle.png +0 -0
  153. package/src/__tests__/snapshots/cube.png +0 -0
  154. package/src/__tests__/snapshots/f.png +0 -0
  155. package/src/__tests__/snapshots/f2.png +0 -0
  156. package/src/__tests__/snapshots/fractal-cubes.png +0 -0
  157. package/src/__tests__/snapshots/instanced-cubes.png +0 -0
  158. package/src/__tests__/snapshots/occlusion-query.png +0 -0
  159. package/src/__tests__/snapshots/ref.png +0 -0
  160. package/src/__tests__/snapshots/texture.png +0 -0
  161. package/src/__tests__/snapshots/textured-cube.png +0 -0
  162. package/src/__tests__/snapshots/triangle-msaa.png +0 -0
  163. package/src/__tests__/snapshots/triangle.png +0 -0
  164. package/src/__tests__/snapshots/two-cube.png +0 -0
  165. package/src/utils.ts +3 -1
  166. package/android/gradle.properties +0 -5
  167. package/react-native-wgpu.podspec +0 -53
@@ -0,0 +1,579 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import seedrandom from "seedrandom";
3
+
4
+ import { checkImage, client, encodeImage } from "../setup";
5
+
6
+ const mesh = /*wgsl*/ `struct Uniforms {
7
+ viewProjectionMatrix : mat4x4f
8
+ }
9
+ @group(0) @binding(0) var<uniform> uniforms : Uniforms;
10
+
11
+ @group(1) @binding(0) var<uniform> modelMatrix : mat4x4f;
12
+
13
+ struct VertexInput {
14
+ @location(0) position : vec4f,
15
+ @location(1) normal : vec3f,
16
+ @location(2) uv : vec2f
17
+ }
18
+
19
+ struct VertexOutput {
20
+ @builtin(position) position : vec4f,
21
+ @location(0) normal: vec3f,
22
+ @location(1) uv : vec2f,
23
+ }
24
+
25
+ @vertex
26
+ fn vertexMain(input: VertexInput) -> VertexOutput {
27
+ var output : VertexOutput;
28
+ output.position = uniforms.viewProjectionMatrix * modelMatrix * input.position;
29
+ output.normal = normalize((modelMatrix * vec4(input.normal, 0)).xyz);
30
+ output.uv = input.uv;
31
+ return output;
32
+ }
33
+
34
+ @group(1) @binding(1) var meshSampler: sampler;
35
+ @group(1) @binding(2) var meshTexture: texture_2d<f32>;
36
+
37
+ // Static directional lighting
38
+ const lightDir = vec3f(1, 1, 1);
39
+ const dirColor = vec3(1);
40
+ const ambientColor = vec3f(0.05);
41
+
42
+ @fragment
43
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
44
+ let textureColor = textureSample(meshTexture, meshSampler, input.uv);
45
+
46
+ // Very simplified lighting algorithm.
47
+ let lightColor = saturate(ambientColor + max(dot(input.normal, lightDir), 0.0) * dirColor);
48
+
49
+ return vec4f(textureColor.rgb * lightColor, textureColor.a);
50
+ }
51
+ `;
52
+
53
+ interface Renderable {
54
+ vertices: GPUBuffer;
55
+ indices: GPUBuffer;
56
+ indexCount: number;
57
+ bindGroup?: GPUBindGroup;
58
+ }
59
+
60
+ const rng = seedrandom("asteroid");
61
+ const randomValues: number[] = [];
62
+ for (let j = 0; j < 30000; j++) {
63
+ randomValues.push(rng());
64
+ }
65
+
66
+ describe("Render Bundles", () => {
67
+ it("Asteroid", async () => {
68
+ const result = await client.eval(
69
+ ({
70
+ device,
71
+ gpu,
72
+ ctx,
73
+ meshWGSL,
74
+ mat4,
75
+ vec3,
76
+ assets: { saturn, moon },
77
+ vals,
78
+ }) => {
79
+ interface SphereMesh {
80
+ vertices: Float32Array;
81
+ indices: Uint16Array;
82
+ }
83
+
84
+ const SphereLayout = {
85
+ vertexStride: 8 * 4,
86
+ positionsOffset: 0,
87
+ normalOffset: 3 * 4,
88
+ uvOffset: 6 * 4,
89
+ };
90
+
91
+ let k = 0;
92
+ const random = () => {
93
+ k++;
94
+ return vals[k % vals.length];
95
+ };
96
+
97
+ // Borrowed and simplified from https://github.com/mrdoob/three.js/blob/master/src/geometries/SphereGeometry.js
98
+ function createSphereMesh(
99
+ radius: number,
100
+ widthSegments = 32,
101
+ heightSegments = 16,
102
+ randomness = 0,
103
+ ): SphereMesh {
104
+ const vertices = [];
105
+ const indices = [];
106
+
107
+ widthSegments = Math.max(3, Math.floor(widthSegments));
108
+ heightSegments = Math.max(2, Math.floor(heightSegments));
109
+
110
+ const firstVertex = vec3.create();
111
+ const vertex = vec3.create();
112
+ const normal = vec3.create();
113
+
114
+ let index = 0;
115
+ const grid = [];
116
+
117
+ // generate vertices, normals and uvs
118
+ for (let iy = 0; iy <= heightSegments; iy++) {
119
+ const verticesRow = [];
120
+ const v = iy / heightSegments;
121
+
122
+ // special case for the poles
123
+ let uOffset = 0;
124
+ if (iy === 0) {
125
+ uOffset = 0.5 / widthSegments;
126
+ } else if (iy === heightSegments) {
127
+ uOffset = -0.5 / widthSegments;
128
+ }
129
+
130
+ for (let ix = 0; ix <= widthSegments; ix++) {
131
+ const u = ix / widthSegments;
132
+
133
+ // Poles should just use the same position all the way around.
134
+ if (ix === widthSegments) {
135
+ vec3.copy(firstVertex, vertex);
136
+ } else if (ix === 0 || (iy !== 0 && iy !== heightSegments)) {
137
+ const rr = radius + (random() - 0.5) * 2 * randomness * radius;
138
+
139
+ // vertex
140
+ vertex[0] =
141
+ -rr * Math.cos(u * Math.PI * 2) * Math.sin(v * Math.PI);
142
+ vertex[1] = rr * Math.cos(v * Math.PI);
143
+ vertex[2] =
144
+ rr * Math.sin(u * Math.PI * 2) * Math.sin(v * Math.PI);
145
+
146
+ if (ix === 0) {
147
+ vec3.copy(vertex, firstVertex);
148
+ }
149
+ }
150
+
151
+ for (const element of vertex) {
152
+ vertices.push(element);
153
+ }
154
+
155
+ // normal
156
+ vec3.copy(vertex, normal);
157
+ vec3.normalize(normal, normal);
158
+ for (const element of normal) {
159
+ vertices.push(element);
160
+ }
161
+
162
+ // uv
163
+ vertices.push(u + uOffset, 1 - v);
164
+ verticesRow.push(index++);
165
+ }
166
+
167
+ grid.push(verticesRow);
168
+ }
169
+
170
+ // indices
171
+ for (let iy = 0; iy < heightSegments; iy++) {
172
+ for (let ix = 0; ix < widthSegments; ix++) {
173
+ const a = grid[iy][ix + 1];
174
+ const b = grid[iy][ix];
175
+ const c = grid[iy + 1][ix];
176
+ const d = grid[iy + 1][ix + 1];
177
+
178
+ if (iy !== 0) {
179
+ indices.push(a, b, d);
180
+ }
181
+ if (iy !== heightSegments - 1) {
182
+ indices.push(b, c, d);
183
+ }
184
+ }
185
+ }
186
+
187
+ return {
188
+ vertices: new Float32Array(vertices),
189
+ indices: new Uint16Array(indices),
190
+ };
191
+ }
192
+
193
+ const useRenderBundles = true;
194
+ const asteroidCount = 5000;
195
+ const presentationFormat = gpu.getPreferredCanvasFormat();
196
+
197
+ const shaderModule = device.createShaderModule({
198
+ code: meshWGSL,
199
+ });
200
+
201
+ const pipeline = device.createRenderPipeline({
202
+ layout: "auto",
203
+ vertex: {
204
+ module: shaderModule,
205
+ buffers: [
206
+ {
207
+ arrayStride: SphereLayout.vertexStride,
208
+ attributes: [
209
+ {
210
+ // position
211
+ shaderLocation: 0,
212
+ offset: SphereLayout.positionsOffset,
213
+ format: "float32x3",
214
+ },
215
+ {
216
+ // normal
217
+ shaderLocation: 1,
218
+ offset: SphereLayout.normalOffset,
219
+ format: "float32x3",
220
+ },
221
+ {
222
+ // uv
223
+ shaderLocation: 2,
224
+ offset: SphereLayout.uvOffset,
225
+ format: "float32x2",
226
+ },
227
+ ],
228
+ },
229
+ ],
230
+ },
231
+ fragment: {
232
+ module: shaderModule,
233
+ targets: [
234
+ {
235
+ format: presentationFormat,
236
+ },
237
+ ],
238
+ },
239
+ primitive: {
240
+ topology: "triangle-list",
241
+
242
+ // Backface culling since the sphere is solid piece of geometry.
243
+ // Faces pointing away from the camera will be occluded by faces
244
+ // pointing toward the camera.
245
+ cullMode: "back",
246
+ },
247
+
248
+ // Enable depth testing so that the fragment closest to the camera
249
+ // is rendered in front.
250
+ depthStencil: {
251
+ depthWriteEnabled: true,
252
+ depthCompare: "less",
253
+ format: "depth24plus",
254
+ },
255
+ });
256
+
257
+ const depthTexture = device.createTexture({
258
+ size: [ctx.width, ctx.height],
259
+ format: "depth24plus",
260
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
261
+ });
262
+
263
+ const uniformBufferSize = 4 * 16; // 4x4 matrix
264
+ const uniformBuffer = device.createBuffer({
265
+ size: uniformBufferSize,
266
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
267
+ });
268
+
269
+ // Fetch the images and upload them into a GPUTexture.
270
+ let planetTexture: GPUTexture;
271
+ {
272
+ //const response = await fetch("../../assets/img/saturn.jpg");
273
+ const imageBitmap = saturn;
274
+
275
+ planetTexture = device.createTexture({
276
+ size: [imageBitmap.width, imageBitmap.height, 1],
277
+ format: "rgba8unorm",
278
+ usage:
279
+ GPUTextureUsage.TEXTURE_BINDING |
280
+ GPUTextureUsage.COPY_DST |
281
+ GPUTextureUsage.RENDER_ATTACHMENT,
282
+ });
283
+
284
+ device.queue.copyExternalImageToTexture(
285
+ { source: imageBitmap },
286
+ { texture: planetTexture },
287
+ [imageBitmap.width, imageBitmap.height],
288
+ );
289
+ }
290
+
291
+ let moonTexture: GPUTexture;
292
+ {
293
+ const imageBitmap = moon;
294
+
295
+ moonTexture = device.createTexture({
296
+ size: [imageBitmap.width, imageBitmap.height, 1],
297
+ format: "rgba8unorm",
298
+ usage:
299
+ GPUTextureUsage.TEXTURE_BINDING |
300
+ GPUTextureUsage.COPY_DST |
301
+ GPUTextureUsage.RENDER_ATTACHMENT,
302
+ });
303
+
304
+ device.queue.copyExternalImageToTexture(
305
+ { source: imageBitmap },
306
+ { texture: moonTexture },
307
+ [imageBitmap.width, imageBitmap.height],
308
+ );
309
+ }
310
+
311
+ const sampler = device.createSampler({
312
+ magFilter: "linear",
313
+ minFilter: "linear",
314
+ });
315
+
316
+ // Helper functions to create the required meshes and bind groups for each sphere.
317
+ function createSphereRenderable(
318
+ radius: number,
319
+ widthSegments = 32,
320
+ heightSegments = 16,
321
+ randomness = 0,
322
+ ): Renderable {
323
+ const sphereMesh = createSphereMesh(
324
+ radius,
325
+ widthSegments,
326
+ heightSegments,
327
+ randomness,
328
+ );
329
+
330
+ // Create a vertex buffer from the sphere data.
331
+ const vertices = device.createBuffer({
332
+ size: sphereMesh.vertices.byteLength,
333
+ usage: GPUBufferUsage.VERTEX,
334
+ mappedAtCreation: true,
335
+ });
336
+ new Float32Array(vertices.getMappedRange()).set(sphereMesh.vertices);
337
+ vertices.unmap();
338
+
339
+ const indices = device.createBuffer({
340
+ size: sphereMesh.indices.byteLength,
341
+ usage: GPUBufferUsage.INDEX,
342
+ mappedAtCreation: true,
343
+ });
344
+ new Uint16Array(indices.getMappedRange()).set(sphereMesh.indices);
345
+ indices.unmap();
346
+
347
+ return {
348
+ vertices,
349
+ indices,
350
+ indexCount: sphereMesh.indices.length,
351
+ };
352
+ }
353
+
354
+ function createSphereBindGroup(
355
+ texture: GPUTexture,
356
+ transform: Float32Array,
357
+ ): GPUBindGroup {
358
+ // eslint-disable-next-line @typescript-eslint/no-shadow
359
+ const uniformBufferSize = 4 * 16; // 4x4 matrix
360
+ // eslint-disable-next-line @typescript-eslint/no-shadow
361
+ const uniformBuffer = device.createBuffer({
362
+ size: uniformBufferSize,
363
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
364
+ mappedAtCreation: true,
365
+ });
366
+ new Float32Array(uniformBuffer.getMappedRange()).set(transform);
367
+ uniformBuffer.unmap();
368
+
369
+ const bindGroup = device.createBindGroup({
370
+ layout: pipeline.getBindGroupLayout(1),
371
+ entries: [
372
+ {
373
+ binding: 0,
374
+ resource: {
375
+ buffer: uniformBuffer,
376
+ },
377
+ },
378
+ {
379
+ binding: 1,
380
+ resource: sampler,
381
+ },
382
+ {
383
+ binding: 2,
384
+ resource: texture.createView(),
385
+ },
386
+ ],
387
+ });
388
+
389
+ return bindGroup;
390
+ }
391
+
392
+ const transform = mat4.create();
393
+ mat4.identity(transform);
394
+
395
+ // Create one large central planet surrounded by a large ring of asteroids
396
+ const planet = createSphereRenderable(1.0);
397
+ planet.bindGroup = createSphereBindGroup(planetTexture, transform);
398
+
399
+ const asteroids = [
400
+ createSphereRenderable(0.01, 8, 6, 0.15),
401
+ createSphereRenderable(0.013, 8, 6, 0.15),
402
+ createSphereRenderable(0.017, 8, 6, 0.15),
403
+ createSphereRenderable(0.02, 8, 6, 0.15),
404
+ createSphereRenderable(0.03, 16, 8, 0.15),
405
+ ];
406
+
407
+ const renderables = [planet];
408
+
409
+ function ensureEnoughAsteroids() {
410
+ for (let i = renderables.length; i <= asteroidCount; ++i) {
411
+ // Place copies of the asteroid in a ring.
412
+ const radius = random() * 1.7 + 1.25;
413
+ const angle = random() * Math.PI * 2;
414
+ const x = Math.sin(angle) * radius;
415
+ const y = (random() - 0.5) * 0.015;
416
+ const z = Math.cos(angle) * radius;
417
+
418
+ mat4.identity(transform);
419
+ mat4.translate(transform, [x, y, z], transform);
420
+ mat4.rotateX(transform, random() * Math.PI, transform);
421
+ mat4.rotateY(transform, random() * Math.PI, transform);
422
+ renderables.push({
423
+ ...asteroids[i % asteroids.length],
424
+ bindGroup: createSphereBindGroup(moonTexture, transform),
425
+ });
426
+ }
427
+ }
428
+ ensureEnoughAsteroids();
429
+
430
+ const renderPassDescriptor: GPURenderPassDescriptor = {
431
+ // @ts-expect-error
432
+ colorAttachments: [
433
+ {
434
+ view: undefined, // Assigned later
435
+
436
+ clearValue: [0, 0, 0, 1],
437
+ loadOp: "clear",
438
+ storeOp: "store",
439
+ },
440
+ ],
441
+ depthStencilAttachment: {
442
+ view: depthTexture.createView(),
443
+
444
+ depthClearValue: 1.0,
445
+ depthLoadOp: "clear",
446
+ depthStoreOp: "store",
447
+ },
448
+ };
449
+
450
+ const aspect = ctx.width / ctx.height;
451
+ const projectionMatrix = mat4.perspective(
452
+ (2 * Math.PI) / 5,
453
+ aspect,
454
+ 1,
455
+ 100.0,
456
+ );
457
+ const modelViewProjectionMatrix = mat4.create();
458
+
459
+ const frameBindGroup = device.createBindGroup({
460
+ layout: pipeline.getBindGroupLayout(0),
461
+ entries: [
462
+ {
463
+ binding: 0,
464
+ resource: {
465
+ buffer: uniformBuffer,
466
+ },
467
+ },
468
+ ],
469
+ });
470
+
471
+ function getTransformationMatrix() {
472
+ const viewMatrix = mat4.identity();
473
+ mat4.translate(viewMatrix, vec3.fromValues(0, 0, -4), viewMatrix);
474
+ const now = 1721766068905;
475
+ // Tilt the view matrix so the planet looks like it's off-axis.
476
+ mat4.rotateZ(viewMatrix, Math.PI * 0.1, viewMatrix);
477
+ mat4.rotateX(viewMatrix, Math.PI * 0.1, viewMatrix);
478
+ // Rotate the view matrix slowly so the planet appears to spin.
479
+ mat4.rotateY(viewMatrix, now * 0.05, viewMatrix);
480
+
481
+ mat4.multiply(
482
+ projectionMatrix,
483
+ viewMatrix,
484
+ modelViewProjectionMatrix,
485
+ );
486
+
487
+ return modelViewProjectionMatrix;
488
+ }
489
+
490
+ // Render bundles function as partial, limited render passes, so we can use the
491
+ // same code both to render the scene normally and to build the render bundle.
492
+ function renderScene(
493
+ passEncoder: GPURenderPassEncoder | GPURenderBundleEncoder,
494
+ ) {
495
+ passEncoder.setPipeline(pipeline);
496
+ passEncoder.setBindGroup(0, frameBindGroup);
497
+
498
+ // Loop through every renderable object and draw them individually.
499
+ // (Because many of these meshes are repeated, with only the transforms
500
+ // differing, instancing would be highly effective here. This sample
501
+ // intentionally avoids using instancing in order to emulate a more complex
502
+ // scene, which helps demonstrate the potential time savings a render bundle
503
+ // can provide.)
504
+ let count = 0;
505
+ for (const renderable of renderables) {
506
+ passEncoder.setBindGroup(1, renderable.bindGroup!);
507
+ passEncoder.setVertexBuffer(0, renderable.vertices);
508
+ passEncoder.setIndexBuffer(renderable.indices, "uint16");
509
+ passEncoder.drawIndexed(renderable.indexCount);
510
+
511
+ if (++count > asteroidCount) {
512
+ break;
513
+ }
514
+ }
515
+ }
516
+
517
+ // The render bundle can be encoded once and re-used as many times as needed.
518
+ // Because it encodes all of the commands needed to render at the GPU level,
519
+ // those commands will not need to execute the associated JavaScript code upon
520
+ // execution or be re-validated, which can represent a significant time savings.
521
+ //
522
+ // However, because render bundles are immutable once created, they are only
523
+ // appropriate for rendering content where the same commands will be executed
524
+ // every time, with the only changes being the contents of the buffers and
525
+ // textures used. Cases where the executed commands differ from frame-to-frame,
526
+ // such as when using frustrum or occlusion culling, will not benefit from
527
+ // using render bundles as much.
528
+ let renderBundle: GPURenderBundle;
529
+ function updateRenderBundle() {
530
+ const renderBundleEncoder = device.createRenderBundleEncoder({
531
+ colorFormats: [presentationFormat],
532
+ depthStencilFormat: "depth24plus",
533
+ });
534
+ renderScene(renderBundleEncoder);
535
+ renderBundle = renderBundleEncoder.finish();
536
+ }
537
+ updateRenderBundle();
538
+
539
+ function frame() {
540
+ const transformationMatrix = getTransformationMatrix();
541
+ device.queue.writeBuffer(
542
+ uniformBuffer,
543
+ 0,
544
+ transformationMatrix.buffer,
545
+ transformationMatrix.byteOffset,
546
+ transformationMatrix.byteLength,
547
+ );
548
+ // @ts-expect-error
549
+ renderPassDescriptor.colorAttachments[0].view = ctx
550
+ .getCurrentTexture()
551
+ .createView();
552
+
553
+ const commandEncoder = device.createCommandEncoder();
554
+ const passEncoder =
555
+ commandEncoder.beginRenderPass(renderPassDescriptor);
556
+
557
+ if (useRenderBundles) {
558
+ // Executing a bundle is equivalent to calling all of the commands encoded
559
+ // in the render bundle as part of the current render pass.
560
+ passEncoder.executeBundles([renderBundle]);
561
+ } else {
562
+ // Alternatively, the same render commands can be encoded manually, which
563
+ // can take longer since each command needs to be interpreted by the
564
+ // JavaScript virtual machine and re-validated each time.
565
+ renderScene(passEncoder);
566
+ }
567
+
568
+ passEncoder.end();
569
+ device.queue.submit([commandEncoder.finish()]);
570
+ }
571
+ frame();
572
+ return ctx.getImageData();
573
+ },
574
+ { meshWGSL: mesh, vals: randomValues },
575
+ );
576
+ const image = encodeImage(result);
577
+ checkImage(image, "snapshots/asteroid.png", { maxPixelDiff: 500 });
578
+ });
579
+ });