@multiplekex/shallot 0.2.5 → 0.3.0

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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/core/component.ts +1 -1
  3. package/src/core/index.ts +1 -13
  4. package/src/core/math.ts +186 -0
  5. package/src/core/state.ts +1 -1
  6. package/src/core/xml.ts +56 -41
  7. package/src/extras/orbit/index.ts +1 -1
  8. package/src/extras/text/index.ts +10 -65
  9. package/src/extras/{water.ts → water/index.ts} +59 -4
  10. package/src/standard/raster/batch.ts +149 -0
  11. package/src/standard/raster/forward.ts +832 -0
  12. package/src/standard/raster/index.ts +146 -472
  13. package/src/standard/raster/shadow.ts +408 -0
  14. package/src/standard/raytracing/bvh/blas.ts +335 -87
  15. package/src/standard/raytracing/bvh/radix.ts +225 -228
  16. package/src/standard/raytracing/bvh/refit.ts +711 -0
  17. package/src/standard/raytracing/bvh/structs.ts +0 -55
  18. package/src/standard/raytracing/bvh/tlas.ts +153 -141
  19. package/src/standard/raytracing/bvh/traverse.ts +72 -64
  20. package/src/standard/raytracing/index.ts +233 -204
  21. package/src/standard/raytracing/instance.ts +30 -18
  22. package/src/standard/raytracing/ray.ts +1 -1
  23. package/src/standard/raytracing/shaders.ts +23 -40
  24. package/src/standard/render/camera.ts +10 -28
  25. package/src/standard/render/data.ts +1 -1
  26. package/src/standard/render/index.ts +68 -12
  27. package/src/standard/render/light.ts +2 -2
  28. package/src/standard/render/mesh.ts +404 -0
  29. package/src/standard/render/overlay.ts +5 -2
  30. package/src/standard/render/postprocess.ts +263 -267
  31. package/src/standard/render/surface/index.ts +81 -12
  32. package/src/standard/render/surface/shaders.ts +265 -11
  33. package/src/standard/render/surface/structs.ts +10 -0
  34. package/src/standard/tween/tween.ts +44 -115
  35. package/src/standard/render/mesh/box.ts +0 -20
  36. package/src/standard/render/mesh/index.ts +0 -315
  37. package/src/standard/render/mesh/plane.ts +0 -11
  38. package/src/standard/render/mesh/sphere.ts +0 -40
  39. package/src/standard/render/mesh/unified.ts +0 -96
  40. package/src/standard/render/surface/compile.ts +0 -65
  41. package/src/standard/render/surface/noise.ts +0 -58
@@ -0,0 +1,711 @@
1
+ import type { ComputeNode, ExecutionContext } from "../../compute";
2
+ import type { BLASAtlas } from "./blas";
3
+ import type { SurfaceData } from "../../render/surface";
4
+ import { compileVertexBody } from "../../render/surface/shaders";
5
+ import { SCENE_STRUCT_WGSL, SKY_STRUCT_WGSL } from "../../render/surface/structs";
6
+ import {
7
+ BLAS_TRIANGLE_STRUCT_WGSL,
8
+ TREE_NODE_STRUCT_WGSL,
9
+ BVH_NODE_STRUCT_WGSL,
10
+ LEAF_FLAG_WGSL,
11
+ } from "./structs";
12
+
13
+ const WORKGROUP_SIZE = 64;
14
+
15
+ const OCT_DECODE_WGSL = /* wgsl */ `
16
+ fn octDecode(enc: u32) -> vec3<f32> {
17
+ let x = f32(enc & 0xFFFFu) / 65535.0 * 2.0 - 1.0;
18
+ let y = f32(enc >> 16u) / 65535.0 * 2.0 - 1.0;
19
+ let z = 1.0 - abs(x) - abs(y);
20
+ var n: vec3<f32>;
21
+ if (z < 0.0) {
22
+ let signX = select(-1.0, 1.0, x >= 0.0);
23
+ let signY = select(-1.0, 1.0, y >= 0.0);
24
+ n = vec3<f32>((1.0 - abs(y)) * signX, (1.0 - abs(x)) * signY, z);
25
+ } else {
26
+ n = vec3<f32>(x, y, z);
27
+ }
28
+ return normalize(n);
29
+ }`;
30
+
31
+ function compileDisplacementShader(vertexCode: string): string {
32
+ const vertexBody = compileVertexBody(vertexCode);
33
+
34
+ return /* wgsl */ `
35
+ ${SCENE_STRUCT_WGSL}
36
+ ${SKY_STRUCT_WGSL}
37
+ ${BLAS_TRIANGLE_STRUCT_WGSL}
38
+ ${OCT_DECODE_WGSL}
39
+
40
+ struct Params {
41
+ triOffset: u32,
42
+ triCount: u32,
43
+ _pad0: u32,
44
+ _pad1: u32,
45
+ }
46
+
47
+ @group(0) @binding(0) var<uniform> scene: Scene;
48
+ @group(0) @binding(1) var<uniform> sky: Sky;
49
+ @group(0) @binding(2) var<storage, read> baseTriangles: array<BLASTriangle>;
50
+ @group(0) @binding(3) var<storage, read_write> triangles: array<BLASTriangle>;
51
+ @group(0) @binding(4) var<uniform> params: Params;
52
+
53
+ fn displaceVertex(worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
54
+ ${vertexBody}
55
+ }
56
+
57
+ @compute @workgroup_size(${WORKGROUP_SIZE})
58
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
59
+ if (gid.x >= params.triCount) { return; }
60
+ let idx = params.triOffset + gid.x;
61
+ let base = baseTriangles[idx];
62
+
63
+ let v0 = base.v0;
64
+ let v1 = v0 + base.e1;
65
+ let v2 = v0 + base.e2;
66
+ let n0 = octDecode(base.n0_enc);
67
+ let n1 = octDecode(base.n1_enc);
68
+ let n2 = octDecode(base.n2_enc);
69
+
70
+ let dv0 = displaceVertex(v0, n0, 0u);
71
+ let dv1 = displaceVertex(v1, n1, 0u);
72
+ let dv2 = displaceVertex(v2, n2, 0u);
73
+
74
+ var tri: BLASTriangle;
75
+ tri.v0 = dv0;
76
+ tri.e1 = dv1 - dv0;
77
+ tri.e2 = dv2 - dv0;
78
+ tri._pad0 = 0u;
79
+ tri._pad1 = 0u;
80
+ tri._pad2 = 0u;
81
+ tri.n0_enc = base.n0_enc;
82
+ tri.n1_enc = base.n1_enc;
83
+ tri.n2_enc = base.n2_enc;
84
+ tri._pad3 = 0u;
85
+ triangles[idx] = tri;
86
+ }
87
+ `;
88
+ }
89
+
90
+ const propagateShader = /* wgsl */ `
91
+ ${BLAS_TRIANGLE_STRUCT_WGSL}
92
+ ${LEAF_FLAG_WGSL}
93
+
94
+ struct PropagateParams {
95
+ triOffset: u32,
96
+ triIdOffset: u32,
97
+ treeNodeOffset: u32,
98
+ parentOffset: u32,
99
+ triCount: u32,
100
+ _pad0: u32,
101
+ _pad1: u32,
102
+ _pad2: u32,
103
+ }
104
+
105
+ @group(0) @binding(0) var<storage, read> triangles: array<BLASTriangle>;
106
+ @group(0) @binding(1) var<storage, read> triIds: array<u32>;
107
+ @group(0) @binding(2) var<storage, read_write> treeNodesRaw: array<atomic<u32>>;
108
+ @group(0) @binding(3) var<storage, read_write> boundsFlags: array<atomic<u32>>;
109
+ @group(0) @binding(4) var<storage, read> parentIndices: array<u32>;
110
+ @group(0) @binding(5) var<uniform> params: PropagateParams;
111
+
112
+ fn isLeaf(child: u32) -> bool {
113
+ return (child & LEAF_FLAG) != 0u;
114
+ }
115
+
116
+ fn leafIndex(child: u32) -> u32 {
117
+ return child & ~LEAF_FLAG;
118
+ }
119
+
120
+ fn getTriangleBounds(leafIdx: u32) -> array<vec3<f32>, 2> {
121
+ let triId = triIds[params.triIdOffset + leafIdx];
122
+ let tri = triangles[params.triOffset + triId];
123
+ let v0 = tri.v0;
124
+ let v1 = v0 + tri.e1;
125
+ let v2 = v0 + tri.e2;
126
+ return array<vec3<f32>, 2>(min(min(v0, v1), v2), max(max(v0, v1), v2));
127
+ }
128
+
129
+ fn nodeBase(idx: u32) -> u32 {
130
+ return (params.treeNodeOffset + idx) * 8u;
131
+ }
132
+
133
+ fn readChildBounds(childIdx: u32) -> array<vec3<f32>, 2> {
134
+ let base = nodeBase(childIdx);
135
+ let minX = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 0u]));
136
+ let minY = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 1u]));
137
+ let minZ = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 2u]));
138
+ let maxX = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 4u]));
139
+ let maxY = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 5u]));
140
+ let maxZ = bitcast<f32>(atomicLoad(&treeNodesRaw[base + 6u]));
141
+ return array<vec3<f32>, 2>(vec3(minX, minY, minZ), vec3(maxX, maxY, maxZ));
142
+ }
143
+
144
+ fn writeBounds(nodeIdx: u32, minB: vec3<f32>, maxB: vec3<f32>) {
145
+ let base = nodeBase(nodeIdx);
146
+ atomicStore(&treeNodesRaw[base + 0u], bitcast<u32>(minB.x));
147
+ atomicStore(&treeNodesRaw[base + 1u], bitcast<u32>(minB.y));
148
+ atomicStore(&treeNodesRaw[base + 2u], bitcast<u32>(minB.z));
149
+ atomicStore(&treeNodesRaw[base + 4u], bitcast<u32>(maxB.x));
150
+ atomicStore(&treeNodesRaw[base + 5u], bitcast<u32>(maxB.y));
151
+ atomicStore(&treeNodesRaw[base + 6u], bitcast<u32>(maxB.z));
152
+ }
153
+
154
+ fn readLeftChild(nodeIdx: u32) -> u32 {
155
+ return atomicLoad(&treeNodesRaw[nodeBase(nodeIdx) + 3u]);
156
+ }
157
+
158
+ fn readRightChild(nodeIdx: u32) -> u32 {
159
+ return atomicLoad(&treeNodesRaw[nodeBase(nodeIdx) + 7u]);
160
+ }
161
+
162
+ fn writeLeafBounds(leafIdx: u32, minB: vec3<f32>, maxB: vec3<f32>) {
163
+ let n = params.triCount;
164
+ let leafNodeIdx = n - 1u + leafIdx;
165
+ writeBounds(leafNodeIdx, minB, maxB);
166
+ }
167
+
168
+ fn getParent(nodeIdx: u32, isLeafNode: bool) -> u32 {
169
+ let n = params.triCount;
170
+ if (isLeafNode) {
171
+ return parentIndices[params.parentOffset + nodeIdx];
172
+ } else {
173
+ return parentIndices[params.parentOffset + n + nodeIdx];
174
+ }
175
+ }
176
+
177
+ @compute @workgroup_size(${WORKGROUP_SIZE})
178
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
179
+ let n = params.triCount;
180
+ let leafIdx = gid.x;
181
+
182
+ if (leafIdx >= n) { return; }
183
+
184
+ let bounds = getTriangleBounds(leafIdx);
185
+ writeLeafBounds(leafIdx, bounds[0], bounds[1]);
186
+
187
+ if (n <= 1u) { return; }
188
+
189
+ var current = leafIdx;
190
+ var isLeafNode = true;
191
+
192
+ for (var iter = 0u; iter < 64u; iter++) {
193
+ let parent = getParent(current, isLeafNode);
194
+
195
+ let oldFlag = atomicAdd(&boundsFlags[params.treeNodeOffset + parent], 1u);
196
+ if (oldFlag == 0u) { return; }
197
+
198
+ let left = readLeftChild(parent);
199
+ let right = readRightChild(parent);
200
+
201
+ var leftMin: vec3<f32>;
202
+ var leftMax: vec3<f32>;
203
+ var rightMin: vec3<f32>;
204
+ var rightMax: vec3<f32>;
205
+
206
+ if (isLeaf(left)) {
207
+ let leftBounds = getTriangleBounds(leafIndex(left));
208
+ leftMin = leftBounds[0];
209
+ leftMax = leftBounds[1];
210
+ } else {
211
+ let leftBounds = readChildBounds(left);
212
+ leftMin = leftBounds[0];
213
+ leftMax = leftBounds[1];
214
+ }
215
+
216
+ if (isLeaf(right)) {
217
+ let rightBounds = getTriangleBounds(leafIndex(right));
218
+ rightMin = rightBounds[0];
219
+ rightMax = rightBounds[1];
220
+ } else {
221
+ let rightBounds = readChildBounds(right);
222
+ rightMin = rightBounds[0];
223
+ rightMax = rightBounds[1];
224
+ }
225
+
226
+ writeBounds(parent, min(leftMin, rightMin), max(leftMax, rightMax));
227
+
228
+ current = parent;
229
+ isLeafNode = false;
230
+
231
+ if (parent == 0u) { break; }
232
+ }
233
+ }
234
+ `;
235
+
236
+ const collapseShader = /* wgsl */ `
237
+ ${TREE_NODE_STRUCT_WGSL}
238
+ ${BVH_NODE_STRUCT_WGSL}
239
+ ${LEAF_FLAG_WGSL}
240
+
241
+ const INVALID_NODE: u32 = 0xFFFFFFFFu;
242
+ const MAX_DEPTH_ITERS: u32 = 32u;
243
+
244
+ struct CollapseParams {
245
+ treeNodeOffset: u32,
246
+ parentOffset: u32,
247
+ nodeOffset: u32,
248
+ triCount: u32,
249
+ }
250
+
251
+ @group(0) @binding(0) var<storage, read> treeNodes: array<TreeNode>;
252
+ @group(0) @binding(1) var<storage, read> parentIndices: array<u32>;
253
+ @group(0) @binding(2) var<storage, read_write> bvhNodes: array<BVHNode>;
254
+ @group(0) @binding(3) var<uniform> params: CollapseParams;
255
+
256
+ fn isLeaf(child: u32) -> bool {
257
+ return (child & LEAF_FLAG) != 0u;
258
+ }
259
+
260
+ fn leafIndex(child: u32) -> u32 {
261
+ return child & ~LEAF_FLAG;
262
+ }
263
+
264
+ fn getDepth(nodeIdx: u32) -> u32 {
265
+ let n = params.triCount;
266
+ var depth = 0u;
267
+ var current = nodeIdx;
268
+ for (var iter = 0u; iter < MAX_DEPTH_ITERS; iter++) {
269
+ if (current == 0u) { break; }
270
+ current = parentIndices[params.parentOffset + n + current];
271
+ depth++;
272
+ }
273
+ return depth;
274
+ }
275
+
276
+ fn getChildBounds(child: u32) -> array<vec3<f32>, 2> {
277
+ let n = params.triCount;
278
+ if (isLeaf(child)) {
279
+ let leafNodeIdx = n - 1u + leafIndex(child);
280
+ let node = treeNodes[params.treeNodeOffset + leafNodeIdx];
281
+ return array<vec3<f32>, 2>(
282
+ vec3<f32>(node.minX, node.minY, node.minZ),
283
+ vec3<f32>(node.maxX, node.maxY, node.maxZ)
284
+ );
285
+ } else {
286
+ let node = treeNodes[params.treeNodeOffset + child];
287
+ return array<vec3<f32>, 2>(
288
+ vec3<f32>(node.minX, node.minY, node.minZ),
289
+ vec3<f32>(node.maxX, node.maxY, node.maxZ)
290
+ );
291
+ }
292
+ }
293
+
294
+ @compute @workgroup_size(${WORKGROUP_SIZE})
295
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
296
+ let n = params.triCount;
297
+ let nodeIdx = gid.x;
298
+
299
+ if (n == 1u) {
300
+ if (nodeIdx == 0u) {
301
+ var out: BVHNode;
302
+ out.child0 = 0u | LEAF_FLAG;
303
+ out.child1 = INVALID_NODE;
304
+ out.child2 = INVALID_NODE;
305
+ out.child3 = INVALID_NODE;
306
+
307
+ let bounds = getChildBounds(0u | LEAF_FLAG);
308
+ out.c0_minX = bounds[0].x; out.c0_minY = bounds[0].y; out.c0_minZ = bounds[0].z;
309
+ out.c0_maxX = bounds[1].x; out.c0_maxY = bounds[1].y; out.c0_maxZ = bounds[1].z;
310
+ out.c1_minX = 1e30; out.c1_minY = 1e30; out.c1_minZ = 1e30;
311
+ out.c1_maxX = -1e30; out.c1_maxY = -1e30; out.c1_maxZ = -1e30;
312
+ out.c2_minX = 1e30; out.c2_minY = 1e30; out.c2_minZ = 1e30;
313
+ out.c2_maxX = -1e30; out.c2_maxY = -1e30; out.c2_maxZ = -1e30;
314
+ out.c3_minX = 1e30; out.c3_minY = 1e30; out.c3_minZ = 1e30;
315
+ out.c3_maxX = -1e30; out.c3_maxY = -1e30; out.c3_maxZ = -1e30;
316
+
317
+ bvhNodes[params.nodeOffset] = out;
318
+ }
319
+ return;
320
+ }
321
+
322
+ if (nodeIdx >= n - 1u) { return; }
323
+
324
+ let depth = getDepth(nodeIdx);
325
+ let node = treeNodes[params.treeNodeOffset + nodeIdx];
326
+ let left = node.leftChild;
327
+ let right = node.rightChild;
328
+
329
+ var out: BVHNode;
330
+ out.child0 = INVALID_NODE; out.child1 = INVALID_NODE;
331
+ out.child2 = INVALID_NODE; out.child3 = INVALID_NODE;
332
+ out.c0_minX = 1e30; out.c0_minY = 1e30; out.c0_minZ = 1e30;
333
+ out.c0_maxX = -1e30; out.c0_maxY = -1e30; out.c0_maxZ = -1e30;
334
+ out.c1_minX = 1e30; out.c1_minY = 1e30; out.c1_minZ = 1e30;
335
+ out.c1_maxX = -1e30; out.c1_maxY = -1e30; out.c1_maxZ = -1e30;
336
+ out.c2_minX = 1e30; out.c2_minY = 1e30; out.c2_minZ = 1e30;
337
+ out.c2_maxX = -1e30; out.c2_maxY = -1e30; out.c2_maxZ = -1e30;
338
+ out.c3_minX = 1e30; out.c3_minY = 1e30; out.c3_minZ = 1e30;
339
+ out.c3_maxX = -1e30; out.c3_maxY = -1e30; out.c3_maxZ = -1e30;
340
+
341
+ if ((depth & 1u) != 0u) {
342
+ out.child0 = left;
343
+ let bounds0 = getChildBounds(left);
344
+ out.c0_minX = bounds0[0].x; out.c0_minY = bounds0[0].y; out.c0_minZ = bounds0[0].z;
345
+ out.c0_maxX = bounds0[1].x; out.c0_maxY = bounds0[1].y; out.c0_maxZ = bounds0[1].z;
346
+
347
+ out.child1 = right;
348
+ let bounds1 = getChildBounds(right);
349
+ out.c1_minX = bounds1[0].x; out.c1_minY = bounds1[0].y; out.c1_minZ = bounds1[0].z;
350
+ out.c1_maxX = bounds1[1].x; out.c1_maxY = bounds1[1].y; out.c1_maxZ = bounds1[1].z;
351
+
352
+ bvhNodes[params.nodeOffset + nodeIdx] = out;
353
+ return;
354
+ }
355
+
356
+ if (isLeaf(left)) {
357
+ out.child0 = left;
358
+ let bounds = getChildBounds(left);
359
+ out.c0_minX = bounds[0].x; out.c0_minY = bounds[0].y; out.c0_minZ = bounds[0].z;
360
+ out.c0_maxX = bounds[1].x; out.c0_maxY = bounds[1].y; out.c0_maxZ = bounds[1].z;
361
+ } else {
362
+ let leftNode = treeNodes[params.treeNodeOffset + left];
363
+ let ll = leftNode.leftChild;
364
+ let lr = leftNode.rightChild;
365
+
366
+ out.child0 = ll;
367
+ let bounds0 = getChildBounds(ll);
368
+ out.c0_minX = bounds0[0].x; out.c0_minY = bounds0[0].y; out.c0_minZ = bounds0[0].z;
369
+ out.c0_maxX = bounds0[1].x; out.c0_maxY = bounds0[1].y; out.c0_maxZ = bounds0[1].z;
370
+
371
+ out.child1 = lr;
372
+ let bounds1 = getChildBounds(lr);
373
+ out.c1_minX = bounds1[0].x; out.c1_minY = bounds1[0].y; out.c1_minZ = bounds1[0].z;
374
+ out.c1_maxX = bounds1[1].x; out.c1_maxY = bounds1[1].y; out.c1_maxZ = bounds1[1].z;
375
+ }
376
+
377
+ if (isLeaf(right)) {
378
+ out.child2 = right;
379
+ let bounds = getChildBounds(right);
380
+ out.c2_minX = bounds[0].x; out.c2_minY = bounds[0].y; out.c2_minZ = bounds[0].z;
381
+ out.c2_maxX = bounds[1].x; out.c2_maxY = bounds[1].y; out.c2_maxZ = bounds[1].z;
382
+ } else {
383
+ let rightNode = treeNodes[params.treeNodeOffset + right];
384
+ let rl = rightNode.leftChild;
385
+ let rr = rightNode.rightChild;
386
+
387
+ out.child2 = rl;
388
+ let bounds2 = getChildBounds(rl);
389
+ out.c2_minX = bounds2[0].x; out.c2_minY = bounds2[0].y; out.c2_minZ = bounds2[0].z;
390
+ out.c2_maxX = bounds2[1].x; out.c2_maxY = bounds2[1].y; out.c2_maxZ = bounds2[1].z;
391
+
392
+ out.child3 = rr;
393
+ let bounds3 = getChildBounds(rr);
394
+ out.c3_minX = bounds3[0].x; out.c3_minY = bounds3[0].y; out.c3_minZ = bounds3[0].z;
395
+ out.c3_maxX = bounds3[1].x; out.c3_maxY = bounds3[1].y; out.c3_maxZ = bounds3[1].z;
396
+ }
397
+
398
+ bvhNodes[params.nodeOffset + nodeIdx] = out;
399
+ }
400
+ `;
401
+
402
+ const aabbShader = /* wgsl */ `
403
+ struct ShapeAABB {
404
+ minX: f32, minY: f32, minZ: f32, _pad0: u32,
405
+ maxX: f32, maxY: f32, maxZ: f32, _pad1: u32,
406
+ }
407
+
408
+ ${TREE_NODE_STRUCT_WGSL}
409
+
410
+ struct AABBParams {
411
+ treeNodeOffset: u32,
412
+ shapeId: u32,
413
+ _pad0: u32,
414
+ _pad1: u32,
415
+ }
416
+
417
+ @group(0) @binding(0) var<storage, read> treeNodes: array<TreeNode>;
418
+ @group(0) @binding(1) var<storage, read_write> shapeAABBs: array<ShapeAABB>;
419
+ @group(0) @binding(2) var<uniform> params: AABBParams;
420
+
421
+ @compute @workgroup_size(1)
422
+ fn main() {
423
+ let root = treeNodes[params.treeNodeOffset];
424
+ var aabb: ShapeAABB;
425
+ aabb.minX = root.minX;
426
+ aabb.minY = root.minY;
427
+ aabb.minZ = root.minZ;
428
+ aabb._pad0 = 0u;
429
+ aabb.maxX = root.maxX;
430
+ aabb.maxY = root.maxY;
431
+ aabb.maxZ = root.maxZ;
432
+ aabb._pad1 = 0u;
433
+ shapeAABBs[params.shapeId] = aabb;
434
+ }
435
+ `;
436
+
437
+ interface DisplacementPipeline {
438
+ pipeline: GPUComputePipeline;
439
+ bindGroup: GPUBindGroup;
440
+ }
441
+
442
+ export interface BLASRefitConfig {
443
+ getBlasAtlas: () => BLASAtlas;
444
+ scene: GPUBuffer;
445
+ sky: GPUBuffer;
446
+ getDynamicShapes: () => Map<number, SurfaceData>;
447
+ getRaytracing: () => boolean;
448
+ }
449
+
450
+ interface RefitGPU {
451
+ propagatePipeline: GPUComputePipeline;
452
+ propagateLayout: GPUBindGroupLayout;
453
+ collapsePipeline: GPUComputePipeline;
454
+ collapseLayout: GPUBindGroupLayout;
455
+ aabbPipeline: GPUComputePipeline;
456
+ aabbLayout: GPUBindGroupLayout;
457
+ displacementPipelines: Map<string, DisplacementPipeline>;
458
+ displacementBindGroupLayout: GPUBindGroupLayout;
459
+ displacementPipelineLayout: GPUPipelineLayout;
460
+ displaceParams: GPUBuffer;
461
+ propagateParams: GPUBuffer;
462
+ collapseParams: GPUBuffer;
463
+ aabbParams: GPUBuffer;
464
+ propagateBindGroup: GPUBindGroup | null;
465
+ collapseBindGroup: GPUBindGroup | null;
466
+ aabbBindGroup: GPUBindGroup | null;
467
+ cachedAtlas: BLASAtlas | null;
468
+ }
469
+
470
+ export function createBLASRefitNode(config: BLASRefitConfig): ComputeNode {
471
+ let gpu: RefitGPU | null = null;
472
+
473
+ return {
474
+ id: "blas-refit",
475
+ inputs: [],
476
+ outputs: [{ id: "blas-nodes", access: "write" }],
477
+
478
+ async prepare(device: GPUDevice) {
479
+ const createParams = (label: string, size: number) =>
480
+ device.createBuffer({
481
+ label,
482
+ size,
483
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
484
+ });
485
+
486
+ const displaceParams = createParams("blas-refit-displace-params", 16);
487
+ const propagateParams = createParams("blas-refit-propagate-params", 32);
488
+ const collapseParams = createParams("blas-refit-collapse-params", 16);
489
+ const aabbParams = createParams("blas-refit-aabb-params", 16);
490
+
491
+ const displacementBindGroupLayout = device.createBindGroupLayout({
492
+ entries: [
493
+ { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
494
+ { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
495
+ {
496
+ binding: 2,
497
+ visibility: GPUShaderStage.COMPUTE,
498
+ buffer: { type: "read-only-storage" },
499
+ },
500
+ { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
501
+ { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
502
+ ],
503
+ });
504
+ const displacementPipelineLayout = device.createPipelineLayout({
505
+ bindGroupLayouts: [displacementBindGroupLayout],
506
+ });
507
+
508
+ const propagateModule = device.createShaderModule({ code: propagateShader });
509
+ const propagatePipeline = await device.createComputePipelineAsync({
510
+ layout: "auto",
511
+ compute: { module: propagateModule, entryPoint: "main" },
512
+ });
513
+
514
+ const collapseModule = device.createShaderModule({ code: collapseShader });
515
+ const collapsePipeline = await device.createComputePipelineAsync({
516
+ layout: "auto",
517
+ compute: { module: collapseModule, entryPoint: "main" },
518
+ });
519
+
520
+ const aabbModule = device.createShaderModule({ code: aabbShader });
521
+ const aabbPipeline = await device.createComputePipelineAsync({
522
+ layout: "auto",
523
+ compute: { module: aabbModule, entryPoint: "main" },
524
+ });
525
+
526
+ gpu = {
527
+ propagatePipeline,
528
+ propagateLayout: propagatePipeline.getBindGroupLayout(0),
529
+ collapsePipeline,
530
+ collapseLayout: collapsePipeline.getBindGroupLayout(0),
531
+ aabbPipeline,
532
+ aabbLayout: aabbPipeline.getBindGroupLayout(0),
533
+ displacementPipelines: new Map(),
534
+ displacementBindGroupLayout,
535
+ displacementPipelineLayout,
536
+ displaceParams,
537
+ propagateParams,
538
+ collapseParams,
539
+ aabbParams,
540
+ propagateBindGroup: null,
541
+ collapseBindGroup: null,
542
+ aabbBindGroup: null,
543
+ cachedAtlas: null,
544
+ };
545
+ },
546
+
547
+ execute(ctx: ExecutionContext) {
548
+ if (!gpu) return;
549
+ if (!config.getRaytracing()) return;
550
+
551
+ const dynamicShapes = config.getDynamicShapes();
552
+ if (dynamicShapes.size === 0) return;
553
+
554
+ const { device, encoder } = ctx;
555
+ const atlas = config.getBlasAtlas();
556
+
557
+ if (atlas !== gpu.cachedAtlas) {
558
+ gpu.displacementPipelines.clear();
559
+
560
+ gpu.propagateBindGroup = device.createBindGroup({
561
+ layout: gpu.propagateLayout,
562
+ entries: [
563
+ { binding: 0, resource: { buffer: atlas.trianglesBuffer } },
564
+ { binding: 1, resource: { buffer: atlas.triIdsBuffer } },
565
+ { binding: 2, resource: { buffer: atlas.treeNodesBuffer } },
566
+ { binding: 3, resource: { buffer: atlas.boundsFlagsBuffer } },
567
+ { binding: 4, resource: { buffer: atlas.parentIndicesBuffer } },
568
+ { binding: 5, resource: { buffer: gpu.propagateParams } },
569
+ ],
570
+ });
571
+
572
+ gpu.collapseBindGroup = device.createBindGroup({
573
+ layout: gpu.collapseLayout,
574
+ entries: [
575
+ { binding: 0, resource: { buffer: atlas.treeNodesBuffer } },
576
+ { binding: 1, resource: { buffer: atlas.parentIndicesBuffer } },
577
+ { binding: 2, resource: { buffer: atlas.nodesBuffer } },
578
+ { binding: 3, resource: { buffer: gpu.collapseParams } },
579
+ ],
580
+ });
581
+
582
+ gpu.aabbBindGroup = device.createBindGroup({
583
+ layout: gpu.aabbLayout,
584
+ entries: [
585
+ { binding: 0, resource: { buffer: atlas.treeNodesBuffer } },
586
+ { binding: 1, resource: { buffer: atlas.shapeAABBs } },
587
+ { binding: 2, resource: { buffer: gpu.aabbParams } },
588
+ ],
589
+ });
590
+
591
+ gpu.cachedAtlas = atlas;
592
+ }
593
+
594
+ for (const [shapeId, surfaceData] of dynamicShapes) {
595
+ if (!surfaceData.vertex) continue;
596
+
597
+ const meta = atlas.metas[shapeId];
598
+ if (!meta || meta.triCount === 0) continue;
599
+
600
+ const vertexKey = surfaceData.vertex;
601
+ if (!gpu.displacementPipelines.has(vertexKey)) {
602
+ const code = compileDisplacementShader(vertexKey);
603
+ const module = device.createShaderModule({ code });
604
+ const pipeline = device.createComputePipeline({
605
+ layout: gpu.displacementPipelineLayout,
606
+ compute: { module, entryPoint: "main" },
607
+ });
608
+
609
+ const bindGroup = device.createBindGroup({
610
+ layout: gpu.displacementBindGroupLayout,
611
+ entries: [
612
+ { binding: 0, resource: { buffer: config.scene } },
613
+ { binding: 1, resource: { buffer: config.sky } },
614
+ { binding: 2, resource: { buffer: atlas.baseTrianglesBuffer } },
615
+ { binding: 3, resource: { buffer: atlas.trianglesBuffer } },
616
+ { binding: 4, resource: { buffer: gpu.displaceParams } },
617
+ ],
618
+ });
619
+
620
+ gpu.displacementPipelines.set(vertexKey, { pipeline, bindGroup });
621
+ }
622
+
623
+ const displacement = gpu.displacementPipelines.get(vertexKey)!;
624
+
625
+ device.queue.writeBuffer(
626
+ gpu.displaceParams,
627
+ 0,
628
+ new Uint32Array([meta.triOffset, meta.triCount, 0, 0])
629
+ );
630
+
631
+ const displacePass = encoder.beginComputePass();
632
+ displacePass.setPipeline(displacement.pipeline);
633
+ displacePass.setBindGroup(0, displacement.bindGroup);
634
+ displacePass.dispatchWorkgroups(Math.ceil(meta.triCount / WORKGROUP_SIZE));
635
+ displacePass.end();
636
+
637
+ if (meta.triCount <= 1) {
638
+ device.queue.writeBuffer(
639
+ gpu.aabbParams,
640
+ 0,
641
+ new Uint32Array([meta.treeNodeOffset, shapeId, 0, 0])
642
+ );
643
+
644
+ const aabbPass = encoder.beginComputePass();
645
+ aabbPass.setPipeline(gpu.aabbPipeline);
646
+ aabbPass.setBindGroup(0, gpu.aabbBindGroup!);
647
+ aabbPass.dispatchWorkgroups(1);
648
+ aabbPass.end();
649
+ continue;
650
+ }
651
+
652
+ const n = meta.triCount;
653
+ const internalNodes = n - 1;
654
+
655
+ const flagsByteOffset = meta.treeNodeOffset * 4;
656
+ const flagsByteSize = internalNodes * 4;
657
+ encoder.clearBuffer(atlas.boundsFlagsBuffer, flagsByteOffset, flagsByteSize);
658
+
659
+ device.queue.writeBuffer(
660
+ gpu.propagateParams,
661
+ 0,
662
+ new Uint32Array([
663
+ meta.triOffset,
664
+ meta.triIdOffset,
665
+ meta.treeNodeOffset,
666
+ meta.parentOffset,
667
+ meta.triCount,
668
+ 0,
669
+ 0,
670
+ 0,
671
+ ])
672
+ );
673
+
674
+ const propagatePass = encoder.beginComputePass();
675
+ propagatePass.setPipeline(gpu.propagatePipeline);
676
+ propagatePass.setBindGroup(0, gpu.propagateBindGroup!);
677
+ propagatePass.dispatchWorkgroups(Math.ceil(n / WORKGROUP_SIZE));
678
+ propagatePass.end();
679
+
680
+ device.queue.writeBuffer(
681
+ gpu.collapseParams,
682
+ 0,
683
+ new Uint32Array([
684
+ meta.treeNodeOffset,
685
+ meta.parentOffset,
686
+ meta.nodeOffset,
687
+ meta.triCount,
688
+ ])
689
+ );
690
+
691
+ const collapsePass = encoder.beginComputePass();
692
+ collapsePass.setPipeline(gpu.collapsePipeline);
693
+ collapsePass.setBindGroup(0, gpu.collapseBindGroup!);
694
+ collapsePass.dispatchWorkgroups(Math.ceil(internalNodes / WORKGROUP_SIZE));
695
+ collapsePass.end();
696
+
697
+ device.queue.writeBuffer(
698
+ gpu.aabbParams,
699
+ 0,
700
+ new Uint32Array([meta.treeNodeOffset, shapeId, 0, 0])
701
+ );
702
+
703
+ const aabbPass = encoder.beginComputePass();
704
+ aabbPass.setPipeline(gpu.aabbPipeline);
705
+ aabbPass.setBindGroup(0, gpu.aabbBindGroup!);
706
+ aabbPass.dispatchWorkgroups(1);
707
+ aabbPass.end();
708
+ }
709
+ },
710
+ };
711
+ }