gaussian-splat-lite 1.3.2 → 1.4.1

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 (53) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/CONTRIBUTING.md +14 -2
  3. package/NOTICE +11 -3
  4. package/README.md +5 -7
  5. package/THIRD_PARTY_LICENSES.md +64 -6
  6. package/bin/build-lod.js +241 -0
  7. package/bin/decoder/decoder.js +340 -0
  8. package/bin/decoder/decoder_bg.wasm +0 -0
  9. package/bin/rad/grid.js +620 -0
  10. package/bin/rad/heap.js +270 -0
  11. package/bin/rad/input.js +87 -0
  12. package/bin/rad/lod.js +657 -0
  13. package/bin/rad/math.js +166 -0
  14. package/bin/rad/parallel.js +578 -0
  15. package/bin/rad/progress.js +50 -0
  16. package/bin/rad/rad.js +612 -0
  17. package/bin/rad/splats.js +432 -0
  18. package/bin/rad/storage.js +776 -0
  19. package/dist/gaussian-splat-lite.cjs +392 -794
  20. package/dist/gaussian-splat-lite.cjs.map +1 -1
  21. package/dist/gaussian-splat-lite.min.cjs +246 -308
  22. package/dist/gaussian-splat-lite.min.cjs.map +1 -1
  23. package/dist/gaussian-splat-lite.module.js +394 -796
  24. package/dist/gaussian-splat-lite.module.js.map +1 -1
  25. package/dist/gaussian-splat-lite.module.min.js +2518 -2802
  26. package/dist/gaussian-splat-lite.module.min.js.map +1 -1
  27. package/dist/types/addons/NeuralDenoiseNode.d.cts +1 -2
  28. package/dist/types/addons/NeuralDenoiseNode.d.ts +1 -2
  29. package/dist/types/addons/NeuralDenoiseXR.d.cts +31 -0
  30. package/dist/types/addons/NeuralDenoiseXR.d.ts +31 -0
  31. package/dist/types/addons/neuralDenoiseState.d.cts +22 -0
  32. package/dist/types/addons/neuralDenoiseState.d.ts +22 -0
  33. package/dist/types/addons/neuralDenoiseWeights.d.cts +41 -2
  34. package/dist/types/addons/neuralDenoiseWeights.d.ts +41 -2
  35. package/dist/types/data/RadPagedSplats.d.cts +1 -1
  36. package/dist/types/data/RadPagedSplats.d.ts +1 -1
  37. package/dist/types/index.d.cts +1 -2
  38. package/dist/types/index.d.ts +1 -2
  39. package/dist/types/loaders/rad/radFormat.d.cts +2 -2
  40. package/dist/types/loaders/rad/radFormat.d.ts +2 -2
  41. package/docs/BuildRad.md +91 -0
  42. package/docs/GaussianSplatRenderer.md +2 -2
  43. package/docs/NeuralDenoiseNode.md +32 -13
  44. package/docs/RadStreamScheduler.md +3 -1
  45. package/docs/StochasticRendering.md +20 -42
  46. package/package.json +9 -3
  47. package/dist/types/addons/TAANode.d.cts +0 -49
  48. package/dist/types/addons/TAANode.d.ts +0 -49
  49. package/dist/types/addons/TAAPass.d.cts +0 -57
  50. package/dist/types/addons/TAAPass.d.ts +0 -57
  51. package/dist/types/addons/taaShared.d.cts +0 -74
  52. package/dist/types/addons/taaShared.d.ts +0 -74
  53. package/docs/TAAPass.md +0 -184
@@ -0,0 +1,270 @@
1
+ import { Progress } from "./progress.js";
2
+ import { IndexArray } from "./storage.js";
3
+
4
+ const float = new Float32Array(1);
5
+ const bits = new Uint32Array(float.buffer);
6
+ // An unsigned integer that orders like the float score; -0 counts as 0.
7
+ function rank(score) {
8
+ float[0] = score + 0;
9
+ const word = bits[0];
10
+ return (word & 0x80000000 ? ~word : word | 0x80000000) >>> 0;
11
+ }
12
+
13
+ // Whether item (key, index) pops before item (otherKey, otherIndex).
14
+ function above(key, index, otherKey, otherIndex) {
15
+ return key > otherKey || (key === otherKey && index > otherIndex);
16
+ }
17
+
18
+ // The pages of an IndexArray in one growing array, for heaps that stay small.
19
+ class Flat {
20
+ constructor() {
21
+ this.values = new Uint32Array(1024);
22
+ this.mask = 0xffffffff;
23
+ }
24
+ page(index) {
25
+ if (index + 2 > this.values.length) {
26
+ const larger = new Uint32Array(
27
+ Math.max(index + 2, this.values.length * 2),
28
+ );
29
+ larger.set(this.values);
30
+ this.values = larger;
31
+ }
32
+ return this.values;
33
+ }
34
+ get(index) {
35
+ return this.page(index)[index];
36
+ }
37
+ dispose() {}
38
+ }
39
+
40
+ // Each item is a (rank, index) pair of words, so visiting it reads one page.
41
+ // Of equal ranks, the larger index pops first.
42
+ export class Heap {
43
+ constructor(workspace, flat = false) {
44
+ this.items = flat ? new Flat() : new IndexArray(workspace);
45
+ this.length = 0;
46
+ }
47
+ // Whether the top item pops before the item (key, index).
48
+ before(key, index) {
49
+ const words = this.items.page(0);
50
+ return above(words[0], words[1], key, index);
51
+ }
52
+ index(i) {
53
+ return this.items.get(i * 2 + 1);
54
+ }
55
+ store(i, key, index) {
56
+ const words = this.items.page(i * 2, true);
57
+ const offset = (i * 2) & this.items.mask;
58
+ words[offset] = key;
59
+ words[offset + 1] = index;
60
+ }
61
+ push(index, score) {
62
+ this.pushKey(index, rank(score));
63
+ }
64
+ pushKey(index, key) {
65
+ let i = this.length++;
66
+ while (i > 0) {
67
+ const parent = Math.floor((i - 1) / 2);
68
+ const words = this.items.page(parent * 2);
69
+ const offset = (parent * 2) & this.items.mask;
70
+ const otherKey = words[offset];
71
+ const other = words[offset + 1];
72
+ if (!above(key, index, otherKey, other)) break;
73
+ this.store(i, otherKey, other);
74
+ i = parent;
75
+ }
76
+ this.store(i, key, index);
77
+ }
78
+ pop() {
79
+ const items = this.items;
80
+ const result = items.get(1);
81
+ // The popped item's key.
82
+ this.key = items.get(0);
83
+ const last = --this.length;
84
+ const tail = items.page(last * 2);
85
+ const key = tail[(last * 2) & items.mask];
86
+ const index = tail[((last * 2) & items.mask) + 1];
87
+ let i = 0;
88
+ // Down to the items whose children leave the first page, one view of it
89
+ // serves: no other page is read meanwhile, so none is evicted.
90
+ const first = Math.min(last, (items.mask + 1) / 2);
91
+ const top = items.page(0, true);
92
+ let placed = false;
93
+ while (i * 2 + 2 < first) {
94
+ let child = i * 2 + 1;
95
+ let childKey = top[child * 2];
96
+ let childIndex = top[child * 2 + 1];
97
+ const rightKey = top[child * 2 + 2];
98
+ const rightIndex = top[child * 2 + 3];
99
+ if (above(rightKey, rightIndex, childKey, childIndex)) {
100
+ child++;
101
+ childKey = rightKey;
102
+ childIndex = rightIndex;
103
+ }
104
+ if (above(key, index, childKey, childIndex)) {
105
+ placed = true;
106
+ break;
107
+ }
108
+ top[i * 2] = childKey;
109
+ top[i * 2 + 1] = childIndex;
110
+ i = child;
111
+ }
112
+ while (!placed && i * 2 + 1 < last) {
113
+ let child = i * 2 + 1;
114
+ const left = items.page(child * 2);
115
+ let childKey = left[(child * 2) & items.mask];
116
+ let childIndex = left[((child * 2) & items.mask) + 1];
117
+ if (child + 1 < last) {
118
+ const right = items.page(child * 2 + 2);
119
+ const rightKey = right[(child * 2 + 2) & items.mask];
120
+ const rightIndex = right[((child * 2 + 2) & items.mask) + 1];
121
+ if (above(rightKey, rightIndex, childKey, childIndex)) {
122
+ child++;
123
+ childKey = rightKey;
124
+ childIndex = rightIndex;
125
+ }
126
+ }
127
+ if (above(key, index, childKey, childIndex)) break;
128
+ this.store(i, childKey, childIndex);
129
+ i = child;
130
+ }
131
+ if (last) this.store(i, key, index);
132
+ return result;
133
+ }
134
+ dispose() {
135
+ this.items.dispose();
136
+ }
137
+ }
138
+
139
+ // Pops the items of a heap with ties broken toward larger indices in the
140
+ // same order as the heap: the items it holds now from a sorted copy, and items
141
+ // pushed later from a second heap. Popping a sorted copy reads it in order.
142
+ export class Queue {
143
+ constructor(heap, workspace, label) {
144
+ this.size = heap.length;
145
+ this.items = sortItems(heap.items, this.size, workspace, false, label);
146
+ this.cursor = 0;
147
+ this.fresh = new Heap(workspace);
148
+ // The popped item's key.
149
+ this.key = 0;
150
+ }
151
+ get length() {
152
+ return this.size - this.cursor + this.fresh.length;
153
+ }
154
+ // Whether the next item pops before the item (key, index).
155
+ before(key, index) {
156
+ if (this.fresh.length && this.fresh.before(key, index)) return true;
157
+ if (this.cursor === this.size) return false;
158
+ const words = this.items.page(this.cursor * 2);
159
+ const offset = (this.cursor * 2) & this.items.mask;
160
+ return above(words[offset], words[offset + 1], key, index);
161
+ }
162
+ pop() {
163
+ if (this.cursor < this.size) {
164
+ const words = this.items.page(this.cursor * 2);
165
+ const offset = (this.cursor * 2) & this.items.mask;
166
+ const key = words[offset];
167
+ const index = words[offset + 1];
168
+ if (!this.fresh.length || !this.fresh.before(key, index)) {
169
+ this.cursor++;
170
+ this.key = key;
171
+ return index;
172
+ }
173
+ }
174
+ const index = this.fresh.pop();
175
+ this.key = this.fresh.key;
176
+ return index;
177
+ }
178
+ push(index, score) {
179
+ this.fresh.push(index, score);
180
+ }
181
+ dispose() {
182
+ this.items.dispose();
183
+ this.fresh.dispose();
184
+ }
185
+ }
186
+
187
+ // The first `count` (key, index) items of `items`, sorted by descending key
188
+ // and then index, or by key alone if `byKey`, keeping the order of items with
189
+ // equal keys: a radix sort by byte, skipping bytes that all items share.
190
+ function sortItems(items, count, workspace, byKey = false, label = undefined) {
191
+ const first = byKey ? 4 : 0;
192
+ const progress = label && new Progress(label, count * (10 - first));
193
+ const counts = Array.from({ length: 8 }, () => new Float64Array(256));
194
+ // Byte `pass` of the index, then of the key, inverted to sort descending.
195
+ const digit = (key, index, pass) =>
196
+ 255 - (((pass < 4 ? index : key) >>> ((pass & 3) * 8)) & 255);
197
+ for (let i = 0; i < count; i++) {
198
+ const words = items.page(i * 2);
199
+ const offset = (i * 2) & items.mask;
200
+ for (let pass = first; pass < 8; pass++)
201
+ counts[pass][digit(words[offset], words[offset + 1], pass)]++;
202
+ if ((i & 4095) === 0) progress?.update(i);
203
+ }
204
+ let source = items;
205
+ for (let pass = first; pass < 8; pass++) {
206
+ const cursor = counts[pass];
207
+ if (cursor.includes(count)) continue;
208
+ for (let b = 0, sum = 0; b < 256; b++) {
209
+ const size = cursor[b];
210
+ cursor[b] = sum;
211
+ sum += size;
212
+ }
213
+ const sorted = new IndexArray(workspace);
214
+ for (let i = 0; i < count; i++) {
215
+ const words = source.page(i * 2);
216
+ const offset = (i * 2) & source.mask;
217
+ const key = words[offset];
218
+ const index = words[offset + 1];
219
+ const target = cursor[digit(key, index, pass)]++ * 2;
220
+ const out = sorted.page(target, true);
221
+ out[target & sorted.mask] = key;
222
+ out[(target & sorted.mask) + 1] = index;
223
+ if ((i & 4095) === 0) progress?.update(count * (1 + pass - first) + i);
224
+ }
225
+ if (source !== items) source.dispose();
226
+ source = sorted;
227
+ }
228
+ if (source !== items) {
229
+ progress?.finish();
230
+ return source;
231
+ }
232
+ const copy = new IndexArray(workspace);
233
+ for (let i = 0; i < count * 2; i++) {
234
+ copy.set(i, items.get(i));
235
+ if ((i & 4095) === 0) progress?.update(count * (9 - first) + i / 2);
236
+ }
237
+ progress?.finish();
238
+ return copy;
239
+ }
240
+
241
+ // Sorts the splats by feature size, and splats of equal size by index.
242
+ export function sortSplats(splats, pool) {
243
+ const workspace = splats.workspace;
244
+ const count = splats.length;
245
+ const progress = new Progress("Preparing splat sort", count);
246
+ // Sorted by descending rank, the smallest feature comes first; sorting by
247
+ // rank alone keeps splats of equal size in index order.
248
+ const items = new IndexArray(workspace);
249
+ for (let i = 0; i < count; i++) {
250
+ const key = rank(-splats.feature(i));
251
+ const words = items.page(i * 2, true);
252
+ const offset = (i * 2) & items.mask;
253
+ words[offset] = key;
254
+ words[offset + 1] = i;
255
+ if ((i & 4095) === 0) progress.update(i);
256
+ }
257
+ progress.finish();
258
+ const sorted = sortItems(items, count, workspace, true, "Sorting splats");
259
+ items.dispose();
260
+ const order = new IndexArray(workspace);
261
+ const ordering = new Progress("Collecting splat order", count);
262
+ for (let i = 0; i < count; i++) {
263
+ order.push(sorted.get(i * 2 + 1));
264
+ if ((i & 4095) === 0) ordering.update(i);
265
+ }
266
+ sorted.dispose();
267
+ ordering.finish();
268
+ splats.reorder(order, pool);
269
+ order.dispose();
270
+ }
@@ -0,0 +1,87 @@
1
+ import {
2
+ closeSync,
3
+ fstatSync,
4
+ openSync,
5
+ readFileSync,
6
+ readSync,
7
+ } from "node:fs";
8
+ import path from "node:path";
9
+ import init, { RawDecoder } from "../decoder/decoder.js";
10
+ import { Progress } from "./progress.js";
11
+ import { Splats } from "./splats.js";
12
+
13
+ let wasm;
14
+ // A trapped WebAssembly instance is unusable, and this process cannot load a
15
+ // second copy of the module, so later inputs fail with this error instead.
16
+ let crashed;
17
+ function initialize() {
18
+ wasm ??= init({
19
+ module_or_path: readFileSync(
20
+ new URL("../decoder/decoder_bg.wasm", import.meta.url),
21
+ ),
22
+ });
23
+ return wasm;
24
+ }
25
+
26
+ export async function readInput(input, maxSh, workspace) {
27
+ const extension = path.extname(input).toLowerCase();
28
+ if (extension !== ".ply" && extension !== ".spz") {
29
+ throw new Error("Input must be a .ply or .spz file");
30
+ }
31
+ if (crashed) {
32
+ throw new Error(
33
+ `Skipped after the decoder failed on an earlier input (${crashed.message}); convert this file in a new run`,
34
+ );
35
+ }
36
+ const backend = await initialize();
37
+ const splats = new Splats(workspace, maxSh);
38
+ // The decoder asks for `base` bytes of linear memory for each batch; the
39
+ // workspace measures that memory with the rest of the process once it
40
+ // has grown.
41
+ const callback = (name, base, values) => {
42
+ if (name !== "memory") splats.receive(name, base, values);
43
+ else if (base > backend.memory.buffer.byteLength)
44
+ workspace.expect(base - backend.memory.buffer.byteLength);
45
+ };
46
+ const fd = openSync(input, "r");
47
+ try {
48
+ const size = fstatSync(fd).size;
49
+ const progress = new Progress("Reading input", size);
50
+ const decoder = new RawDecoder(input, size, callback);
51
+ try {
52
+ const bytes = Buffer.allocUnsafe(1024 * 1024);
53
+ let read = 0;
54
+ for (;;) {
55
+ workspace.check();
56
+ const size = readSync(fd, bytes);
57
+ if (!size) break;
58
+ decoder.push(bytes.subarray(0, size));
59
+ read += size;
60
+ progress.update(read);
61
+ }
62
+ decoder.finish();
63
+ } catch (error) {
64
+ // A trap leaves the decoder borrowed; free() would then throw and hide
65
+ // the original error.
66
+ try {
67
+ decoder.free();
68
+ } catch {}
69
+ throw error;
70
+ }
71
+ decoder.free();
72
+ progress.finish(`${splats.length} splats, SH${splats.inputSh}`);
73
+ return splats;
74
+ } catch (error) {
75
+ splats.dispose();
76
+ if (error instanceof WebAssembly.RuntimeError) {
77
+ crashed = error;
78
+ throw new Error(
79
+ `Decoder failed with an internal error (${error.message})`,
80
+ { cause: error },
81
+ );
82
+ }
83
+ throw error;
84
+ } finally {
85
+ closeSync(fd);
86
+ }
87
+ }