@cesium/engine 26.3.0 → 26.3.1-wasm.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 (143) hide show
  1. package/Build/ThirdParty/Workers/zip-web-worker.js +504 -535
  2. package/Build/Workers/chunk-2P3WKV6V.js +290 -0
  3. package/Build/Workers/{chunk-MUW4DJV7.js → chunk-2U5272QU.js} +17 -17
  4. package/Build/Workers/{chunk-7WXBTNRV.js → chunk-4KCOC6OZ.js} +8 -10
  5. package/Build/Workers/{chunk-GS7RY2DH.js → chunk-5FULJNWY.js} +11 -13
  6. package/Build/Workers/{chunk-2PWVICP6.js → chunk-65ZF23AQ.js} +1 -1
  7. package/Build/Workers/{chunk-SEJIXZZ4.js → chunk-6D5534B7.js} +10 -12
  8. package/Build/Workers/{chunk-MDDKCLMF.js → chunk-6GFMBHBD.js} +19 -19
  9. package/Build/Workers/{chunk-PKZA44LH.js → chunk-6VRGQF3D.js} +4 -4
  10. package/Build/Workers/{chunk-JBJ6BRIM.js → chunk-6XUIECFB.js} +4 -6
  11. package/Build/Workers/{chunk-DKSANM4A.js → chunk-7KWUH6RK.js} +14 -14
  12. package/Build/Workers/{chunk-6BSNYZZG.js → chunk-7ZBJRQQT.js} +7 -7
  13. package/Build/Workers/{chunk-XRZCS3HE.js → chunk-AEHANE5A.js} +991 -5612
  14. package/Build/Workers/chunk-AUU74SIZ.js +2420 -0
  15. package/Build/Workers/{chunk-2YY724ZX.js → chunk-B6V5NMIE.js} +13 -11
  16. package/Build/Workers/{chunk-5JCZHJUU.js → chunk-BRQ52ASU.js} +15 -17
  17. package/Build/Workers/{chunk-Z2DW5NM2.js → chunk-FVLSQBPH.js} +7 -14
  18. package/Build/Workers/{chunk-L73K5UPP.js → chunk-IFKM2GZR.js} +12 -12
  19. package/Build/Workers/{chunk-F34TJJLA.js → chunk-KF6OEWVT.js} +17 -17
  20. package/Build/Workers/{chunk-JL4NWWQY.js → chunk-KOAXSU77.js} +5 -7
  21. package/Build/Workers/{chunk-CKK6CUXK.js → chunk-KRVYCMGN.js} +6 -6
  22. package/Build/Workers/{chunk-KBGAHN5R.js → chunk-LTZRLNQP.js} +1 -1
  23. package/Build/Workers/{chunk-CQ4TZXAT.js → chunk-M7WEAGWT.js} +8 -8
  24. package/Build/Workers/{chunk-A7SMPV6B.js → chunk-MJTLGN63.js} +14 -14
  25. package/Build/Workers/{chunk-DZK34ZVD.js → chunk-MUNZ77KV.js} +1 -1
  26. package/Build/Workers/{chunk-X27HKF5E.js → chunk-NC5BUJEF.js} +2 -2
  27. package/Build/Workers/chunk-NYKURYFR.js +2131 -0
  28. package/Build/Workers/{chunk-5KG7KDDW.js → chunk-NZAQG57U.js} +7 -7
  29. package/Build/Workers/{chunk-JKTWW7IO.js → chunk-O5GUOP2G.js} +5 -7
  30. package/Build/Workers/{chunk-6R6SGKYN.js → chunk-OQUOLQTN.js} +8 -10
  31. package/Build/Workers/chunk-OUG5JB2K.js +46 -0
  32. package/Build/Workers/{chunk-HDD3RFLT.js → chunk-P4SDLSGF.js} +13 -6
  33. package/Build/Workers/{chunk-M6W46OAP.js → chunk-P6PC2OIM.js} +7 -7
  34. package/Build/Workers/{chunk-XSIUYAT6.js → chunk-PPRK3XQU.js} +31 -35
  35. package/Build/Workers/{chunk-4LRFYRBX.js → chunk-QGTYLWOT.js} +2 -2
  36. package/Build/Workers/{chunk-COKOYTRS.js → chunk-QHTLIQUH.js} +5 -7
  37. package/Build/Workers/{chunk-N5YK44FS.js → chunk-ROHWZQZ5.js} +5 -5
  38. package/Build/Workers/{chunk-XBY3354M.js → chunk-SNUWISJO.js} +6 -8
  39. package/Build/Workers/{chunk-EDETO6NY.js → chunk-T67YY3KP.js} +4 -2
  40. package/Build/Workers/{chunk-4DMAXPKB.js → chunk-TF5473GK.js} +13 -13
  41. package/Build/Workers/{chunk-B7Q2WF6Q.js → chunk-THHFTMAL.js} +4 -6
  42. package/Build/Workers/{chunk-SL2HCFYW.js → chunk-UFCZ62WM.js} +15 -13
  43. package/Build/Workers/{chunk-NZQUP52G.js → chunk-UV2YC2Q5.js} +11 -11
  44. package/Build/Workers/{chunk-E7G32DWL.js → chunk-V4ICCWYA.js} +1 -1
  45. package/Build/Workers/{chunk-75BC2IHI.js → chunk-VC6XXI2Z.js} +9 -9
  46. package/Build/Workers/{chunk-JMUUAW4X.js → chunk-VXJ6P4UI.js} +10 -10
  47. package/Build/Workers/{chunk-QTAEVUED.js → chunk-W2BKP7G7.js} +15 -17
  48. package/Build/Workers/{chunk-VRECSMG3.js → chunk-WMVGNBSH.js} +2 -2
  49. package/Build/Workers/{chunk-7SESBH2I.js → chunk-XFZMQZSZ.js} +4 -4
  50. package/Build/Workers/{chunk-Q2JS5LCB.js → chunk-XJ532R6R.js} +20 -20
  51. package/Build/Workers/{chunk-SLHK3KAR.js → chunk-YJNU4SM4.js} +5 -5
  52. package/Build/Workers/{chunk-LL2SGJYH.js → chunk-YJRDTEHQ.js} +8 -8
  53. package/Build/Workers/chunk-YUICDZKO.js +2660 -0
  54. package/Build/Workers/{chunk-ATSXKJ3L.js → chunk-Z3DEVGJV.js} +7 -7
  55. package/Build/Workers/{chunk-6V2KDEHL.js → chunk-ZECGI7UZ.js} +4 -4
  56. package/Build/Workers/{chunk-LYJUL4XQ.js → chunk-ZFLPVPUJ.js} +6 -8
  57. package/Build/Workers/{chunk-BNDSDQ7D.js → chunk-ZIM5H2DB.js} +11 -11
  58. package/Build/Workers/{chunk-KEWK6VZL.js → chunk-ZSQMGGSB.js} +14 -16
  59. package/Build/Workers/{chunk-2DKJSPYF.js → chunk-ZWYRGMBK.js} +16 -18
  60. package/Build/Workers/{chunk-UZY5XRL2.js → chunk-ZY733NZA.js} +1 -1
  61. package/Build/Workers/combineGeometry.js +24 -24
  62. package/Build/Workers/createBoxGeometry.js +18 -18
  63. package/Build/Workers/createBoxOutlineGeometry.js +20 -19
  64. package/Build/Workers/createCircleGeometry.js +28 -27
  65. package/Build/Workers/createCircleOutlineGeometry.js +21 -20
  66. package/Build/Workers/createCoplanarPolygonGeometry.js +34 -34
  67. package/Build/Workers/createCoplanarPolygonOutlineGeometry.js +33 -32
  68. package/Build/Workers/createCorridorGeometry.js +31 -31
  69. package/Build/Workers/createCorridorOutlineGeometry.js +30 -30
  70. package/Build/Workers/createCylinderGeometry.js +20 -20
  71. package/Build/Workers/createCylinderOutlineGeometry.js +22 -21
  72. package/Build/Workers/createEllipseGeometry.js +26 -26
  73. package/Build/Workers/createEllipseOutlineGeometry.js +19 -19
  74. package/Build/Workers/createEllipsoidGeometry.js +19 -19
  75. package/Build/Workers/createEllipsoidOutlineGeometry.js +18 -18
  76. package/Build/Workers/createFrustumGeometry.js +18 -18
  77. package/Build/Workers/createFrustumOutlineGeometry.js +18 -18
  78. package/Build/Workers/createGeometry.js +27 -26
  79. package/Build/Workers/createGroundPolylineGeometry.js +27 -26
  80. package/Build/Workers/createPlaneGeometry.js +18 -17
  81. package/Build/Workers/createPlaneOutlineGeometry.js +15 -15
  82. package/Build/Workers/createPolygonGeometry.js +35 -35
  83. package/Build/Workers/createPolygonOutlineGeometry.js +34 -34
  84. package/Build/Workers/createPolylineGeometry.js +28 -29
  85. package/Build/Workers/createPolylineVolumeGeometry.js +34 -35
  86. package/Build/Workers/createPolylineVolumeOutlineGeometry.js +30 -31
  87. package/Build/Workers/createRectangleGeometry.js +30 -30
  88. package/Build/Workers/createRectangleOutlineGeometry.js +23 -24
  89. package/Build/Workers/createSimplePolylineGeometry.js +26 -27
  90. package/Build/Workers/createSphereGeometry.js +19 -19
  91. package/Build/Workers/createSphereOutlineGeometry.js +18 -18
  92. package/Build/Workers/createTaskProcessorWorker.js +3 -3
  93. package/Build/Workers/createVectorTileClampedPolylines.js +17 -16
  94. package/Build/Workers/createVectorTileGeometries.js +24 -24
  95. package/Build/Workers/createVectorTilePoints.js +13 -13
  96. package/Build/Workers/createVectorTilePolygons.js +22 -22
  97. package/Build/Workers/createVectorTilePolylines.js +17 -16
  98. package/Build/Workers/createVerticesFromCesium3DTilesTerrain.js +36 -26
  99. package/Build/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +24 -24
  100. package/Build/Workers/createVerticesFromHeightmap.js +24 -25
  101. package/Build/Workers/createVerticesFromQuantizedTerrainMesh.js +20 -20
  102. package/Build/Workers/createWallGeometry.js +27 -28
  103. package/Build/Workers/createWallOutlineGeometry.js +26 -27
  104. package/Build/Workers/decodeDraco.js +16 -12
  105. package/Build/Workers/decodeGoogleEarthEnterprisePacket.js +336 -337
  106. package/Build/Workers/decodeI3S.js +17 -12
  107. package/Build/Workers/{chunk-GLHDTTTO.js → decodeMeshopt.js} +23 -29
  108. package/Build/Workers/decodeSpz.js +1960 -0
  109. package/Build/Workers/gaussianSplatSorter.js +13 -6
  110. package/Build/Workers/gaussianSplatTextureGenerator.js +13 -6
  111. package/Build/Workers/incrementallyBuildTerrainPicker.js +9 -10
  112. package/Build/Workers/transcodeKTX2.js +26 -12
  113. package/Build/Workers/transferTypedArrayTest.js +1 -1
  114. package/Build/Workers/upsampleQuantizedTerrainMesh.js +24 -24
  115. package/Build/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +25 -25
  116. package/Source/Core/Cesium3DTilesTerrainData.js +11 -1
  117. package/Source/Core/Cesium3DTilesTerrainGeometryProcessor.js +33 -23
  118. package/Source/Core/KTX2Transcoder.js +99 -5
  119. package/Source/Core/Resource.js +2 -0
  120. package/Source/Core/TaskProcessor.js +249 -129
  121. package/Source/Core/TrustedServers.js +34 -1
  122. package/Source/Core/WebAssemblyWorkerInitializer.js +125 -0
  123. package/Source/Core/fetchWebAssemblyBinary.js +52 -0
  124. package/Source/Scene/GaussianSplatPrimitive.js +153 -66
  125. package/Source/Scene/GltfBufferViewLoader.js +25 -13
  126. package/Source/Scene/GltfSpzLoader.js +5 -4
  127. package/Source/Scene/SpzDecoder.js +106 -0
  128. package/Source/ThirdParty/Workers/zip-web-worker.js +1 -1
  129. package/Source/ThirdParty/zip-module.wasm +0 -0
  130. package/Source/Workers/createTaskProcessorWorker.js +2 -0
  131. package/Source/Workers/createVerticesFromCesium3DTilesTerrain.js +13 -1
  132. package/Source/Workers/decodeDraco.js +7 -3
  133. package/Source/Workers/decodeI3S.js +7 -3
  134. package/Source/Workers/decodeMeshopt.js +21 -0
  135. package/Source/Workers/decodeSpz.js +20 -0
  136. package/Source/Workers/gaussianSplatSorter.js +7 -3
  137. package/Source/Workers/gaussianSplatTextureGenerator.js +7 -3
  138. package/Source/Workers/transcodeKTX2.js +22 -5
  139. package/index.d.ts +93 -0
  140. package/index.js +4 -1
  141. package/package.json +1 -1
  142. package/Build/Workers/chunk-B4AVL7VI.js +0 -82
  143. package/Build/Workers/chunk-HDPNFEPP.js +0 -3820
@@ -0,0 +1,52 @@
1
+ import defined from "./defined.js";
2
+ import Resource from "./Resource.js";
3
+
4
+ /**
5
+ * Fetches a WebAssembly binary when a configuration provides its URL and does not already
6
+ * contain the binary.
7
+ *
8
+ * <code>TaskProcessor</code> posts the URL of the binary rather than its bytes, so that the
9
+ * document neither fetches nor compiles WebAssembly. Worker implementations call this function
10
+ * to obtain the bytes and pass them to an Emscripten module factory.
11
+ *
12
+ * The input configuration is not changed. If the configuration already carries
13
+ * <code>wasmBinary</code>, or has no <code>wasmBinaryFile</code>, this function returns undefined.
14
+ *
15
+ * @function fetchWebAssemblyBinary
16
+ *
17
+ * @param {WebAssemblyConfig} webAssemblyConfig The configuration posted by {@link TaskProcessor#initWebAssemblyModule}.
18
+ * @returns {Promise<ArrayBuffer|undefined>} A promise that resolves to the fetched binary, or undefined when no fetch is needed.
19
+ *
20
+ * @private
21
+ * @see TaskProcessor#initWebAssemblyModule
22
+ * @see createTaskProcessorWorker
23
+ */
24
+ async function fetchWebAssemblyBinary(webAssemblyConfig) {
25
+ if (
26
+ !defined(webAssemblyConfig.wasmBinaryFile) ||
27
+ defined(webAssemblyConfig.wasmBinary)
28
+ ) {
29
+ return undefined;
30
+ }
31
+
32
+ return Resource.fetchArrayBuffer({
33
+ url: webAssemblyConfig.wasmBinaryFile,
34
+ });
35
+ }
36
+
37
+ /**
38
+ * The WebAssembly configuration posted to a worker by
39
+ * {@link TaskProcessor#initWebAssemblyModule}, as the first message that worker receives.
40
+ *
41
+ * @typedef {object} WebAssemblyConfig
42
+ *
43
+ * @property {string} [wasmBinaryFile] The absolute url of the WebAssembly binary. Undefined
44
+ * when the browser does not support WebAssembly and a fallback module is used instead.
45
+ * @property {string} [modulePath] The absolute URL of the JavaScript wrapper module, or the
46
+ * fallback module when the browser does not support WebAssembly.
47
+ * @property {ArrayBuffer} [wasmBinary] The binary contents, when they are already available.
48
+ *
49
+ * @private
50
+ */
51
+
52
+ export default fetchWebAssemblyBinary;
@@ -35,6 +35,7 @@ import SplitDirection from "./SplitDirection.js";
35
35
  import destroyObject from "../Core/destroyObject.js";
36
36
  import ContextLimits from "../Renderer/ContextLimits.js";
37
37
  import Transforms from "../Core/Transforms.js";
38
+ import getTimestamp from "../Core/getTimestamp.js";
38
39
 
39
40
  const scratchMatrix4A = new Matrix4();
40
41
  const scratchMatrix4C = new Matrix4();
@@ -144,12 +145,114 @@ const SnapshotState = {
144
145
  * @private
145
146
  */
146
147
 
147
- // Two stable frames avoids rebuilding during brief selected-tile jitter.
148
- const DEFAULT_STABLE_FRAMES = 2;
149
- // If selection keeps changing, force a rebuild after ~0.5s at 60fps to guarantee progress.
150
- // Lower values react faster but can thrash on noisy LOD transitions.
151
- // Higher values reduce rebuild churn but keep stale snapshots visible longer.
152
- const DEFAULT_MAX_SNAPSHOT_STALL_FRAMES = 30;
148
+ // A snapshot rebuild re-aggregates every selected tile and re-uploads the
149
+ // splat textures. Each rebuild is a burst of main-thread and GPU work, so the
150
+ // scheduler waits for the selected tile set to settle before starting one.
151
+ //
152
+ // Both values below were tuned by hand on one dev machine. They have not been
153
+ // measured across hardware or network speeds (coming shortly).
154
+
155
+ // How long the selected tile set must stay unchanged, with no tiles decoding
156
+ // or downloading, before a rebuild may start.
157
+ const DEFAULT_SNAPSHOT_DEBOUNCE_MILLISECONDS = 500;
158
+
159
+ // Upper bound on how long a pending rebuild can wait while the selection keeps
160
+ // changing. Once passed, a rebuild is forced unless a committed snapshot
161
+ // already covers most of the selected tiles (see the overlap check in
162
+ // SnapshotRebuildScheduler.update).
163
+ const DEFAULT_MAX_SNAPSHOT_WAIT_MILLISECONDS = 2000;
164
+
165
+ class SnapshotRebuildScheduler {
166
+ constructor() {
167
+ this._selectedTileSet = new Set();
168
+ this._selectionChangedTime = undefined;
169
+ this._pendingSince = undefined;
170
+ this._lastRebuildTileSet = undefined;
171
+ }
172
+
173
+ get isPending() {
174
+ return defined(this._pendingSince);
175
+ }
176
+
177
+ hasSelectedTile(tile) {
178
+ return this._selectedTileSet.has(tile);
179
+ }
180
+
181
+ update(
182
+ timestamp,
183
+ {
184
+ selectedTiles,
185
+ rebuildRequested,
186
+ isBootstrap,
187
+ tilesLoaded,
188
+ tilesInFlight = false,
189
+ hasCommittedSnapshot = false,
190
+ },
191
+ ) {
192
+ if (selectedTiles.length === 0) {
193
+ this.clear();
194
+ return false;
195
+ }
196
+
197
+ const selectedTilesChanged = haveSelectedTilesChanged(
198
+ this._selectedTileSet,
199
+ selectedTiles,
200
+ );
201
+ if (selectedTilesChanged) {
202
+ this._selectedTileSet = new Set(selectedTiles);
203
+ this._selectionChangedTime = timestamp;
204
+ }
205
+
206
+ if (
207
+ (selectedTilesChanged || rebuildRequested || isBootstrap) &&
208
+ !this.isPending
209
+ ) {
210
+ this._pendingSince = timestamp;
211
+ }
212
+
213
+ if (!this.isPending) {
214
+ return false;
215
+ }
216
+
217
+ const selectionIsStable =
218
+ !tilesInFlight &&
219
+ defined(this._selectionChangedTime) &&
220
+ timestamp - this._selectionChangedTime >=
221
+ DEFAULT_SNAPSHOT_DEBOUNCE_MILLISECONDS;
222
+
223
+ let tileOverlap = 0;
224
+ if (defined(this._lastRebuildTileSet)) {
225
+ for (const tile of this._selectedTileSet) {
226
+ if (this._lastRebuildTileSet.has(tile)) {
227
+ tileOverlap++;
228
+ }
229
+ }
230
+ tileOverlap /= this._selectedTileSet.size;
231
+ }
232
+
233
+ const deadlineReached =
234
+ timestamp - this._pendingSince >=
235
+ DEFAULT_MAX_SNAPSHOT_WAIT_MILLISECONDS &&
236
+ // If the committed snapshot still covers at least half of the selected
237
+ // tiles, the stale view is close enough to keep showing. Skip the forced
238
+ // rebuild and wait for the selection to settle or for tilesLoaded.
239
+ (!hasCommittedSnapshot || tileOverlap < 0.5);
240
+
241
+ return isBootstrap || tilesLoaded || selectionIsStable || deadlineReached;
242
+ }
243
+
244
+ markRebuildStarted() {
245
+ this._lastRebuildTileSet = new Set(this._selectedTileSet);
246
+ this._pendingSince = undefined;
247
+ }
248
+
249
+ clear() {
250
+ this._selectedTileSet.clear();
251
+ this._selectionChangedTime = undefined;
252
+ this._pendingSince = undefined;
253
+ }
254
+ }
255
+
153
256
  // Minimum delay between steady re-sort requests once the camera is moving.
154
257
  const DEFAULT_SORT_MIN_FRAME_INTERVAL = 3;
155
258
  // ~0.5 degree camera direction change threshold before triggering steady re-sort.
@@ -236,23 +339,18 @@ function markSteadySortStart(primitive, frameState) {
236
339
  }
237
340
 
238
341
  /**
239
- * Checks whether the set of currently selected tiles differs from the set
240
- * recorded on the primitive. This is used to detect LOD transitions that
241
- * require a snapshot rebuild.
242
- *
243
- * @param {GaussianSplatPrimitive} primitive The splat primitive.
244
- * @param {Cesium3DTile[]} selectedTiles The tiles selected this frame.
245
- * @returns {boolean} {@code true} if the tile set has changed.
342
+ * @param {Set<Cesium3DTile>} previousTiles The prior selected tile set.
343
+ * @param {Cesium3DTile[]} selectedTiles The current selected tiles.
344
+ * @returns {boolean} Whether the tile set changed.
246
345
  * @private
247
346
  */
248
- function haveSelectedTilesChanged(primitive, selectedTiles) {
249
- const prevSet = primitive._selectedTileSet;
250
- if (!defined(prevSet) || prevSet.size !== selectedTiles.length) {
347
+ function haveSelectedTilesChanged(previousTiles, selectedTiles) {
348
+ if (previousTiles.size !== selectedTiles.length) {
251
349
  return true;
252
350
  }
253
351
 
254
352
  for (let i = 0; i < selectedTiles.length; i++) {
255
- if (!prevSet.has(selectedTiles[i])) {
353
+ if (!previousTiles.has(selectedTiles[i])) {
256
354
  return true;
257
355
  }
258
356
  }
@@ -868,9 +966,7 @@ function GaussianSplatPrimitive(options) {
868
966
  * @private
869
967
  */
870
968
  this._scratchAggregateShBuffer = undefined;
871
- this._selectedTilesStableFrames = 0;
872
- this._needsSnapshotRebuild = false;
873
- this._snapshotRebuildStallFrames = 0;
969
+ this._snapshotRebuildScheduler = new SnapshotRebuildScheduler();
874
970
 
875
971
  /**
876
972
  * The previous view matrix used to determine if the primitive needs to be updated.
@@ -957,7 +1053,6 @@ function GaussianSplatPrimitive(options) {
957
1053
  * @private
958
1054
  */
959
1055
  this.selectedTileLength = 0;
960
- this._selectedTileSet = new Set();
961
1056
 
962
1057
  /**
963
1058
  * Indicates whether or not the primitive is ready for use.
@@ -1096,6 +1191,7 @@ Object.defineProperties(GaussianSplatPrimitive.prototype, {
1096
1191
  get: function () {
1097
1192
  return (
1098
1193
  !this._dirty &&
1194
+ !this._snapshotRebuildScheduler.isPending &&
1099
1195
  (!defined(this._pendingSnapshot) ||
1100
1196
  this._pendingSnapshot.state === SnapshotState.READY)
1101
1197
  );
@@ -1197,14 +1293,15 @@ GaussianSplatPrimitive.prototype.isDestroyed = function () {
1197
1293
  };
1198
1294
 
1199
1295
  /**
1200
- * Event callback for when a tile is loaded.
1201
- * This method is called when a tile is loaded and the primitive needs to be updated.
1202
- * It sets the dirty flag to true, indicating that the primitive needs to be rebuilt.
1296
+ * Marks the primitive dirty when a selected tile loads new content.
1297
+ * Loads outside the selected set do not affect the current snapshot.
1203
1298
  * @param {Cesium3DTile} tile
1204
1299
  * @private
1205
1300
  */
1206
1301
  GaussianSplatPrimitive.prototype.onTileLoad = function (tile) {
1207
- this._dirty = true;
1302
+ if (this._snapshotRebuildScheduler.hasSelectedTile(tile)) {
1303
+ this._dirty = true;
1304
+ }
1208
1305
  };
1209
1306
 
1210
1307
  /**
@@ -1711,43 +1808,32 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1711
1808
  return;
1712
1809
  }
1713
1810
 
1811
+ const selectedTiles = tileset._selectedTiles;
1812
+ const hasSelectedTiles = selectedTiles.length !== 0;
1813
+ const statistics = tileset.statistics;
1814
+ const isBootstrap =
1815
+ !defined(this._snapshot) &&
1816
+ !defined(this._pendingSnapshot) &&
1817
+ !defined(this._drawCommand);
1818
+ const allowSnapshotRebuild = this._snapshotRebuildScheduler.update(
1819
+ getTimestamp(),
1820
+ {
1821
+ selectedTiles: selectedTiles,
1822
+ rebuildRequested: this._dirty,
1823
+ isBootstrap: isBootstrap,
1824
+ tilesLoaded: tileset.tilesLoaded,
1825
+ tilesInFlight:
1826
+ defined(statistics) &&
1827
+ (statistics.numberOfTilesProcessing > 0 ||
1828
+ statistics.numberOfPendingRequests > 0),
1829
+ hasCommittedSnapshot: defined(this._snapshot),
1830
+ },
1831
+ );
1832
+
1714
1833
  if (this._sorterState === GaussianSplatSortingState.IDLE) {
1715
- const selectedTilesChanged =
1716
- tileset._selectedTiles.length !== 0 &&
1717
- haveSelectedTilesChanged(this, tileset._selectedTiles);
1718
- if (tileset._selectedTiles.length === 0) {
1719
- this._selectedTilesStableFrames = 0;
1720
- this._needsSnapshotRebuild = false;
1721
- this._snapshotRebuildStallFrames = 0;
1722
- } else if (selectedTilesChanged) {
1723
- this._selectedTilesStableFrames = 0;
1724
- } else {
1725
- this._selectedTilesStableFrames++;
1726
- }
1727
- if (selectedTilesChanged || this._dirty) {
1728
- this._needsSnapshotRebuild = true;
1729
- }
1730
- const isStable = this._selectedTilesStableFrames >= DEFAULT_STABLE_FRAMES;
1731
- const isBootstrap =
1732
- !defined(this._snapshot) &&
1733
- !defined(this._pendingSnapshot) &&
1734
- !defined(this._drawCommand);
1735
- // This prevents an indefinite wait if selected tiles never settle completely.
1736
- // In practice, this is the upper bound on "wait-for-stability" before forcing
1737
- // a rebuild to avoid visible starvation.
1738
- if (this._needsSnapshotRebuild && tileset._selectedTiles.length !== 0) {
1739
- this._snapshotRebuildStallFrames++;
1740
- } else {
1741
- this._snapshotRebuildStallFrames = 0;
1742
- }
1743
- const allowRebuild =
1744
- isStable ||
1745
- isBootstrap ||
1746
- this._snapshotRebuildStallFrames >= DEFAULT_MAX_SNAPSHOT_STALL_FRAMES;
1747
1834
  const hasPendingWork =
1748
1835
  this._dirty ||
1749
- this._needsSnapshotRebuild ||
1750
- selectedTilesChanged ||
1836
+ this._snapshotRebuildScheduler.isPending ||
1751
1837
  defined(this._pendingSnapshot) ||
1752
1838
  defined(this._pendingSortPromise) ||
1753
1839
  !defined(this._drawCommand);
@@ -1759,10 +1845,11 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1759
1845
  }
1760
1846
 
1761
1847
  if (
1762
- tileset._selectedTiles.length !== 0 &&
1763
- this._needsSnapshotRebuild &&
1764
- allowRebuild
1848
+ hasSelectedTiles &&
1849
+ this._snapshotRebuildScheduler.isPending &&
1850
+ allowSnapshotRebuild
1765
1851
  ) {
1852
+ this._snapshotRebuildScheduler.markRebuildStarted();
1766
1853
  this._splatDataGeneration++;
1767
1854
  this._activeSort = undefined;
1768
1855
  this._sorterPromise = undefined;
@@ -1971,11 +2058,8 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1971
2058
  state: SnapshotState.BUILDING,
1972
2059
  };
1973
2060
 
1974
- this.selectedTileLength = tileset._selectedTiles.length;
1975
- this._selectedTileSet = new Set(tileset._selectedTiles);
2061
+ this.selectedTileLength = selectedTiles.length;
1976
2062
  this._dirty = false;
1977
- this._needsSnapshotRebuild = false;
1978
- this._snapshotRebuildStallFrames = 0;
1979
2063
  }
1980
2064
 
1981
2065
  if (defined(this._pendingSnapshot)) {
@@ -2144,4 +2228,7 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
2144
2228
  this._dirty = false;
2145
2229
  };
2146
2230
 
2231
+ // For testing.
2232
+ GaussianSplatPrimitive._SnapshotRebuildScheduler = SnapshotRebuildScheduler;
2233
+
2147
2234
  export default GaussianSplatPrimitive;
@@ -2,9 +2,11 @@ import Check from "../Core/Check.js";
2
2
  import Frozen from "../Core/Frozen.js";
3
3
  import defined from "../Core/defined.js";
4
4
  import findMeshoptExtension from "./findMeshoptExtension.js";
5
- import { MeshoptDecoder } from "meshoptimizer";
6
5
  import ResourceLoader from "./ResourceLoader.js";
7
6
  import ResourceLoaderState from "./ResourceLoaderState.js";
7
+ import TaskProcessor from "../Core/TaskProcessor.js";
8
+
9
+ const decodeMeshoptTaskProcessor = new TaskProcessor("decodeMeshopt");
8
10
 
9
11
  /**
10
12
  * Loads a glTF buffer view.
@@ -159,29 +161,39 @@ async function loadResources(loader) {
159
161
  }
160
162
 
161
163
  const bufferTypedArray = bufferLoader.typedArray;
162
- const bufferViewTypedArray = new Uint8Array(
164
+ let bufferViewTypedArray = new Uint8Array(
163
165
  bufferTypedArray.buffer,
164
166
  bufferTypedArray.byteOffset + loader._byteOffset,
165
167
  loader._byteLength,
166
168
  );
167
169
 
170
+ if (loader._hasMeshopt) {
171
+ // The buffer can be shared with other cached resources. Copy the encoded
172
+ // range before transferring it to the worker so the cache stays intact.
173
+ bufferViewTypedArray = new Uint8Array(bufferViewTypedArray);
174
+ }
175
+
168
176
  // Unload the buffer
169
177
  loader.unload();
170
178
 
171
179
  loader._typedArray = bufferViewTypedArray;
172
180
  if (loader._hasMeshopt) {
173
- const count = loader._meshoptCount;
174
- const byteStride = loader._meshoptByteStride;
175
- const result = new Uint8Array(count * byteStride);
176
- MeshoptDecoder.decodeGltfBuffer(
177
- result,
178
- count,
179
- byteStride,
180
- loader._typedArray,
181
- loader._meshoptMode,
182
- loader._meshoptFilter,
181
+ const result = await decodeMeshoptTaskProcessor.scheduleTask(
182
+ {
183
+ source: loader._typedArray,
184
+ count: loader._meshoptCount,
185
+ byteStride: loader._meshoptByteStride,
186
+ mode: loader._meshoptMode,
187
+ filter: loader._meshoptFilter,
188
+ },
189
+ [loader._typedArray.buffer],
183
190
  );
184
- loader._typedArray = result;
191
+
192
+ if (loader.isDestroyed()) {
193
+ return;
194
+ }
195
+
196
+ loader._typedArray = new Uint8Array(result);
185
197
  }
186
198
 
187
199
  loader._state = ResourceLoaderState.READY;
@@ -4,7 +4,7 @@ import defined from "../Core/defined.js";
4
4
  import RuntimeError from "../Core/RuntimeError.js";
5
5
  import ResourceLoader from "./ResourceLoader.js";
6
6
  import ResourceLoaderState from "./ResourceLoaderState.js";
7
- import { loadSpz } from "@spz-loader/core";
7
+ import SpzDecoder from "./SpzDecoder.js";
8
8
 
9
9
  // Cumulative number of SH coefficient floats per splat per channel for each
10
10
  // degree. Degree 0 has no extra SH data (base color is stored separately in
@@ -234,9 +234,10 @@ class GltfSpzLoader extends ResourceLoader {
234
234
  }
235
235
  }
236
236
 
237
- const decodePromise = loadSpz(this._bufferViewTypedArray, {
238
- unpackOptions: { coordinateSystem: "UNSPECIFIED" },
239
- });
237
+ // Buffer views can be shared by cached resources. Transfer a private copy
238
+ // so decoding does not detach the cached source.
239
+ const spzData = new Uint8Array(this._bufferViewTypedArray);
240
+ const decodePromise = SpzDecoder.decode(spzData);
240
241
 
241
242
  if (!defined(decodePromise)) {
242
243
  return false;
@@ -0,0 +1,106 @@
1
+ import defined from "../Core/defined.js";
2
+ import DeveloperError from "../Core/DeveloperError.js";
3
+ import getAbsoluteUri from "../Core/getAbsoluteUri.js";
4
+ import TaskProcessor from "../Core/TaskProcessor.js";
5
+
6
+ const defaultWorkerModuleId = "decodeSpz";
7
+
8
+ function getDecoderTaskProcessor() {
9
+ let taskProcessor = SpzDecoder._decoderTaskProcessor;
10
+ if (!defined(taskProcessor)) {
11
+ const workerModuleUrl = SpzDecoder._workerModuleUrl;
12
+ const workerPath = defined(workerModuleUrl)
13
+ ? getAbsoluteUri(workerModuleUrl)
14
+ : defaultWorkerModuleId;
15
+ taskProcessor = new TaskProcessor(workerPath);
16
+ SpzDecoder._decoderTaskProcessor = taskProcessor;
17
+ }
18
+
19
+ return taskProcessor;
20
+ }
21
+
22
+ /**
23
+ * Configures the Web Worker module used to decode SPZ-compressed Gaussian
24
+ * splats.
25
+ *
26
+ * @alias SpzDecoder
27
+ * @namespace
28
+ *
29
+ * @experimental This feature is not final and is subject to change without
30
+ * Cesium's standard deprecation policy.
31
+ */
32
+ function SpzDecoder() {}
33
+
34
+ Object.defineProperties(SpzDecoder, {
35
+ /**
36
+ * The URL of the ECMAScript Web Worker module that decodes SPZ data. Set
37
+ * this before the first SPZ decode to use a decoder other than Cesium's
38
+ * bundled <code>Workers/decodeSpz.js</code> worker. Relative URLs are
39
+ * resolved relative to the document URL.
40
+ *
41
+ * To apply a separate Content Security Policy to the decoder, serve the
42
+ * module as a separate, same-origin resource. Cross-origin URLs use a
43
+ * <code>blob:</code> worker fallback, which inherits the document's policy.
44
+ *
45
+ * The module must use {@link createTaskProcessorWorker}, receive an object
46
+ * with a <code>spzData</code> {@link Uint8Array}, and return the gcloud
47
+ * object produced by <code>@spz-loader/core</code>. It must transfer the
48
+ * buffers for its typed-array attributes.
49
+ *
50
+ * @memberof SpzDecoder
51
+ * @type {string|undefined}
52
+ * @default undefined
53
+ * @experimental This feature is not final and is subject to change without
54
+ * Cesium's standard deprecation policy.
55
+ */
56
+ workerModuleUrl: {
57
+ get: function () {
58
+ return SpzDecoder._workerModuleUrl;
59
+ },
60
+ set: function (value) {
61
+ if (defined(SpzDecoder._decoderTaskProcessor)) {
62
+ throw new DeveloperError(
63
+ "SpzDecoder.workerModuleUrl must be configured before the first SPZ decode.",
64
+ );
65
+ }
66
+
67
+ if (defined(value) && (typeof value !== "string" || value.length === 0)) {
68
+ throw new DeveloperError(
69
+ "SpzDecoder.workerModuleUrl must be a non-empty string or undefined.",
70
+ );
71
+ }
72
+
73
+ SpzDecoder._workerModuleUrl = value;
74
+ },
75
+ },
76
+ });
77
+
78
+ /**
79
+ * Decodes SPZ data in the configured Web Worker.
80
+ *
81
+ * @param {Uint8Array} spzData The compressed SPZ data. Its backing buffer is
82
+ * transferred to the worker.
83
+ * @returns {Promise<object>|undefined} A promise that resolves to the gcloud
84
+ * decode result, or undefined when the worker is busy.
85
+ * @private
86
+ */
87
+ SpzDecoder.decode = function (spzData) {
88
+ return getDecoderTaskProcessor().scheduleTask({ spzData: spzData }, [
89
+ spzData.buffer,
90
+ ]);
91
+ };
92
+
93
+ SpzDecoder._decoderTaskProcessor = undefined;
94
+ SpzDecoder._workerModuleUrl = undefined;
95
+
96
+ // Exposed for testing purposes.
97
+ SpzDecoder._resetForTesting = function () {
98
+ const taskProcessor = SpzDecoder._decoderTaskProcessor;
99
+ if (defined(taskProcessor) && !taskProcessor.isDestroyed()) {
100
+ taskProcessor.destroy();
101
+ }
102
+ SpzDecoder._decoderTaskProcessor = undefined;
103
+ SpzDecoder._workerModuleUrl = undefined;
104
+ };
105
+
106
+ export default SpzDecoder;