@manycore/aholo-splat-transform 1.2.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 (88) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +33 -0
  3. package/bin/cli.js +118 -0
  4. package/dist/SplatData.d.ts +67 -0
  5. package/dist/SplatData.js +156 -0
  6. package/dist/constant.d.ts +3 -0
  7. package/dist/constant.js +13 -0
  8. package/dist/file/IFile.d.ts +5 -0
  9. package/dist/file/IFile.js +1 -0
  10. package/dist/file/index.d.ts +7 -0
  11. package/dist/file/index.js +6 -0
  12. package/dist/file/ksplat.d.ts +12 -0
  13. package/dist/file/ksplat.js +232 -0
  14. package/dist/file/lcc.d.ts +11 -0
  15. package/dist/file/lcc.js +157 -0
  16. package/dist/file/ply.d.ts +13 -0
  17. package/dist/file/ply.js +388 -0
  18. package/dist/file/sog.d.ts +80 -0
  19. package/dist/file/sog.js +504 -0
  20. package/dist/file/splat.d.ts +6 -0
  21. package/dist/file/splat.js +99 -0
  22. package/dist/file/spz.d.ts +8 -0
  23. package/dist/file/spz.js +400 -0
  24. package/dist/file/voxel.d.ts +37 -0
  25. package/dist/file/voxel.js +280 -0
  26. package/dist/index.d.ts +33 -0
  27. package/dist/index.js +54 -0
  28. package/dist/native/cpp/bin/linux/binding.node +0 -0
  29. package/dist/native/cpp/bin/windows/binding.node +0 -0
  30. package/dist/native/index.d.ts +54 -0
  31. package/dist/native/index.js +128 -0
  32. package/dist/tasks/AutoChunkLodTask.d.ts +13 -0
  33. package/dist/tasks/AutoChunkLodTask.js +117 -0
  34. package/dist/tasks/AutoLodTask.d.ts +10 -0
  35. package/dist/tasks/AutoLodTask.js +20 -0
  36. package/dist/tasks/BaseTask.d.ts +15 -0
  37. package/dist/tasks/BaseTask.js +5 -0
  38. package/dist/tasks/FlexLodTask.d.ts +12 -0
  39. package/dist/tasks/FlexLodTask.js +44 -0
  40. package/dist/tasks/ModifyTask.d.ts +9 -0
  41. package/dist/tasks/ModifyTask.js +156 -0
  42. package/dist/tasks/ReadTask.d.ts +8 -0
  43. package/dist/tasks/ReadTask.js +29 -0
  44. package/dist/tasks/SkeletonLodTask.d.ts +10 -0
  45. package/dist/tasks/SkeletonLodTask.js +156 -0
  46. package/dist/tasks/VoxelTask.d.ts +30 -0
  47. package/dist/tasks/VoxelTask.js +37 -0
  48. package/dist/tasks/WriteTask.d.ts +11 -0
  49. package/dist/tasks/WriteTask.js +70 -0
  50. package/dist/utils/BufferReader.d.ts +12 -0
  51. package/dist/utils/BufferReader.js +47 -0
  52. package/dist/utils/Logger.d.ts +11 -0
  53. package/dist/utils/Logger.js +38 -0
  54. package/dist/utils/StreamChunkDecoder.d.ts +16 -0
  55. package/dist/utils/StreamChunkDecoder.js +36 -0
  56. package/dist/utils/index.d.ts +27 -0
  57. package/dist/utils/index.js +101 -0
  58. package/dist/utils/k-means.d.ts +4 -0
  59. package/dist/utils/k-means.js +350 -0
  60. package/dist/utils/math.d.ts +46 -0
  61. package/dist/utils/math.js +351 -0
  62. package/dist/utils/quantize-1d.d.ts +4 -0
  63. package/dist/utils/quantize-1d.js +164 -0
  64. package/dist/utils/sh-rotate.d.ts +2 -0
  65. package/dist/utils/sh-rotate.js +175 -0
  66. package/dist/utils/splat.d.ts +20 -0
  67. package/dist/utils/splat.js +378 -0
  68. package/dist/utils/voxel/common.d.ts +162 -0
  69. package/dist/utils/voxel/common.js +1700 -0
  70. package/dist/utils/voxel/coplanar-merge.d.ts +63 -0
  71. package/dist/utils/voxel/coplanar-merge.js +819 -0
  72. package/dist/utils/voxel/gpu-dilation.d.ts +2 -0
  73. package/dist/utils/voxel/gpu-dilation.js +665 -0
  74. package/dist/utils/voxel/marching-cubes.d.ts +42 -0
  75. package/dist/utils/voxel/marching-cubes.js +1657 -0
  76. package/dist/utils/voxel/mesh.d.ts +3 -0
  77. package/dist/utils/voxel/mesh.js +130 -0
  78. package/dist/utils/voxel/nav.d.ts +29 -0
  79. package/dist/utils/voxel/nav.js +1043 -0
  80. package/dist/utils/voxel/postprocess.d.ts +23 -0
  81. package/dist/utils/voxel/postprocess.js +375 -0
  82. package/dist/utils/voxel/voxel-faces.d.ts +18 -0
  83. package/dist/utils/voxel/voxel-faces.js +663 -0
  84. package/dist/utils/voxel/voxelize.d.ts +33 -0
  85. package/dist/utils/voxel/voxelize.js +1193 -0
  86. package/dist/utils/webgpu.d.ts +8 -0
  87. package/dist/utils/webgpu.js +122 -0
  88. package/package.json +32 -0
@@ -0,0 +1,2 @@
1
+ import { SparseVoxelGrid } from './common.js';
2
+ export declare const gpuDilate3: (src: SparseVoxelGrid, halfExtentXZ: number, halfExtentY: number) => Promise<SparseVoxelGrid>;
@@ -0,0 +1,665 @@
1
+ import { getOrCreateDevice } from '../webgpu.js';
2
+ import { BLOCK_EMPTY, BLOCK_MIXED, BLOCK_SOLID, SparseVoxelGrid, readBlockType } from './common.js';
3
+ const GPU_BUFFER_USAGE_STORAGE = 128;
4
+ const GPU_BUFFER_USAGE_COPY_DST = 8;
5
+ const GPU_BUFFER_USAGE_COPY_SRC = 4;
6
+ const GPU_BUFFER_USAGE_UNIFORM = 64;
7
+ const GPU_BUFFER_USAGE_MAP_READ = 1;
8
+ const GPU_MAP_MODE_READ = 1;
9
+ const CHUNK_INNER = 512;
10
+ const SOLID_WORD = 0x55555555 >>> 0;
11
+ const extractWgsl = () => /* wgsl */ `
12
+ struct ExtractUniforms {
13
+ minBx: i32,
14
+ minBy: i32,
15
+ minBz: i32,
16
+ outerBx: u32,
17
+ outerBy: u32,
18
+ outerBz: u32,
19
+ numXWords: u32,
20
+ srcNbx: u32,
21
+ srcNby: u32,
22
+ srcNbz: u32,
23
+ srcBStride: u32,
24
+ srcCapMinusOne: u32
25
+ }
26
+
27
+ @group(0) @binding(0) var<uniform> u: ExtractUniforms;
28
+ @group(0) @binding(1) var<storage, read> srcTypes: array<u32>;
29
+ @group(0) @binding(2) var<storage, read> srcKeys: array<u32>;
30
+ @group(0) @binding(3) var<storage, read> srcLo: array<u32>;
31
+ @group(0) @binding(4) var<storage, read> srcHi: array<u32>;
32
+ @group(0) @binding(5) var<storage, read_write> dstDense: array<atomic<u32>>;
33
+
34
+ @compute @workgroup_size(8, 4, 8)
35
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
36
+ if (gid.x >= u.outerBx || gid.y >= u.outerBy || gid.z >= u.outerBz) { return; }
37
+
38
+ let chunkBx = i32(gid.x);
39
+ let chunkBy = i32(gid.y);
40
+ let chunkBz = i32(gid.z);
41
+ let globalBx = u.minBx + chunkBx;
42
+ let globalBy = u.minBy + chunkBy;
43
+ let globalBz = u.minBz + chunkBz;
44
+ if (globalBx < 0 || globalBy < 0 || globalBz < 0) { return; }
45
+ if (globalBx >= i32(u.srcNbx) || globalBy >= i32(u.srcNby) || globalBz >= i32(u.srcNbz)) { return; }
46
+
47
+ let blockIdx = u32(globalBx) + u32(globalBy) * u.srcNbx + u32(globalBz) * u.srcBStride;
48
+ let typeWord = srcTypes[blockIdx >> 4u];
49
+ let bt = (typeWord >> ((blockIdx & 15u) * 2u)) & 3u;
50
+ if (bt == 0u) { return; }
51
+
52
+ var lo: u32;
53
+ var hi: u32;
54
+ if (bt == 1u) {
55
+ lo = 0xFFFFFFFFu;
56
+ hi = 0xFFFFFFFFu;
57
+ } else {
58
+ var i = (blockIdx * 0x9E3779B9u) & u.srcCapMinusOne;
59
+ loop {
60
+ let k = srcKeys[i];
61
+ if (k == blockIdx) {
62
+ lo = srcLo[i];
63
+ hi = srcHi[i];
64
+ break;
65
+ }
66
+ if (k == 0xFFFFFFFFu) { return; }
67
+ i = (i + 1u) & u.srcCapMinusOne;
68
+ }
69
+ }
70
+
71
+ let dx0 = u32(chunkBx) * 4u;
72
+ let wordOffsetX = dx0 / 32u;
73
+ let bitShiftX = dx0 & 31u;
74
+ let outerNy = u.outerBy * 4u;
75
+ let planeWords = u.numXWords * outerNy;
76
+
77
+ for (var lz = 0u; lz < 4u; lz = lz + 1u) {
78
+ let dz = u32(chunkBz) * 4u + lz;
79
+ let zBitBase = (lz & 1u) * 16u;
80
+ let word = select(lo, hi, lz >= 2u);
81
+ for (var ly = 0u; ly < 4u; ly = ly + 1u) {
82
+ let dy = u32(chunkBy) * 4u + ly;
83
+ let bitBase = zBitBase + ly * 4u;
84
+ let pattern = (word >> bitBase) & 0xFu;
85
+ if (pattern == 0u) { continue; }
86
+ let wordIdx = wordOffsetX + dy * u.numXWords + dz * planeWords;
87
+ atomicOr(&dstDense[wordIdx], pattern << bitShiftX);
88
+ }
89
+ }
90
+ }
91
+ `;
92
+ const compactWgsl = () => /* wgsl */ `
93
+ struct CompactUniforms {
94
+ haloBx: u32,
95
+ haloBy: u32,
96
+ haloBz: u32,
97
+ numXWords: u32,
98
+ innerBx: u32,
99
+ innerBy: u32,
100
+ innerBz: u32,
101
+ outerBy: u32
102
+ }
103
+
104
+ @group(0) @binding(0) var<uniform> u: CompactUniforms;
105
+ @group(0) @binding(1) var<storage, read> dilatedDense: array<u32>;
106
+ @group(0) @binding(2) var<storage, read_write> typesOut: array<atomic<u32>>;
107
+ @group(0) @binding(3) var<storage, read_write> masksOut: array<u32>;
108
+
109
+ @compute @workgroup_size(8, 4, 8)
110
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
111
+ if (gid.x >= u.innerBx || gid.y >= u.innerBy || gid.z >= u.innerBz) { return; }
112
+
113
+ let innerBlockIdx = gid.x + gid.y * u.innerBx + gid.z * u.innerBx * u.innerBy;
114
+ let outerBx = gid.x + u.haloBx;
115
+ let outerBy = gid.y + u.haloBy;
116
+ let outerBz = gid.z + u.haloBz;
117
+ let dx0 = outerBx * 4u;
118
+ let wordOffsetX = dx0 / 32u;
119
+ let bitShiftX = dx0 & 31u;
120
+ let outerNy = u.outerBy * 4u;
121
+ let planeWords = u.numXWords * outerNy;
122
+
123
+ var lo = 0u;
124
+ var hi = 0u;
125
+ for (var lz = 0u; lz < 4u; lz = lz + 1u) {
126
+ let dz = outerBz * 4u + lz;
127
+ let zBitBase = (lz & 1u) * 16u;
128
+ let inHi = lz >= 2u;
129
+ let planeBase = dz * planeWords;
130
+ for (var ly = 0u; ly < 4u; ly = ly + 1u) {
131
+ let dy = outerBy * 4u + ly;
132
+ let bitBase = zBitBase + ly * 4u;
133
+ let wordIdx = wordOffsetX + dy * u.numXWords + planeBase;
134
+ let pattern = (dilatedDense[wordIdx] >> bitShiftX) & 0xFu;
135
+ let bits = pattern << bitBase;
136
+ if (inHi) { hi = hi | bits; } else { lo = lo | bits; }
137
+ }
138
+ }
139
+
140
+ masksOut[innerBlockIdx * 2u] = lo;
141
+ masksOut[innerBlockIdx * 2u + 1u] = hi;
142
+
143
+ var bt = 0u;
144
+ if (lo != 0u || hi != 0u) {
145
+ if (lo == 0xFFFFFFFFu && hi == 0xFFFFFFFFu) { bt = 1u; } else { bt = 2u; }
146
+ }
147
+ let typeWordIdx = innerBlockIdx >> 4u;
148
+ let typeBitShift = (innerBlockIdx & 15u) * 2u;
149
+ atomicOr(&typesOut[typeWordIdx], bt << typeBitShift);
150
+ }
151
+ `;
152
+ const dilateXWgsl = () => /* wgsl */ `
153
+ struct DilateXUniforms {
154
+ numXWords: u32,
155
+ ny: u32,
156
+ nz: u32,
157
+ halfExtent: u32
158
+ }
159
+
160
+ @group(0) @binding(0) var<uniform> u: DilateXUniforms;
161
+ @group(0) @binding(1) var<storage, read> src: array<u32>;
162
+ @group(0) @binding(2) var<storage, read_write> dst: array<u32>;
163
+
164
+ fn readWord(rowOffset: u32, word: i32) -> u32 {
165
+ if (word < 0 || word >= i32(u.numXWords)) { return 0u; }
166
+ return src[rowOffset + u32(word)];
167
+ }
168
+
169
+ @compute @workgroup_size(8, 4, 8)
170
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
171
+ if (gid.x >= u.numXWords || gid.y >= u.ny || gid.z >= u.nz) { return; }
172
+
173
+ let xWord = gid.x;
174
+ let y = gid.y;
175
+ let z = gid.z;
176
+ let rowStride = u.numXWords;
177
+ let planeStride = rowStride * u.ny;
178
+ let rowOffset = y * rowStride + z * planeStride;
179
+ var output = src[rowOffset + xWord];
180
+ let rowBits = u.numXWords * 32u;
181
+ let r = min(u.halfExtent, rowBits);
182
+ for (var d = 1u; d <= r; d = d + 1u) {
183
+ let wordOffset = i32(d >> 5u);
184
+ let bitShift = d & 31u;
185
+ let baseWord = i32(xWord);
186
+ var shiftedPos = readWord(rowOffset, baseWord + wordOffset);
187
+ if (bitShift != 0u) {
188
+ shiftedPos = (shiftedPos >> bitShift) | (readWord(rowOffset, baseWord + wordOffset + 1) << (32u - bitShift));
189
+ }
190
+ var shiftedNeg = readWord(rowOffset, baseWord - wordOffset);
191
+ if (bitShift != 0u) {
192
+ shiftedNeg = (shiftedNeg << bitShift) | (readWord(rowOffset, baseWord - wordOffset - 1) >> (32u - bitShift));
193
+ }
194
+ output = output | shiftedPos | shiftedNeg;
195
+ if (output == 0xFFFFFFFFu) { break; }
196
+ }
197
+ dst[rowOffset + xWord] = output;
198
+ }
199
+ `;
200
+ const dilateYZWgsl = () => /* wgsl */ `
201
+ struct DilateYZUniforms {
202
+ numXWords: u32,
203
+ ny: u32,
204
+ nz: u32,
205
+ halfExtent: u32,
206
+ stride: u32,
207
+ axisLen: u32
208
+ }
209
+
210
+ @group(0) @binding(0) var<uniform> u: DilateYZUniforms;
211
+ @group(0) @binding(1) var<storage, read> src: array<u32>;
212
+ @group(0) @binding(2) var<storage, read_write> dst: array<u32>;
213
+
214
+ @compute @workgroup_size(8, 4, 8)
215
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
216
+ if (gid.x >= u.numXWords || gid.y >= u.ny || gid.z >= u.nz) { return; }
217
+
218
+ let xWord = gid.x;
219
+ let y = gid.y;
220
+ let z = gid.z;
221
+ let rowStride = u.numXWords;
222
+ let planeStride = rowStride * u.ny;
223
+ let outIdx = i32(xWord) + i32(y) * i32(rowStride) + i32(z) * i32(planeStride);
224
+ let pos = select(z, y, u.stride == rowStride);
225
+ let r = i32(u.halfExtent);
226
+ let lo = max(0, i32(pos) - r);
227
+ let hi = min(i32(u.axisLen) - 1, i32(pos) + r);
228
+ let baseIdx = outIdx - i32(pos) * i32(u.stride);
229
+ var output = 0u;
230
+ for (var p = lo; p <= hi; p = p + 1) {
231
+ output = output | src[baseIdx + p * i32(u.stride)];
232
+ if (output == 0xFFFFFFFFu) { break; }
233
+ }
234
+ dst[outIdx] = output;
235
+ }
236
+ `;
237
+ const makeBuffer = (device, size, usage) => (device.createBuffer({ size: Math.max(4, size), usage }));
238
+ const writeUniform = (device, values) => {
239
+ const buffer = makeBuffer(device, 256, GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST);
240
+ device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
241
+ return buffer;
242
+ };
243
+ const createStoragePipeline = (device, code) => (device.createComputePipeline({
244
+ layout: 'auto',
245
+ compute: { module: device.createShaderModule({ code }), entryPoint: 'main' }
246
+ }));
247
+ const blockAlignedExtent = (halfExtent) => (halfExtent === 0 ? 0 : Math.ceil(halfExtent / 4) * 4);
248
+ const chunkIsEmpty = (src, ox, oy, oz, cx, cy, cz) => {
249
+ const minBx = Math.max(0, Math.floor(ox / 4));
250
+ const minBy = Math.max(0, Math.floor(oy / 4));
251
+ const minBz = Math.max(0, Math.floor(oz / 4));
252
+ const maxBx = Math.min(src.nbx, Math.ceil((ox + cx) / 4));
253
+ const maxBy = Math.min(src.nby, Math.ceil((oy + cy) / 4));
254
+ const maxBz = Math.min(src.nbz, Math.ceil((oz + cz) / 4));
255
+ if (maxBx <= minBx || maxBy <= minBy || maxBz <= minBz) {
256
+ return true;
257
+ }
258
+ for (let bz = minBz; bz < maxBz; bz++) {
259
+ for (let by = minBy; by < maxBy; by++) {
260
+ for (let bx = minBx; bx < maxBx; bx++) {
261
+ const blockIdx = bx + by * src.nbx + bz * src.bStride;
262
+ if (readBlockType(src.types, blockIdx) !== BLOCK_EMPTY) {
263
+ return false;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return true;
269
+ };
270
+ const chunkIsSaturated = (src, ox, oy, oz, cx, cy, cz) => {
271
+ if (ox < 0 || oy < 0 || oz < 0) {
272
+ return false;
273
+ }
274
+ if (ox + cx > src.nx || oy + cy > src.ny || oz + cz > src.nz) {
275
+ return false;
276
+ }
277
+ const minBx = ox >> 2;
278
+ const minBy = oy >> 2;
279
+ const minBz = oz >> 2;
280
+ const maxBx = (ox + cx + 3) >> 2;
281
+ const maxBy = (oy + cy + 3) >> 2;
282
+ const maxBz = (oz + cz + 3) >> 2;
283
+ for (let bz = minBz; bz < maxBz; bz++) {
284
+ for (let by = minBy; by < maxBy; by++) {
285
+ for (let bx = minBx; bx < maxBx; bx++) {
286
+ const blockIdx = bx + by * src.nbx + bz * src.bStride;
287
+ if (readBlockType(src.types, blockIdx) !== BLOCK_SOLID) {
288
+ return false;
289
+ }
290
+ }
291
+ }
292
+ }
293
+ return true;
294
+ };
295
+ const insertSaturatedInner = (dst, innerOx, innerOy, innerOz, innerCx, innerCy, innerCz) => {
296
+ const minBx = Math.max(0, innerOx >> 2);
297
+ const minBy = Math.max(0, innerOy >> 2);
298
+ const minBz = Math.max(0, innerOz >> 2);
299
+ const maxBx = Math.min(dst.nbx, (innerOx + innerCx + 3) >> 2);
300
+ const maxBy = Math.min(dst.nby, (innerOy + innerCy + 3) >> 2);
301
+ const maxBz = Math.min(dst.nbz, (innerOz + innerCz + 3) >> 2);
302
+ for (let bz = minBz; bz < maxBz; bz++) {
303
+ for (let by = minBy; by < maxBy; by++) {
304
+ const rowBase = by * dst.nbx + bz * dst.bStride;
305
+ let blockIdx = rowBase + minBx;
306
+ const endIdx = rowBase + maxBx;
307
+ while (blockIdx < endIdx) {
308
+ const w = blockIdx >>> 4;
309
+ const shift = (blockIdx & 15) << 1;
310
+ const remainingInWord = 16 - (blockIdx & 15);
311
+ const remainingInRow = endIdx - blockIdx;
312
+ const blocksToWrite = Math.min(remainingInWord, remainingInRow);
313
+ if (blocksToWrite === 16) {
314
+ dst.types[w] = SOLID_WORD;
315
+ }
316
+ else {
317
+ const bits = blocksToWrite << 1;
318
+ const mask = (((1 << bits) - 1) >>> 0) << shift;
319
+ dst.types[w] = ((dst.types[w] & ~mask) | (SOLID_WORD & mask)) >>> 0;
320
+ }
321
+ blockIdx += blocksToWrite;
322
+ }
323
+ }
324
+ }
325
+ };
326
+ const applyChunkToDst = (dst, typesOut, masksOut, cx, cy, cz, innerNx, innerNy, innerNz) => {
327
+ const innerBx = innerNx >> 2;
328
+ const innerBy = innerNy >> 2;
329
+ const innerBz = innerNz >> 2;
330
+ const baseBx = cx >> 2;
331
+ const baseBy = cy >> 2;
332
+ const baseBz = cz >> 2;
333
+ let innerIdx = 0;
334
+ for (let bz = 0; bz < innerBz; bz++) {
335
+ const globalBz = baseBz + bz;
336
+ for (let by = 0; by < innerBy; by++) {
337
+ const globalBy = baseBy + by;
338
+ const baseGlobalIdx = baseBx + globalBy * dst.nbx + globalBz * dst.bStride;
339
+ for (let bx = 0; bx < innerBx; bx++, innerIdx++) {
340
+ const wordIdx = innerIdx >>> 4;
341
+ const bitShift = (innerIdx & 15) << 1;
342
+ const bt = (typesOut[wordIdx] >>> bitShift) & 3;
343
+ if (bt === BLOCK_EMPTY) {
344
+ continue;
345
+ }
346
+ const globalBlockIdx = baseGlobalIdx + bx;
347
+ const w = globalBlockIdx >>> 4;
348
+ const shift = (globalBlockIdx & 15) << 1;
349
+ dst.types[w] |= bt << shift;
350
+ if (bt === BLOCK_MIXED) {
351
+ const m2 = innerIdx * 2;
352
+ dst.masks.set(globalBlockIdx, masksOut[m2], masksOut[m2 + 1]);
353
+ }
354
+ }
355
+ }
356
+ }
357
+ };
358
+ class GpuDilation {
359
+ device;
360
+ extractPipeline;
361
+ compactPipeline;
362
+ dilateXPipeline;
363
+ dilateYZPipeline;
364
+ slots = [];
365
+ srcTypesBuffer;
366
+ srcKeysBuffer;
367
+ srcLoBuffer;
368
+ srcHiBuffer;
369
+ srcMeta = { nbx: 0, nby: 0, nbz: 0, bStride: 0, capMinusOne: 0 };
370
+ static NUM_SLOTS = 2;
371
+ constructor(device) {
372
+ this.device = device;
373
+ this.extractPipeline = createStoragePipeline(device, extractWgsl());
374
+ this.compactPipeline = createStoragePipeline(device, compactWgsl());
375
+ this.dilateXPipeline = createStoragePipeline(device, dilateXWgsl());
376
+ this.dilateYZPipeline = createStoragePipeline(device, dilateYZWgsl());
377
+ for (let i = 0; i < GpuDilation.NUM_SLOTS; i++) {
378
+ const capacity = 1024 * 1024 * 4;
379
+ const typesOutCapacity = 64 * 1024;
380
+ const masksOutCapacity = 1024 * 1024;
381
+ this.slots.push({
382
+ bufferA: makeBuffer(device, capacity, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC),
383
+ bufferB: makeBuffer(device, capacity, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC),
384
+ readTypesBuffer: makeBuffer(device, typesOutCapacity, GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ),
385
+ readMasksBuffer: makeBuffer(device, masksOutCapacity, GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ),
386
+ typesOutBuffer: makeBuffer(device, typesOutCapacity, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC),
387
+ masksOutBuffer: makeBuffer(device, masksOutCapacity, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC),
388
+ capacity,
389
+ typesOutCapacity,
390
+ masksOutCapacity
391
+ });
392
+ }
393
+ }
394
+ replaceBuffer(slot, key, size, usage) {
395
+ slot[key].destroy();
396
+ slot[key] = makeBuffer(this.device, size, usage);
397
+ }
398
+ ensureSlotBuffers(slot, numWords) {
399
+ const neededBytes = numWords * 4;
400
+ if (neededBytes <= slot.capacity) {
401
+ return;
402
+ }
403
+ let cap = slot.capacity;
404
+ while (cap < neededBytes) {
405
+ cap *= 2;
406
+ }
407
+ this.replaceBuffer(slot, 'bufferA', cap, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC);
408
+ this.replaceBuffer(slot, 'bufferB', cap, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC);
409
+ slot.capacity = cap;
410
+ }
411
+ ensureSlotOutputBuffers(slot, innerBlocks) {
412
+ const typesBytes = ((innerBlocks + 15) >>> 4) * 4;
413
+ if (slot.typesOutCapacity < typesBytes) {
414
+ this.replaceBuffer(slot, 'typesOutBuffer', typesBytes, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC);
415
+ this.replaceBuffer(slot, 'readTypesBuffer', typesBytes, GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ);
416
+ slot.typesOutCapacity = typesBytes;
417
+ }
418
+ const masksBytes = innerBlocks * 8;
419
+ if (slot.masksOutCapacity < masksBytes) {
420
+ this.replaceBuffer(slot, 'masksOutBuffer', masksBytes, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_COPY_SRC);
421
+ this.replaceBuffer(slot, 'readMasksBuffer', masksBytes, GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ);
422
+ slot.masksOutCapacity = masksBytes;
423
+ }
424
+ }
425
+ uploadSrc(src) {
426
+ this.releaseSrc();
427
+ this.srcTypesBuffer = makeBuffer(this.device, src.types.byteLength, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST);
428
+ this.device.queue.writeBuffer(this.srcTypesBuffer, 0, src.types.buffer, src.types.byteOffset, src.types.byteLength);
429
+ const keysU32 = new Uint32Array(src.masks.keys.buffer, src.masks.keys.byteOffset, src.masks.keys.length);
430
+ this.srcKeysBuffer = makeBuffer(this.device, keysU32.byteLength, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST);
431
+ this.srcLoBuffer = makeBuffer(this.device, src.masks.lo.byteLength, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST);
432
+ this.srcHiBuffer = makeBuffer(this.device, src.masks.hi.byteLength, GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST);
433
+ this.device.queue.writeBuffer(this.srcKeysBuffer, 0, keysU32.buffer, keysU32.byteOffset, keysU32.byteLength);
434
+ this.device.queue.writeBuffer(this.srcLoBuffer, 0, src.masks.lo.buffer, src.masks.lo.byteOffset, src.masks.lo.byteLength);
435
+ this.device.queue.writeBuffer(this.srcHiBuffer, 0, src.masks.hi.buffer, src.masks.hi.byteOffset, src.masks.hi.byteLength);
436
+ this.srcMeta = {
437
+ nbx: src.nbx,
438
+ nby: src.nby,
439
+ nbz: src.nbz,
440
+ bStride: src.bStride,
441
+ capMinusOne: src.masks.keys.length - 1
442
+ };
443
+ }
444
+ releaseSrc() {
445
+ this.srcTypesBuffer?.destroy();
446
+ this.srcKeysBuffer?.destroy();
447
+ this.srcLoBuffer?.destroy();
448
+ this.srcHiBuffer?.destroy();
449
+ this.srcTypesBuffer = undefined;
450
+ this.srcKeysBuffer = undefined;
451
+ this.srcLoBuffer = undefined;
452
+ this.srcHiBuffer = undefined;
453
+ }
454
+ submitChunkSparse(slotIdx, minBx, minBy, minBz, outerBx, outerBy, outerBz, haloBx, haloBy, haloBz, innerBx, innerBy, innerBz, halfExtentXZ, halfExtentY) {
455
+ if (!this.srcTypesBuffer || !this.srcKeysBuffer || !this.srcLoBuffer || !this.srcHiBuffer) {
456
+ throw new Error('GpuDilation: must call uploadSrc() before submitChunkSparse()');
457
+ }
458
+ const slot = this.slots[slotIdx];
459
+ const outerNx = outerBx * 4;
460
+ const outerNy = outerBy * 4;
461
+ const outerNz = outerBz * 4;
462
+ const numXWords = (outerNx + 31) >>> 5;
463
+ const numWords = numXWords * outerNy * outerNz;
464
+ const innerBlocks = innerBx * innerBy * innerBz;
465
+ const typesOutWords = (innerBlocks + 15) >>> 4;
466
+ this.ensureSlotBuffers(slot, numWords);
467
+ this.ensureSlotOutputBuffers(slot, innerBlocks);
468
+ const uniformBuffers = [];
469
+ const makeUniform = (values) => {
470
+ const buffer = writeUniform(this.device, values);
471
+ uniformBuffers.push(buffer);
472
+ return buffer;
473
+ };
474
+ {
475
+ const encoder = this.device.createCommandEncoder();
476
+ encoder.clearBuffer(slot.bufferA, 0, numWords * 4);
477
+ const uniforms = new Uint32Array([
478
+ minBx >>> 0, minBy >>> 0, minBz >>> 0,
479
+ outerBx, outerBy, outerBz, numXWords,
480
+ this.srcMeta.nbx, this.srcMeta.nby, this.srcMeta.nbz,
481
+ this.srcMeta.bStride, this.srcMeta.capMinusOne
482
+ ]);
483
+ const uniformBuffer = makeUniform(uniforms);
484
+ const bindGroup = this.device.createBindGroup({
485
+ layout: this.extractPipeline.getBindGroupLayout(0),
486
+ entries: [
487
+ { binding: 0, resource: { buffer: uniformBuffer } },
488
+ { binding: 1, resource: { buffer: this.srcTypesBuffer } },
489
+ { binding: 2, resource: { buffer: this.srcKeysBuffer } },
490
+ { binding: 3, resource: { buffer: this.srcLoBuffer } },
491
+ { binding: 4, resource: { buffer: this.srcHiBuffer } },
492
+ { binding: 5, resource: { buffer: slot.bufferA } }
493
+ ]
494
+ });
495
+ const pass = encoder.beginComputePass();
496
+ pass.setPipeline(this.extractPipeline);
497
+ pass.setBindGroup(0, bindGroup);
498
+ pass.dispatchWorkgroups(Math.ceil(outerBx / 8), Math.ceil(outerBy / 4), Math.ceil(outerBz / 8));
499
+ pass.end();
500
+ this.device.queue.submit([encoder.finish()]);
501
+ }
502
+ {
503
+ const encoder = this.device.createCommandEncoder();
504
+ const dispatch = (pipeline, src, dst, uniforms, wgX, wgY, wgZ) => {
505
+ const uniformBuffer = makeUniform(uniforms);
506
+ const bindGroup = this.device.createBindGroup({
507
+ layout: pipeline.getBindGroupLayout(0),
508
+ entries: [
509
+ { binding: 0, resource: { buffer: uniformBuffer } },
510
+ { binding: 1, resource: { buffer: src } },
511
+ { binding: 2, resource: { buffer: dst } }
512
+ ]
513
+ });
514
+ const pass = encoder.beginComputePass();
515
+ pass.setPipeline(pipeline);
516
+ pass.setBindGroup(0, bindGroup);
517
+ pass.dispatchWorkgroups(wgX, wgY, wgZ);
518
+ pass.end();
519
+ };
520
+ dispatch(this.dilateXPipeline, slot.bufferA, slot.bufferB, new Uint32Array([numXWords, outerNy, outerNz, halfExtentXZ]), Math.ceil(numXWords / 8), Math.ceil(outerNy / 4), Math.ceil(outerNz / 8));
521
+ dispatch(this.dilateYZPipeline, slot.bufferB, slot.bufferA, new Uint32Array([numXWords, outerNy, outerNz, halfExtentXZ, numXWords * outerNy, outerNz]), Math.ceil(numXWords / 8), Math.ceil(outerNy / 4), Math.ceil(outerNz / 8));
522
+ dispatch(this.dilateYZPipeline, slot.bufferA, slot.bufferB, new Uint32Array([numXWords, outerNy, outerNz, halfExtentY, numXWords, outerNy]), Math.ceil(numXWords / 8), Math.ceil(outerNy / 4), Math.ceil(outerNz / 8));
523
+ encoder.clearBuffer(slot.typesOutBuffer, 0, typesOutWords * 4);
524
+ const compactUniformBuffer = makeUniform(new Uint32Array([
525
+ haloBx, haloBy, haloBz, numXWords, innerBx, innerBy, innerBz, outerBy
526
+ ]));
527
+ const compactBindGroup = this.device.createBindGroup({
528
+ layout: this.compactPipeline.getBindGroupLayout(0),
529
+ entries: [
530
+ { binding: 0, resource: { buffer: compactUniformBuffer } },
531
+ { binding: 1, resource: { buffer: slot.bufferB } },
532
+ { binding: 2, resource: { buffer: slot.typesOutBuffer } },
533
+ { binding: 3, resource: { buffer: slot.masksOutBuffer } }
534
+ ]
535
+ });
536
+ const pass = encoder.beginComputePass();
537
+ pass.setPipeline(this.compactPipeline);
538
+ pass.setBindGroup(0, compactBindGroup);
539
+ pass.dispatchWorkgroups(Math.ceil(innerBx / 8), Math.ceil(innerBy / 4), Math.ceil(innerBz / 8));
540
+ pass.end();
541
+ encoder.copyBufferToBuffer(slot.typesOutBuffer, 0, slot.readTypesBuffer, 0, typesOutWords * 4);
542
+ encoder.copyBufferToBuffer(slot.masksOutBuffer, 0, slot.readMasksBuffer, 0, innerBlocks * 8);
543
+ this.device.queue.submit([encoder.finish()]);
544
+ }
545
+ const typesPromise = (async () => {
546
+ await slot.readTypesBuffer.mapAsync(GPU_MAP_MODE_READ, 0, typesOutWords * 4);
547
+ const mapped = new Uint32Array(slot.readTypesBuffer.getMappedRange(0, typesOutWords * 4));
548
+ const out = new Uint32Array(typesOutWords);
549
+ out.set(mapped);
550
+ slot.readTypesBuffer.unmap();
551
+ return out;
552
+ })();
553
+ const masksPromise = (async () => {
554
+ await slot.readMasksBuffer.mapAsync(GPU_MAP_MODE_READ, 0, innerBlocks * 8);
555
+ const mapped = new Uint32Array(slot.readMasksBuffer.getMappedRange(0, innerBlocks * 8));
556
+ const out = new Uint32Array(innerBlocks * 2);
557
+ out.set(mapped);
558
+ slot.readMasksBuffer.unmap();
559
+ return out;
560
+ })();
561
+ void Promise.all([typesPromise, masksPromise]).then(() => {
562
+ for (const buffer of uniformBuffers) {
563
+ buffer.destroy();
564
+ }
565
+ });
566
+ return { types: typesPromise, masks: masksPromise };
567
+ }
568
+ destroy() {
569
+ this.releaseSrc();
570
+ for (const slot of this.slots) {
571
+ slot.bufferA.destroy();
572
+ slot.bufferB.destroy();
573
+ slot.readTypesBuffer.destroy();
574
+ slot.readMasksBuffer.destroy();
575
+ slot.typesOutBuffer.destroy();
576
+ slot.masksOutBuffer.destroy();
577
+ }
578
+ }
579
+ }
580
+ export const gpuDilate3 = async (src, halfExtentXZ, halfExtentY) => {
581
+ if (halfExtentXZ === 0 && halfExtentY === 0) {
582
+ return src.clone();
583
+ }
584
+ if (!Number.isInteger(halfExtentXZ) || halfExtentXZ < 0) {
585
+ throw new Error(`gpuDilate3: halfExtentXZ=${halfExtentXZ} must be a non-negative integer`);
586
+ }
587
+ if (!Number.isInteger(halfExtentY) || halfExtentY < 0) {
588
+ throw new Error(`gpuDilate3: halfExtentY=${halfExtentY} must be a non-negative integer`);
589
+ }
590
+ const device = await getOrCreateDevice();
591
+ const gpu = new GpuDilation(device);
592
+ const dst = new SparseVoxelGrid(src.nx, src.ny, src.nz);
593
+ const haloX = blockAlignedExtent(halfExtentXZ);
594
+ const haloY = blockAlignedExtent(halfExtentY);
595
+ const haloZ = haloX;
596
+ const haloBx = haloX / 4;
597
+ const haloBy = haloY / 4;
598
+ const haloBz = haloZ / 4;
599
+ const innerStep = CHUNK_INNER & ~3;
600
+ let currentSlot = 0;
601
+ let inflight;
602
+ const drainInflight = async () => {
603
+ if (!inflight) {
604
+ return;
605
+ }
606
+ const f = inflight;
607
+ inflight = undefined;
608
+ const [typesOut, masksOut] = await Promise.all([f.typesPromise, f.masksPromise]);
609
+ applyChunkToDst(dst, typesOut, masksOut, f.cx, f.cy, f.cz, f.innerNx, f.innerNy, f.innerNz);
610
+ };
611
+ gpu.uploadSrc(src);
612
+ try {
613
+ for (let cz = 0; cz < src.nz; cz += innerStep) {
614
+ for (let cy = 0; cy < src.ny; cy += innerStep) {
615
+ for (let cx = 0; cx < src.nx; cx += innerStep) {
616
+ const innerNx = Math.min(innerStep, src.nx - cx);
617
+ const innerNy = Math.min(innerStep, src.ny - cy);
618
+ const innerNz = Math.min(innerStep, src.nz - cz);
619
+ const ox = cx - haloX;
620
+ const oy = cy - haloY;
621
+ const oz = cz - haloZ;
622
+ const outerNx = innerNx + 2 * haloX;
623
+ const outerNy = innerNy + 2 * haloY;
624
+ const outerNz = innerNz + 2 * haloZ;
625
+ if (chunkIsEmpty(src, ox, oy, oz, outerNx, outerNy, outerNz)) {
626
+ continue;
627
+ }
628
+ if (chunkIsSaturated(src, ox, oy, oz, outerNx, outerNy, outerNz)) {
629
+ insertSaturatedInner(dst, cx, cy, cz, innerNx, innerNy, innerNz);
630
+ continue;
631
+ }
632
+ const innerBx = innerNx >> 2;
633
+ const innerBy = innerNy >> 2;
634
+ const innerBz = innerNz >> 2;
635
+ const outerBx = outerNx >> 2;
636
+ const outerBy = outerNy >> 2;
637
+ const outerBz = outerNz >> 2;
638
+ const minBx = Math.floor(ox / 4);
639
+ const minBy = Math.floor(oy / 4);
640
+ const minBz = Math.floor(oz / 4);
641
+ const { types, masks } = gpu.submitChunkSparse(currentSlot, minBx, minBy, minBz, outerBx, outerBy, outerBz, haloBx, haloBy, haloBz, innerBx, innerBy, innerBz, halfExtentXZ, halfExtentY);
642
+ if (inflight) {
643
+ await drainInflight();
644
+ }
645
+ inflight = {
646
+ typesPromise: types,
647
+ masksPromise: masks,
648
+ cx,
649
+ cy,
650
+ cz,
651
+ innerNx,
652
+ innerNy,
653
+ innerNz
654
+ };
655
+ currentSlot = (currentSlot + 1) % GpuDilation.NUM_SLOTS;
656
+ }
657
+ }
658
+ }
659
+ await drainInflight();
660
+ }
661
+ finally {
662
+ gpu.destroy();
663
+ }
664
+ return dst;
665
+ };