gaussian-splat-lite 1.0.1 → 1.0.2

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.2] - 2026-09-11
11
+
12
+ ### Changed
13
+
14
+ - Allowed native WebGPU rendering after shared kernels, the full sorter, and the first projection slot compile, warming the remaining slots asynchronously.
15
+ - Used stable storage binding names to allow native WebGPU projection slots to share shader programs.
16
+ - Removed the separate native WebGPU indirect-draw reset dispatch by initializing fixed arguments once and updating instance counts in the projection finish pass, preserving the maximum count across views.
17
+
10
18
  ## [1.0.1] - 2026-09-11
11
19
 
12
20
  ### Added
@@ -270,7 +278,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
270
278
 
271
279
  - Initial public release of the Three.js Gaussian Splatting renderer.
272
280
 
273
- [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.0.1...HEAD
281
+ [Unreleased]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.0.2...HEAD
282
+ [1.0.2]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.0.1...v1.0.2
274
283
  [1.0.1]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v1.0.0...v1.0.1
275
284
  [1.0.0]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v0.1.16...v1.0.0
276
285
  [0.1.16]: https://github.com/WilliamLiu-1997/Gaussian-Splat-Lite/compare/v0.1.15...v0.1.16
@@ -26423,11 +26423,8 @@ const ELEMENTS_PER_THREAD = 8;
26423
26423
  const ELEMENTS_PER_WORKGROUP = WORKGROUP_SIZE$1 * ELEMENTS_PER_THREAD;
26424
26424
  const PREFIX_ITEMS_PER_WORKGROUP = WORKGROUP_SIZE$1 * 2;
26425
26425
  const PREFIX_LEVELS = 3;
26426
- function storage(buffer2, type, name, readOnly = false) {
26427
- const getBuffer = () => buffer2.value;
26428
- const node = N$2.storage(getBuffer(), type).onObjectUpdate(getBuffer);
26429
- node.name = name;
26430
- return readOnly ? node.toReadOnly() : node;
26426
+ function storage(buffer2, name) {
26427
+ return N$2.storage(buffer2.value, "uint").setName(name).onObjectUpdate(() => buffer2.value);
26431
26428
  }
26432
26429
  function mutableUniform(value, type) {
26433
26430
  const holder = { value };
@@ -26452,8 +26449,8 @@ function makeHistogramTask({
26452
26449
  bitOffset,
26453
26450
  workgroupCount
26454
26451
  }) {
26455
- const inputKeys = storage(input, "uint", "gslRadixInputKeys", true);
26456
- const sums = storage(blockSums, "uint", "gslRadixBlockSums");
26452
+ const inputKeys = storage(input, "gslRadixInputKeys").toReadOnly();
26453
+ const sums = storage(blockSums, "gslRadixBlockSums");
26457
26454
  const bit = N$2.uint(bitOffset);
26458
26455
  const histogram = N$2.workgroupArray("uint", RADIX_BUCKETS).toAtomic().setName("gslRadixHistogram");
26459
26456
  return N$2.Fn(() => {
@@ -26486,8 +26483,8 @@ function makeHistogramTask({
26486
26483
  })().compute([1, 1, 1], [WORKGROUP_SIZE$1]).setName("Splat radix histogram");
26487
26484
  }
26488
26485
  function makePrefixScanTask(itemsBuffer, blockSumsBuffer, elementCount) {
26489
- const items = storage(itemsBuffer, "uint", "gslPrefixItems");
26490
- const blockSums = storage(blockSumsBuffer, "uint", "gslPrefixBlockSums");
26486
+ const items = storage(itemsBuffer, "gslPrefixItems");
26487
+ const blockSums = storage(blockSumsBuffer, "gslPrefixBlockSums");
26491
26488
  const temp = N$2.workgroupArray("uint", PREFIX_ITEMS_PER_WORKGROUP).setName(
26492
26489
  "gslPrefixTemp"
26493
26490
  );
@@ -26559,13 +26556,8 @@ function makePrefixScanTask(itemsBuffer, blockSumsBuffer, elementCount) {
26559
26556
  return compute2;
26560
26557
  }
26561
26558
  function makePrefixAddTask(itemsBuffer, blockSumsBuffer, elementCount) {
26562
- const items = storage(itemsBuffer, "uint", "gslPrefixItems");
26563
- const blockSums = storage(
26564
- blockSumsBuffer,
26565
- "uint",
26566
- "gslPrefixBlockSums",
26567
- true
26568
- );
26559
+ const items = storage(itemsBuffer, "gslPrefixItems");
26560
+ const blockSums = storage(blockSumsBuffer, "gslPrefixBlockSums").toReadOnly();
26569
26561
  const compute2 = N$2.Fn(() => {
26570
26562
  const workgroup = workgroupIndex();
26571
26563
  const first = workgroup.mul(PREFIX_ITEMS_PER_WORKGROUP).add(N$2.invocationLocalIndex.mul(2));
@@ -26595,23 +26587,15 @@ function makeReorderTask({
26595
26587
  }) {
26596
26588
  const inputKeys = storage(
26597
26589
  inputKeysAttribute,
26598
- "uint",
26599
- "gslRadixInputKeys",
26600
- true
26601
- );
26602
- const outputKeys = lastPass ? null : storage(outputKeysAttribute, "uint", "gslRadixOutputKeys");
26590
+ "gslRadixInputKeys"
26591
+ ).toReadOnly();
26592
+ const outputKeys = lastPass ? null : storage(outputKeysAttribute, "gslRadixOutputKeys");
26603
26593
  const inputValues = storage(
26604
26594
  inputValuesAttribute,
26605
- "uint",
26606
- "gslRadixInputValues",
26607
- true
26608
- );
26609
- const outputValues = storage(
26610
- outputValuesAttribute,
26611
- "uint",
26612
- "gslRadixOutputValues"
26613
- );
26614
- const prefix = storage(prefixAttribute, "uint", "gslRadixPrefix", true);
26595
+ "gslRadixInputValues"
26596
+ ).toReadOnly();
26597
+ const outputValues = storage(outputValuesAttribute, "gslRadixOutputValues");
26598
+ const prefix = storage(prefixAttribute, "gslRadixPrefix").toReadOnly();
26615
26599
  const bitOffsetNode = N$2.uint(bitOffset);
26616
26600
  const digitMasks = N$2.workgroupArray("uint", RADIX_BUCKETS * 8).toAtomic().setName("gslRadixDigitMasks");
26617
26601
  const digitOffsets = N$2.workgroupArray("uint", RADIX_BUCKETS).setName(
@@ -26734,7 +26718,7 @@ class WebGPURadixSort {
26734
26718
  this.values = [makeBufferRef(safeCapacity), makeBufferRef(safeCapacity)];
26735
26719
  const histogramCount = RADIX_BUCKETS * this.paddedWorkgroups(safeCapacity, ELEMENTS_PER_WORKGROUP);
26736
26720
  this.blockSums = makeBufferRef(histogramCount);
26737
- const counts = storage(this.counts, "uint", "gslRadixCounts", true);
26721
+ const counts = storage(this.counts, "gslRadixCounts").toReadOnly();
26738
26722
  this.createPrefixLevels(histogramCount, counts);
26739
26723
  const setup = this.makeSetupTask(options.count);
26740
26724
  const prefixNodes = this.prefixLevels.map((_, lastLevel) => {
@@ -26801,13 +26785,11 @@ class WebGPURadixSort {
26801
26785
  return Math.max(1, width * Math.ceil(groups / this.maxWorkgroups));
26802
26786
  }
26803
26787
  makeSetupTask(gpuCount) {
26804
- const counts = storage(this.counts, "uint", "gslRadixCounts");
26788
+ const counts = storage(this.counts, "gslRadixCounts");
26805
26789
  const dispatchBuffers = [
26806
26790
  this.sortDispatch,
26807
26791
  ...this.prefixLevels.map((level) => level.dispatch)
26808
- ].map(
26809
- (value, index2) => storage({ value }, "uint", `gslRadixDispatch${index2}`)
26810
- );
26792
+ ].map((value, index2) => storage({ value }, `gslRadixDispatch${index2}`));
26811
26793
  const maxWorkgroups = N$2.uint(this.maxWorkgroups);
26812
26794
  return N$2.Fn(() => {
26813
26795
  const count = N$2.min(
@@ -26919,29 +26901,31 @@ class WebGPURadixSort {
26919
26901
  const N$1 = TSL;
26920
26902
  const WORKGROUP_SIZE = 256;
26921
26903
  const PROJECT_SLOTS = 8;
26922
- function buffer(count = 1) {
26923
- return { value: new StorageBufferAttribute(new Uint32Array(count), 1) };
26904
+ function buffer(name) {
26905
+ return { value: new StorageBufferAttribute(new Uint32Array(1), 1), name };
26924
26906
  }
26925
- function bindBuffer(ref, readOnly = false) {
26926
- const node = N$1.storage(ref.value, "uint").onObjectUpdate(() => ref.value);
26927
- return readOnly ? node.toReadOnly() : node;
26907
+ function bindBuffer(ref) {
26908
+ return N$1.storage(ref.value, "uint").setName(ref.name).onObjectUpdate(() => ref.value);
26928
26909
  }
26929
26910
  class ProjectedSplats {
26930
26911
  constructor(renderer, uniforms) {
26931
26912
  this.renderer = renderer;
26932
26913
  this.uniforms = uniforms;
26933
- this.indirect = new IndirectStorageBufferAttribute(new Uint32Array(5), 1);
26934
- this.pending = true;
26914
+ this.indirect = new IndirectStorageBufferAttribute(
26915
+ new Uint32Array([WEBGPU_SPLATS_PER_INSTANCE * 6, 0, 0, 0, 0]),
26916
+ 1
26917
+ );
26935
26918
  this.error = null;
26936
26919
  this.cache = new ProjectionCache();
26937
26920
  this.visibleCount = new StorageBufferAttribute(
26938
26921
  new Uint32Array(1),
26939
26922
  1
26940
26923
  );
26941
- this.keys = buffer();
26942
- this.seeds = buffer();
26943
- this.counts = buffer();
26924
+ this.keys = buffer("gslProjectionKeys");
26925
+ this.seeds = buffer("gslProjectionSeeds");
26926
+ this.counts = buffer("gslProjectionCounts");
26944
26927
  this.slots = [];
26928
+ this.compiledSlots = 0;
26945
26929
  this.matrix = new THREE__namespace.Matrix4();
26946
26930
  this.translation = new THREE__namespace.Matrix4();
26947
26931
  this.scale = new THREE__namespace.Vector3();
@@ -26971,8 +26955,8 @@ class ProjectedSplats {
26971
26955
  const viewBase = uniformBinding(this.state, "viewBase", "uint");
26972
26956
  const multiView = uniformBinding(this.state, "multiView", "bool");
26973
26957
  const stochastic = uniformBinding(uniforms, "stochastic", "bool");
26974
- const count = N$1.storage(this.visibleCount, "uint").toReadOnly().element(0);
26975
- const seeds = bindBuffer(this.seeds, true);
26958
+ const count = N$1.storage(this.visibleCount, "uint").setName("gslVisibleCount").toReadOnly().element(0);
26959
+ const seeds = bindBuffer(this.seeds).toReadOnly();
26976
26960
  this.sorter = new WebGPURadixSort(1, this.keys, {
26977
26961
  count,
26978
26962
  storeOrder: (index2, value) => {
@@ -26989,15 +26973,8 @@ class ProjectedSplats {
26989
26973
  this.limits.maxBufferSize
26990
26974
  )
26991
26975
  });
26992
- const draw = N$1.storage(this.indirect, "uint");
26993
- this.resetDraw = N$1.Fn(() => {
26994
- draw.element(0).assign(WEBGPU_SPLATS_PER_INSTANCE * 6);
26995
- draw.element(1).assign(0);
26996
- draw.element(2).assign(0);
26997
- draw.element(3).assign(0);
26998
- draw.element(4).assign(0);
26999
- })().compute(1).setName("Splat reset draw");
27000
- const counter = N$1.storage(this.visibleCount, "uint").toAtomic();
26976
+ const drawCount = N$1.storage(this.indirect, "uint").setName("gslDrawIndirect").element(1);
26977
+ const counter = N$1.storage(this.visibleCount, "uint").setName("gslVisibleCount").toAtomic();
27001
26978
  this.resetCount = N$1.Fn(() => {
27002
26979
  N$1.atomicStore(counter.element(0), N$1.uint(0));
27003
26980
  })().compute(1, [1]).setName("Splat reset visible count");
@@ -27008,7 +26985,9 @@ class ProjectedSplats {
27008
26985
  N$1.If(index2.equal(0), () => {
27009
26986
  counts.element(viewIndex).assign(count);
27010
26987
  const instances = count.add(WEBGPU_SPLATS_PER_INSTANCE - 1).div(N$1.uint(WEBGPU_SPLATS_PER_INSTANCE));
27011
- draw.element(1).assign(draw.element(1).max(instances));
26988
+ drawCount.assign(
26989
+ N$1.select(viewIndex.equal(0), instances, drawCount.max(instances))
26990
+ );
27012
26991
  });
27013
26992
  N$1.If(multiView.and(stochastic).and(index2.lessThan(count)), () => {
27014
26993
  this.cache.storeOrder(
@@ -27019,17 +26998,30 @@ class ProjectedSplats {
27019
26998
  })().compute(1, [WORKGROUP_SIZE]).setName("Splat visible draw arguments");
27020
26999
  for (let i = 0; i < PROJECT_SLOTS; i++) this.slots.push(this.createSlot());
27021
27000
  this.ready = computeRenderer.compileComputeAsync([
27022
- this.resetDraw,
27023
27001
  this.resetCount,
27024
27002
  this.finish,
27025
- ...this.slots.map((slot) => slot.node),
27026
- ...this.sorter.nodes
27027
- ]).catch((error) => {
27003
+ ...this.sorter.nodes,
27004
+ this.slots[0].node
27005
+ ]).then(() => {
27006
+ this.compiledSlots = 1;
27007
+ }).catch((error) => {
27028
27008
  this.error = error;
27029
- }).finally(() => {
27030
- this.pending = false;
27031
- if (this.disposed) this.disposeResources();
27032
27009
  });
27010
+ this.compilation = this.ready.then(() => this.compileRemainingSlots());
27011
+ }
27012
+ async compileRemainingSlots() {
27013
+ if (this.disposed || this.compiledSlots === 0) return;
27014
+ try {
27015
+ await new Promise((resolve) => setTimeout(resolve, 0));
27016
+ while (!this.disposed && this.compiledSlots < this.slots.length) {
27017
+ await this.renderer.compileComputeAsync([
27018
+ this.slots[this.compiledSlots].node
27019
+ ]);
27020
+ this.compiledSlots++;
27021
+ }
27022
+ } catch (error) {
27023
+ this.error = error;
27024
+ }
27033
27025
  }
27034
27026
  createSlot() {
27035
27027
  const uniforms = { ...this.state, ...makeGenerateUniforms() };
@@ -27053,7 +27045,7 @@ class ProjectedSplats {
27053
27045
  const pixelScale = u("renderSize", "vec2").mul(u("focalAdjustment", "float")).mul(0.5);
27054
27046
  const keys = bindBuffer(this.keys);
27055
27047
  const seeds = bindBuffer(this.seeds);
27056
- const counter = N$1.storage(this.visibleCount, "uint").toAtomic();
27048
+ const counter = N$1.storage(this.visibleCount, "uint").setName("gslVisibleCount").toAtomic();
27057
27049
  const node = N$1.Fn(() => {
27058
27050
  const index2 = N$1.uint(N$1.instanceIndex);
27059
27051
  const generated = generate.prepare(index2);
@@ -27106,7 +27098,7 @@ class ProjectedSplats {
27106
27098
  const stride = uniformBinding(this.state, "viewStride", "uint");
27107
27099
  const base = eye.mul(stride).toVar();
27108
27100
  const i = N$1.uint(N$1.instanceIndex).mul(WEBGPU_SPLATS_PER_INSTANCE).add(N$1.uint(N$1.positionGeometry.z));
27109
- const counts = bindBuffer(this.counts, true);
27101
+ const counts = bindBuffer(this.counts).toReadOnly();
27110
27102
  const clipPosition = N$1.vec4(0, 0, 2, 1).toVar();
27111
27103
  const rgba = N$1.vec4(0).toVar();
27112
27104
  const splatUv = N$1.vec2(0).toVar();
@@ -27122,7 +27114,7 @@ class ProjectedSplats {
27122
27114
  const cacheIndex = i.toVar();
27123
27115
  const assignSeed = () => {
27124
27116
  stochasticSeed.assign(
27125
- multiView ? this.cache.readOrder(base.add(i)).y : bindBuffer(this.seeds, true).element(i)
27117
+ multiView ? this.cache.readOrder(base.add(i)).y : bindBuffer(this.seeds).toReadOnly().element(i)
27126
27118
  );
27127
27119
  };
27128
27120
  if (depthOnly) {
@@ -27201,7 +27193,7 @@ class ProjectedSplats {
27201
27193
  }
27202
27194
  render(accumulator, camera, geometry, radial, shrink = false) {
27203
27195
  if (this.error) throw this.error;
27204
- if (this.pending || this.disposed) {
27196
+ if (this.compiledSlots === 0 || this.disposed) {
27205
27197
  geometry.instanceCount = 0;
27206
27198
  return;
27207
27199
  }
@@ -27235,12 +27227,11 @@ class ProjectedSplats {
27235
27227
  this.scale
27236
27228
  );
27237
27229
  this.state.renderToViewScale.value = (this.scale.x + this.scale.y + this.scale.z) / 3;
27238
- const pending = eye === 0 ? [this.resetDraw] : [];
27239
- pending.push(this.resetCount);
27230
+ const pending = [this.resetCount];
27240
27231
  let slotCount = 0;
27241
27232
  for (const { node, count } of accumulator.mapping) {
27242
27233
  if (!view.layers.test(node.layers)) continue;
27243
- if (slotCount === PROJECT_SLOTS) {
27234
+ if (slotCount === this.compiledSlots) {
27244
27235
  this.renderer.compute(pending);
27245
27236
  pending.length = 0;
27246
27237
  slotCount = 0;
@@ -27261,12 +27252,11 @@ class ProjectedSplats {
27261
27252
  dispose() {
27262
27253
  if (this.disposed) return;
27263
27254
  this.disposed = true;
27264
- if (!this.pending) this.disposeResources();
27255
+ void this.compilation.then(() => this.disposeResources());
27265
27256
  }
27266
27257
  disposeResources() {
27267
27258
  for (const slot of this.slots) slot.node.dispose();
27268
- for (const node of [this.resetDraw, this.resetCount, this.finish])
27269
- node.dispose();
27259
+ for (const node of [this.resetCount, this.finish]) node.dispose();
27270
27260
  this.sorter.dispose();
27271
27261
  this.cache.dispose();
27272
27262
  this.visibleCount.dispose();