@luma.gl/engine 9.3.0-alpha.2 → 9.3.0-alpha.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 (87) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +8 -4
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +70 -43
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/dist.dev.js +442 -209
  11. package/dist/dist.min.js +37 -79
  12. package/dist/dynamic-texture/dynamic-texture.d.ts +3 -3
  13. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -1
  14. package/dist/dynamic-texture/dynamic-texture.js +187 -36
  15. package/dist/dynamic-texture/dynamic-texture.js.map +1 -1
  16. package/dist/dynamic-texture/texture-data.d.ts +4 -0
  17. package/dist/dynamic-texture/texture-data.d.ts.map +1 -1
  18. package/dist/dynamic-texture/texture-data.js +9 -1
  19. package/dist/dynamic-texture/texture-data.js.map +1 -1
  20. package/dist/factories/pipeline-factory.d.ts +7 -5
  21. package/dist/factories/pipeline-factory.d.ts.map +1 -1
  22. package/dist/factories/pipeline-factory.js +71 -36
  23. package/dist/factories/pipeline-factory.js.map +1 -1
  24. package/dist/factories/shader-factory.d.ts +0 -3
  25. package/dist/factories/shader-factory.d.ts.map +1 -1
  26. package/dist/factories/shader-factory.js +13 -19
  27. package/dist/factories/shader-factory.js.map +1 -1
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/index.cjs +433 -208
  35. package/dist/index.cjs.map +2 -2
  36. package/dist/model/model.d.ts +3 -1
  37. package/dist/model/model.d.ts.map +1 -1
  38. package/dist/model/model.js +11 -9
  39. package/dist/model/model.js.map +1 -1
  40. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  41. package/dist/models/billboard-texture-model.js +10 -8
  42. package/dist/models/billboard-texture-model.js.map +1 -1
  43. package/dist/models/clip-space.js +7 -7
  44. package/dist/modules/picking/index-picking.d.ts +1 -1
  45. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  46. package/dist/modules/picking/index-picking.js +0 -6
  47. package/dist/modules/picking/index-picking.js.map +1 -1
  48. package/dist/passes/get-fragment-shader.js +11 -30
  49. package/dist/passes/get-fragment-shader.js.map +1 -1
  50. package/dist/passes/shader-pass-renderer.d.ts +0 -2
  51. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  52. package/dist/passes/shader-pass-renderer.js +4 -31
  53. package/dist/passes/shader-pass-renderer.js.map +1 -1
  54. package/dist/scenegraph/group-node.d.ts +5 -0
  55. package/dist/scenegraph/group-node.d.ts.map +1 -1
  56. package/dist/scenegraph/group-node.js +12 -0
  57. package/dist/scenegraph/group-node.js.map +1 -1
  58. package/dist/scenegraph/model-node.d.ts +2 -2
  59. package/dist/scenegraph/model-node.d.ts.map +1 -1
  60. package/dist/scenegraph/model-node.js.map +1 -1
  61. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  62. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  63. package/dist/scenegraph/scenegraph-node.js +23 -15
  64. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  65. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  66. package/dist/utils/buffer-layout-order.js +12 -2
  67. package/dist/utils/buffer-layout-order.js.map +1 -1
  68. package/package.json +4 -4
  69. package/src/animation-loop/animation-loop.ts +75 -46
  70. package/src/animation-loop/make-animation-loop.ts +13 -5
  71. package/src/animation-loop/request-animation-frame.ts +32 -6
  72. package/src/dynamic-texture/dynamic-texture.ts +248 -39
  73. package/src/dynamic-texture/texture-data.ts +15 -1
  74. package/src/factories/pipeline-factory.ts +87 -46
  75. package/src/factories/shader-factory.ts +16 -20
  76. package/src/geometries/cone-geometry.ts +6 -1
  77. package/src/geometries/cylinder-geometry.ts +5 -1
  78. package/src/model/model.ts +14 -10
  79. package/src/models/billboard-texture-model.ts +10 -8
  80. package/src/models/clip-space.ts +7 -7
  81. package/src/modules/picking/index-picking.ts +0 -6
  82. package/src/passes/get-fragment-shader.ts +11 -30
  83. package/src/passes/shader-pass-renderer.ts +4 -33
  84. package/src/scenegraph/group-node.ts +16 -0
  85. package/src/scenegraph/model-node.ts +2 -2
  86. package/src/scenegraph/scenegraph-node.ts +27 -16
  87. package/src/utils/buffer-layout-order.ts +18 -2
package/dist/dist.dev.js CHANGED
@@ -281,10 +281,22 @@ var __exports__ = (() => {
281
281
 
282
282
  // src/animation-loop/request-animation-frame.ts
283
283
  function requestAnimationFramePolyfill(callback) {
284
- return typeof window !== "undefined" && window.requestAnimationFrame ? window.requestAnimationFrame(callback) : setTimeout(callback, 1e3 / 60);
284
+ const browserRequestAnimationFrame = typeof window !== "undefined" ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame : null;
285
+ if (browserRequestAnimationFrame) {
286
+ return browserRequestAnimationFrame.call(window, callback);
287
+ }
288
+ return setTimeout(
289
+ () => callback(typeof performance !== "undefined" ? performance.now() : Date.now()),
290
+ 1e3 / 60
291
+ );
285
292
  }
286
293
  function cancelAnimationFramePolyfill(timerId) {
287
- return typeof window !== "undefined" && window.cancelAnimationFrame ? window.cancelAnimationFrame(timerId) : clearTimeout(timerId);
294
+ const browserCancelAnimationFrame = typeof window !== "undefined" ? window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame : null;
295
+ if (browserCancelAnimationFrame) {
296
+ browserCancelAnimationFrame.call(window, timerId);
297
+ return;
298
+ }
299
+ clearTimeout(timerId);
288
300
  }
289
301
 
290
302
  // ../../node_modules/@probe.gl/stats/dist/utils/hi-res-timestamp.js
@@ -482,6 +494,7 @@ var __exports__ = (() => {
482
494
 
483
495
  // src/animation-loop/animation-loop.ts
484
496
  var statIdCounter = 0;
497
+ var ANIMATION_LOOP_STATS = "Animation Loop";
485
498
  var _AnimationLoop = class {
486
499
  device = null;
487
500
  canvas = null;
@@ -489,6 +502,7 @@ var __exports__ = (() => {
489
502
  animationProps = null;
490
503
  timeline = null;
491
504
  stats;
505
+ sharedStats;
492
506
  cpuTime;
493
507
  gpuTime;
494
508
  frameRate;
@@ -501,7 +515,7 @@ var __exports__ = (() => {
501
515
  _resolveNextFrame = null;
502
516
  _cpuStartTime = 0;
503
517
  _error = null;
504
- // _gpuTimeQuery: Query | null = null;
518
+ _lastFrameTime = 0;
505
519
  /*
506
520
  * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context
507
521
  */
@@ -511,10 +525,12 @@ var __exports__ = (() => {
511
525
  if (!props.device) {
512
526
  throw new Error("No device provided");
513
527
  }
514
- this.stats = props.stats || new Stats({ id: "animation-loop-stats" });
528
+ this.stats = props.stats || new Stats({ id: `animation-loop-${statIdCounter++}` });
529
+ this.sharedStats = import_core.luma.stats.get(ANIMATION_LOOP_STATS);
530
+ this.frameRate = this.stats.get("Frame Rate");
531
+ this.frameRate.setSampleSize(1);
515
532
  this.cpuTime = this.stats.get("CPU Time");
516
533
  this.gpuTime = this.stats.get("GPU Time");
517
- this.frameRate = this.stats.get("Frame Rate");
518
534
  this.setProps({ autoResizeViewport: props.autoResizeViewport });
519
535
  this.start = this.start.bind(this);
520
536
  this.stop = this.stop.bind(this);
@@ -524,6 +540,7 @@ var __exports__ = (() => {
524
540
  destroy() {
525
541
  this.stop();
526
542
  this._setDisplay(null);
543
+ this.device?._disableDebugGPUTime();
527
544
  }
528
545
  /** @deprecated Use .destroy() */
529
546
  delete() {
@@ -588,15 +605,16 @@ var __exports__ = (() => {
588
605
  this._nextFramePromise = null;
589
606
  this._resolveNextFrame = null;
590
607
  this._running = false;
608
+ this._lastFrameTime = 0;
591
609
  }
592
610
  return this;
593
611
  }
594
612
  /** Explicitly draw a frame */
595
- redraw() {
613
+ redraw(time) {
596
614
  if (this.device?.isLost || this._error) {
597
615
  return this;
598
616
  }
599
- this._beginFrameTimers();
617
+ this._beginFrameTimers(time);
600
618
  this._setupFrame();
601
619
  this._updateAnimationProps();
602
620
  this._renderFrame(this._getAnimationProps());
@@ -643,6 +661,7 @@ var __exports__ = (() => {
643
661
  this._initializeAnimationProps();
644
662
  this._updateAnimationProps();
645
663
  this._resizeViewport();
664
+ this.device?._enableDebugGPUTime();
646
665
  }
647
666
  _setDisplay(display) {
648
667
  if (this.display) {
@@ -667,11 +686,11 @@ var __exports__ = (() => {
667
686
  cancelAnimationFramePolyfill(this._animationFrameId);
668
687
  this._animationFrameId = null;
669
688
  }
670
- _animationFrame() {
689
+ _animationFrame(time) {
671
690
  if (!this._running) {
672
691
  return;
673
692
  }
674
- this.redraw();
693
+ this.redraw(time);
675
694
  this._requestAnimationFrame();
676
695
  }
677
696
  // Called on each frame, can be overridden to call onRender multiple times
@@ -785,14 +804,8 @@ var __exports__ = (() => {
785
804
  if (!this.device) {
786
805
  return { width: 1, height: 1, aspect: 1 };
787
806
  }
788
- const [width, height] = this.device?.getDefaultCanvasContext().getDevicePixelSize() || [1, 1];
789
- let aspect = 1;
790
- const canvas2 = this.device?.getDefaultCanvasContext().canvas;
791
- if (canvas2 && canvas2.clientHeight) {
792
- aspect = canvas2.clientWidth / canvas2.clientHeight;
793
- } else if (width > 0 && height > 0) {
794
- aspect = width / height;
795
- }
807
+ const [width, height] = this.device.getDefaultCanvasContext().getDrawingBufferSize();
808
+ const aspect = width > 0 && height > 0 ? width / height : 1;
796
809
  return { width, height, aspect };
797
810
  }
798
811
  /** @deprecated Default viewport setup */
@@ -808,13 +821,61 @@ var __exports__ = (() => {
808
821
  );
809
822
  }
810
823
  }
811
- _beginFrameTimers() {
812
- this.frameRate.timeEnd();
813
- this.frameRate.timeStart();
824
+ _beginFrameTimers(time) {
825
+ const now = time ?? (typeof performance !== "undefined" ? performance.now() : Date.now());
826
+ if (this._lastFrameTime) {
827
+ const frameTime = now - this._lastFrameTime;
828
+ if (frameTime > 0) {
829
+ this.frameRate.addTime(frameTime);
830
+ }
831
+ }
832
+ this._lastFrameTime = now;
833
+ if (this.device?._isDebugGPUTimeEnabled()) {
834
+ this._consumeEncodedGpuTime();
835
+ }
814
836
  this.cpuTime.timeStart();
815
837
  }
816
838
  _endFrameTimers() {
839
+ if (this.device?._isDebugGPUTimeEnabled()) {
840
+ this._consumeEncodedGpuTime();
841
+ }
817
842
  this.cpuTime.timeEnd();
843
+ this._updateSharedStats();
844
+ }
845
+ _consumeEncodedGpuTime() {
846
+ if (!this.device) {
847
+ return;
848
+ }
849
+ const gpuTimeMs = this.device.commandEncoder._gpuTimeMs;
850
+ if (gpuTimeMs !== void 0) {
851
+ this.gpuTime.addTime(gpuTimeMs);
852
+ this.device.commandEncoder._gpuTimeMs = void 0;
853
+ }
854
+ }
855
+ _updateSharedStats() {
856
+ if (this.stats === this.sharedStats) {
857
+ return;
858
+ }
859
+ for (const name of Object.keys(this.sharedStats.stats)) {
860
+ if (!this.stats.stats[name]) {
861
+ delete this.sharedStats.stats[name];
862
+ }
863
+ }
864
+ this.stats.forEach((sourceStat) => {
865
+ const targetStat = this.sharedStats.get(sourceStat.name, sourceStat.type);
866
+ targetStat.sampleSize = sourceStat.sampleSize;
867
+ targetStat.time = sourceStat.time;
868
+ targetStat.count = sourceStat.count;
869
+ targetStat.samples = sourceStat.samples;
870
+ targetStat.lastTiming = sourceStat.lastTiming;
871
+ targetStat.lastSampleTime = sourceStat.lastSampleTime;
872
+ targetStat.lastSampleCount = sourceStat.lastSampleCount;
873
+ targetStat._count = sourceStat._count;
874
+ targetStat._time = sourceStat._time;
875
+ targetStat._samples = sourceStat._samples;
876
+ targetStat._startTime = sourceStat._startTime;
877
+ targetStat._timerPending = sourceStat._timerPending;
878
+ });
818
879
  }
819
880
  // Event handling
820
881
  _startEventHandling() {
@@ -843,7 +904,7 @@ var __exports__ = (() => {
843
904
  },
844
905
  onError: (error) => console.error(error),
845
906
  // eslint-disable-line no-console
846
- stats: import_core.luma.stats.get(`animation-loop-${statIdCounter++}`),
907
+ stats: void 0,
847
908
  // view parameters
848
909
  autoResizeViewport: false
849
910
  });
@@ -875,7 +936,10 @@ var __exports__ = (() => {
875
936
  return animationLoop;
876
937
  }
877
938
  function setError(device, error) {
878
- const canvas2 = device?.getDefaultCanvasContext().canvas;
939
+ if (!device) {
940
+ return;
941
+ }
942
+ const canvas2 = device.getDefaultCanvasContext().canvas;
879
943
  if (canvas2 instanceof HTMLCanvasElement) {
880
944
  canvas2.style.overflow = "visible";
881
945
  let errorDiv = document.getElementById("animation-loop-error");
@@ -892,6 +956,9 @@ var __exports__ = (() => {
892
956
  }
893
957
  }
894
958
  function clearError(device) {
959
+ if (!device) {
960
+ return;
961
+ }
895
962
  const errorDiv = document.getElementById("animation-loop-error");
896
963
  if (errorDiv) {
897
964
  errorDiv.remove();
@@ -1019,13 +1086,11 @@ var __exports__ = (() => {
1019
1086
  return moduleData.defaultPipelineFactory;
1020
1087
  }
1021
1088
  device;
1022
- cachingEnabled;
1023
- destroyPolicy;
1024
- debug;
1025
1089
  _hashCounter = 0;
1026
1090
  _hashes = {};
1027
1091
  _renderPipelineCache = {};
1028
1092
  _computePipelineCache = {};
1093
+ _sharedRenderPipelineCache = {};
1029
1094
  get [Symbol.toStringTag]() {
1030
1095
  return "PipelineFactory";
1031
1096
  }
@@ -1034,35 +1099,34 @@ var __exports__ = (() => {
1034
1099
  }
1035
1100
  constructor(device) {
1036
1101
  this.device = device;
1037
- this.cachingEnabled = device.props._cachePipelines;
1038
- this.destroyPolicy = device.props._cacheDestroyPolicy;
1039
- this.debug = device.props.debugFactories;
1040
1102
  }
1041
1103
  /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
1042
1104
  createRenderPipeline(props) {
1043
- if (!this.cachingEnabled) {
1105
+ if (!this.device.props._cachePipelines) {
1044
1106
  return this.device.createRenderPipeline(props);
1045
1107
  }
1046
1108
  const allProps = { ...import_core4.RenderPipeline.defaultProps, ...props };
1047
1109
  const cache = this._renderPipelineCache;
1048
1110
  const hash = this._hashRenderPipeline(allProps);
1049
- let pipeline = cache[hash]?.pipeline;
1111
+ let pipeline = cache[hash]?.resource;
1050
1112
  if (!pipeline) {
1113
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
1051
1114
  pipeline = this.device.createRenderPipeline({
1052
1115
  ...allProps,
1053
- id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached")
1116
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
1117
+ _sharedRenderPipeline: sharedRenderPipeline
1054
1118
  });
1055
1119
  pipeline.hash = hash;
1056
- cache[hash] = { pipeline, useCount: 1 };
1057
- if (this.debug) {
1120
+ cache[hash] = { resource: pipeline, useCount: 1 };
1121
+ if (this.device.props.debugFactories) {
1058
1122
  import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
1059
1123
  }
1060
1124
  } else {
1061
1125
  cache[hash].useCount++;
1062
- if (this.debug) {
1126
+ if (this.device.props.debugFactories) {
1063
1127
  import_core4.log.log(
1064
1128
  3,
1065
- `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
1129
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
1066
1130
  )();
1067
1131
  }
1068
1132
  }
@@ -1070,36 +1134,36 @@ var __exports__ = (() => {
1070
1134
  }
1071
1135
  /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
1072
1136
  createComputePipeline(props) {
1073
- if (!this.cachingEnabled) {
1137
+ if (!this.device.props._cachePipelines) {
1074
1138
  return this.device.createComputePipeline(props);
1075
1139
  }
1076
1140
  const allProps = { ...import_core4.ComputePipeline.defaultProps, ...props };
1077
1141
  const cache = this._computePipelineCache;
1078
1142
  const hash = this._hashComputePipeline(allProps);
1079
- let pipeline = cache[hash]?.pipeline;
1143
+ let pipeline = cache[hash]?.resource;
1080
1144
  if (!pipeline) {
1081
1145
  pipeline = this.device.createComputePipeline({
1082
1146
  ...allProps,
1083
1147
  id: allProps.id ? `${allProps.id}-cached` : void 0
1084
1148
  });
1085
1149
  pipeline.hash = hash;
1086
- cache[hash] = { pipeline, useCount: 1 };
1087
- if (this.debug) {
1150
+ cache[hash] = { resource: pipeline, useCount: 1 };
1151
+ if (this.device.props.debugFactories) {
1088
1152
  import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
1089
1153
  }
1090
1154
  } else {
1091
1155
  cache[hash].useCount++;
1092
- if (this.debug) {
1156
+ if (this.device.props.debugFactories) {
1093
1157
  import_core4.log.log(
1094
1158
  3,
1095
- `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
1159
+ `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`
1096
1160
  )();
1097
1161
  }
1098
1162
  }
1099
1163
  return pipeline;
1100
1164
  }
1101
1165
  release(pipeline) {
1102
- if (!this.cachingEnabled) {
1166
+ if (!this.device.props._cachePipelines) {
1103
1167
  pipeline.destroy();
1104
1168
  return;
1105
1169
  }
@@ -1108,28 +1172,55 @@ var __exports__ = (() => {
1108
1172
  cache[hash].useCount--;
1109
1173
  if (cache[hash].useCount === 0) {
1110
1174
  this._destroyPipeline(pipeline);
1111
- if (this.debug) {
1175
+ if (this.device.props.debugFactories) {
1112
1176
  import_core4.log.log(3, `${this}: ${pipeline} released and destroyed`)();
1113
1177
  }
1114
1178
  } else if (cache[hash].useCount < 0) {
1115
1179
  import_core4.log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
1116
1180
  cache[hash].useCount = 0;
1117
- } else if (this.debug) {
1181
+ } else if (this.device.props.debugFactories) {
1118
1182
  import_core4.log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
1119
1183
  }
1120
1184
  }
1185
+ createSharedRenderPipeline(props) {
1186
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
1187
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
1188
+ if (!sharedCacheItem) {
1189
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
1190
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
1191
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
1192
+ }
1193
+ sharedCacheItem.useCount++;
1194
+ return sharedCacheItem.resource;
1195
+ }
1196
+ releaseSharedRenderPipeline(pipeline) {
1197
+ if (!pipeline.sharedRenderPipeline) {
1198
+ return;
1199
+ }
1200
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
1201
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
1202
+ if (!sharedCacheItem) {
1203
+ return;
1204
+ }
1205
+ sharedCacheItem.useCount--;
1206
+ if (sharedCacheItem.useCount === 0) {
1207
+ sharedCacheItem.resource.destroy();
1208
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
1209
+ }
1210
+ }
1121
1211
  // PRIVATE
1122
- /** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */
1212
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
1123
1213
  _destroyPipeline(pipeline) {
1124
1214
  const cache = this._getCache(pipeline);
1125
- switch (this.destroyPolicy) {
1126
- case "never":
1127
- return false;
1128
- case "unused":
1129
- delete cache[pipeline.hash];
1130
- pipeline.destroy();
1131
- return true;
1215
+ if (!this.device.props._destroyPipelines) {
1216
+ return false;
1132
1217
  }
1218
+ delete cache[pipeline.hash];
1219
+ pipeline.destroy();
1220
+ if (pipeline instanceof import_core4.RenderPipeline) {
1221
+ this.releaseSharedRenderPipeline(pipeline);
1222
+ }
1223
+ return true;
1133
1224
  }
1134
1225
  /** Get the appropriate cache for the type of pipeline */
1135
1226
  _getCache(pipeline) {
@@ -1158,24 +1249,35 @@ var __exports__ = (() => {
1158
1249
  _hashRenderPipeline(props) {
1159
1250
  const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
1160
1251
  const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
1161
- const varyingHash = "-";
1252
+ const varyingHash = this._getWebGLVaryingHash(props);
1162
1253
  const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
1163
1254
  const { type } = this.device;
1164
1255
  switch (type) {
1165
1256
  case "webgl":
1166
- return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
1257
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
1258
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}BL${bufferLayoutHash}`;
1167
1259
  case "webgpu":
1168
1260
  default:
1169
1261
  const parameterHash = this._getHash(JSON.stringify(props.parameters));
1170
1262
  return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
1171
1263
  }
1172
1264
  }
1265
+ _hashSharedRenderPipeline(props) {
1266
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
1267
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
1268
+ const varyingHash = this._getWebGLVaryingHash(props);
1269
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
1270
+ }
1173
1271
  _getHash(key) {
1174
1272
  if (this._hashes[key] === void 0) {
1175
1273
  this._hashes[key] = this._hashCounter++;
1176
1274
  }
1177
1275
  return this._hashes[key];
1178
1276
  }
1277
+ _getWebGLVaryingHash(props) {
1278
+ const { varyings = [], bufferMode = null } = props;
1279
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
1280
+ }
1179
1281
  };
1180
1282
  var PipelineFactory = _PipelineFactory;
1181
1283
  __publicField(PipelineFactory, "defaultProps", { ...import_core4.RenderPipeline.defaultProps });
@@ -1190,9 +1292,6 @@ var __exports__ = (() => {
1190
1292
  return moduleData.defaultShaderFactory;
1191
1293
  }
1192
1294
  device;
1193
- cachingEnabled;
1194
- destroyPolicy;
1195
- debug;
1196
1295
  _cache = {};
1197
1296
  get [Symbol.toStringTag]() {
1198
1297
  return "ShaderFactory";
@@ -1203,40 +1302,37 @@ var __exports__ = (() => {
1203
1302
  /** @internal */
1204
1303
  constructor(device) {
1205
1304
  this.device = device;
1206
- this.cachingEnabled = device.props._cacheShaders;
1207
- this.destroyPolicy = device.props._cacheDestroyPolicy;
1208
- this.debug = true;
1209
1305
  }
1210
1306
  /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
1211
1307
  createShader(props) {
1212
- if (!this.cachingEnabled) {
1308
+ if (!this.device.props._cacheShaders) {
1213
1309
  return this.device.createShader(props);
1214
1310
  }
1215
1311
  const key = this._hashShader(props);
1216
1312
  let cacheEntry = this._cache[key];
1217
1313
  if (!cacheEntry) {
1218
- const shader = this.device.createShader({
1314
+ const resource = this.device.createShader({
1219
1315
  ...props,
1220
1316
  id: props.id ? `${props.id}-cached` : void 0
1221
1317
  });
1222
- this._cache[key] = cacheEntry = { shader, useCount: 1 };
1223
- if (this.debug) {
1224
- import_core5.log.log(3, `${this}: Created new shader ${shader.id}`)();
1318
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
1319
+ if (this.device.props.debugFactories) {
1320
+ import_core5.log.log(3, `${this}: Created new shader ${resource.id}`)();
1225
1321
  }
1226
1322
  } else {
1227
1323
  cacheEntry.useCount++;
1228
- if (this.debug) {
1324
+ if (this.device.props.debugFactories) {
1229
1325
  import_core5.log.log(
1230
1326
  3,
1231
- `${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`
1327
+ `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`
1232
1328
  )();
1233
1329
  }
1234
1330
  }
1235
- return cacheEntry.shader;
1331
+ return cacheEntry.resource;
1236
1332
  }
1237
1333
  /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
1238
1334
  release(shader) {
1239
- if (!this.cachingEnabled) {
1335
+ if (!this.device.props._cacheShaders) {
1240
1336
  shader.destroy();
1241
1337
  return;
1242
1338
  }
@@ -1245,16 +1341,16 @@ var __exports__ = (() => {
1245
1341
  if (cacheEntry) {
1246
1342
  cacheEntry.useCount--;
1247
1343
  if (cacheEntry.useCount === 0) {
1248
- if (this.destroyPolicy === "unused") {
1344
+ if (this.device.props._destroyShaders) {
1249
1345
  delete this._cache[key];
1250
- cacheEntry.shader.destroy();
1251
- if (this.debug) {
1346
+ cacheEntry.resource.destroy();
1347
+ if (this.device.props.debugFactories) {
1252
1348
  import_core5.log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
1253
1349
  }
1254
1350
  }
1255
1351
  } else if (cacheEntry.useCount < 0) {
1256
1352
  throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
1257
- } else if (this.debug) {
1353
+ } else if (this.device.props.debugFactories) {
1258
1354
  import_core5.log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
1259
1355
  }
1260
1356
  }
@@ -1408,6 +1504,16 @@ var __exports__ = (() => {
1408
1504
  };
1409
1505
 
1410
1506
  // src/utils/buffer-layout-order.ts
1507
+ function getMinLocation(attributeNames, shaderLayoutMap) {
1508
+ let minLocation = Infinity;
1509
+ for (const name of attributeNames) {
1510
+ const location = shaderLayoutMap[name];
1511
+ if (location !== void 0) {
1512
+ minLocation = Math.min(minLocation, location);
1513
+ }
1514
+ }
1515
+ return minLocation;
1516
+ }
1411
1517
  function sortedBufferLayoutByShaderSourceLocations(shaderLayout, bufferLayout) {
1412
1518
  const shaderLayoutMap = Object.fromEntries(
1413
1519
  shaderLayout.attributes.map((attr) => [attr.name, attr.location])
@@ -1416,8 +1522,8 @@ var __exports__ = (() => {
1416
1522
  sortedLayout.sort((a, b) => {
1417
1523
  const attributeNamesA = a.attributes ? a.attributes.map((attr) => attr.attribute) : [a.name];
1418
1524
  const attributeNamesB = b.attributes ? b.attributes.map((attr) => attr.attribute) : [b.name];
1419
- const minLocationA = Math.min(...attributeNamesA.map((name) => shaderLayoutMap[name]));
1420
- const minLocationB = Math.min(...attributeNamesB.map((name) => shaderLayoutMap[name]));
1525
+ const minLocationA = getMinLocation(attributeNamesA, shaderLayoutMap);
1526
+ const minLocationB = getMinLocation(attributeNamesB, shaderLayoutMap);
1421
1527
  return minLocationA - minLocationB;
1422
1528
  });
1423
1529
  return sortedLayout;
@@ -1632,6 +1738,15 @@ var __exports__ = (() => {
1632
1738
  function isTextureImageData(data) {
1633
1739
  return typeof data === "object" && data !== null && "data" in data && "width" in data && "height" in data;
1634
1740
  }
1741
+ function resolveTextureImageFormat(data) {
1742
+ const { textureFormat, format } = data;
1743
+ if (textureFormat && format && textureFormat !== format) {
1744
+ throw new Error(
1745
+ `Conflicting texture formats "${textureFormat}" and "${format}" provided for the same mip level`
1746
+ );
1747
+ }
1748
+ return textureFormat ?? format;
1749
+ }
1635
1750
  function getCubeFaceIndex(face) {
1636
1751
  const idx = TEXTURE_CUBE_FACE_MAP[face];
1637
1752
  if (idx === void 0)
@@ -1664,6 +1779,7 @@ var __exports__ = (() => {
1664
1779
  subresources.push({
1665
1780
  type: "texture-data",
1666
1781
  data: imageData,
1782
+ textureFormat: resolveTextureImageFormat(imageData),
1667
1783
  z,
1668
1784
  mipLevel
1669
1785
  });
@@ -1700,7 +1816,7 @@ var __exports__ = (() => {
1700
1816
  data.forEach((cubeData, cubeIndex) => {
1701
1817
  for (const [face, faceData] of Object.entries(cubeData)) {
1702
1818
  const faceDepth = getCubeArrayFaceIndex(cubeIndex, face);
1703
- getTexture2DSubresources(faceDepth, faceData);
1819
+ subresources.push(...getTexture2DSubresources(faceDepth, faceData));
1704
1820
  }
1705
1821
  });
1706
1822
  return subresources;
@@ -1762,6 +1878,9 @@ var __exports__ = (() => {
1762
1878
  try {
1763
1879
  const propsWithSyncData = await this._loadAllData(originalPropsWithAsyncData);
1764
1880
  this._checkNotDestroyed();
1881
+ const subresources = propsWithSyncData.data ? getTextureSubresources(propsWithSyncData) : [];
1882
+ const userProvidedFormat = "format" in originalPropsWithAsyncData && originalPropsWithAsyncData.format !== void 0;
1883
+ const userProvidedUsage = "usage" in originalPropsWithAsyncData && originalPropsWithAsyncData.usage !== void 0;
1765
1884
  const deduceSize = () => {
1766
1885
  if (this.props.width && this.props.height) {
1767
1886
  return { width: this.props.width, height: this.props.height };
@@ -1776,45 +1895,39 @@ var __exports__ = (() => {
1776
1895
  if (!size || size.width <= 0 || size.height <= 0) {
1777
1896
  throw new Error(`${this} size could not be determined or was zero`);
1778
1897
  }
1898
+ const textureData = analyzeTextureSubresources(this.device, subresources, size, {
1899
+ format: userProvidedFormat ? originalPropsWithAsyncData.format : void 0
1900
+ });
1901
+ const resolvedFormat = textureData.format ?? this.props.format;
1779
1902
  const baseTextureProps = {
1780
1903
  ...this.props,
1781
1904
  ...size,
1905
+ format: resolvedFormat,
1782
1906
  mipLevels: 1,
1783
1907
  // temporary; updated below
1784
1908
  data: void 0
1785
1909
  };
1910
+ if (this.device.isTextureFormatCompressed(resolvedFormat) && !userProvidedUsage) {
1911
+ baseTextureProps.usage = import_core9.Texture.SAMPLE | import_core9.Texture.COPY_DST;
1912
+ }
1913
+ const shouldGenerateMipmaps = this.props.mipmaps && !textureData.hasExplicitMipChain && !this.device.isTextureFormatCompressed(resolvedFormat);
1914
+ if (this.device.type === "webgpu" && shouldGenerateMipmaps) {
1915
+ const requiredUsage = this.props.dimension === "3d" ? import_core9.Texture.SAMPLE | import_core9.Texture.STORAGE | import_core9.Texture.COPY_DST | import_core9.Texture.COPY_SRC : import_core9.Texture.SAMPLE | import_core9.Texture.RENDER | import_core9.Texture.COPY_DST | import_core9.Texture.COPY_SRC;
1916
+ baseTextureProps.usage |= requiredUsage;
1917
+ }
1786
1918
  const maxMips = this.device.getMipLevelCount(baseTextureProps.width, baseTextureProps.height);
1787
- const desired = this.props.mipLevels === "auto" ? maxMips : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
1919
+ const desired = textureData.hasExplicitMipChain ? textureData.mipLevels : this.props.mipLevels === "auto" ? maxMips : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
1788
1920
  const finalTextureProps = { ...baseTextureProps, mipLevels: desired };
1789
1921
  this._texture = this.device.createTexture(finalTextureProps);
1790
1922
  this._sampler = this.texture.sampler;
1791
1923
  this._view = this.texture.view;
1792
- if (propsWithSyncData.data) {
1793
- switch (propsWithSyncData.dimension) {
1794
- case "1d":
1795
- this.setTexture1DData(propsWithSyncData.data);
1796
- break;
1797
- case "2d":
1798
- this.setTexture2DData(propsWithSyncData.data);
1799
- break;
1800
- case "3d":
1801
- this.setTexture3DData(propsWithSyncData.data);
1802
- break;
1803
- case "2d-array":
1804
- this.setTextureArrayData(propsWithSyncData.data);
1805
- break;
1806
- case "cube":
1807
- this.setTextureCubeData(propsWithSyncData.data);
1808
- break;
1809
- case "cube-array":
1810
- this.setTextureCubeArrayData(propsWithSyncData.data);
1811
- break;
1812
- default: {
1813
- throw new Error(`Unhandled dimension ${propsWithSyncData.dimension}`);
1814
- }
1815
- }
1924
+ if (textureData.subresources.length) {
1925
+ this._setTextureSubresources(textureData.subresources);
1926
+ }
1927
+ if (this.props.mipmaps && !textureData.hasExplicitMipChain && !shouldGenerateMipmaps) {
1928
+ import_core9.log.warn(`${this} skipping auto-generated mipmaps for compressed texture format`)();
1816
1929
  }
1817
- if (this.props.mipmaps) {
1930
+ if (shouldGenerateMipmaps) {
1818
1931
  this.generateMipmaps();
1819
1932
  }
1820
1933
  this.isReady = true;
@@ -1838,10 +1951,10 @@ var __exports__ = (() => {
1838
1951
  generateMipmaps() {
1839
1952
  if (this.device.type === "webgl") {
1840
1953
  this.texture.generateMipmapsWebGL();
1954
+ } else if (this.device.type === "webgpu") {
1955
+ this.device.generateMipmapsWebGPU(this.texture);
1841
1956
  } else {
1842
- import_core9.log.warn(
1843
- "Mipmap generation not yet implemented on WebGPU: your texture data will not be correctly initialized"
1844
- );
1957
+ import_core9.log.warn(`${this} mipmaps not supported on ${this.device.type}`);
1845
1958
  }
1846
1959
  }
1847
1960
  /** Set sampler or create one from props */
@@ -1939,8 +2052,21 @@ var __exports__ = (() => {
1939
2052
  this.texture.copyExternalImage({ image, z, mipLevel, flipY });
1940
2053
  break;
1941
2054
  case "texture-data":
1942
- const { data } = subresource;
1943
- this.texture.copyImageData({ data: data.data, z, mipLevel });
2055
+ const { data, textureFormat } = subresource;
2056
+ if (textureFormat && textureFormat !== this.texture.format) {
2057
+ throw new Error(
2058
+ `${this} mip level ${mipLevel} uses format "${textureFormat}" but texture format is "${this.texture.format}"`
2059
+ );
2060
+ }
2061
+ this.texture.writeData(data.data, {
2062
+ x: 0,
2063
+ y: 0,
2064
+ z,
2065
+ width: data.width,
2066
+ height: data.height,
2067
+ depthOrArrayLayers: 1,
2068
+ mipLevel
2069
+ });
1944
2070
  break;
1945
2071
  default:
1946
2072
  throw new Error("Unsupported 2D mip-level payload");
@@ -1972,6 +2098,140 @@ var __exports__ = (() => {
1972
2098
  data: null,
1973
2099
  mipmaps: false
1974
2100
  });
2101
+ function getTextureSubresources(props) {
2102
+ if (!props.data) {
2103
+ return [];
2104
+ }
2105
+ switch (props.dimension) {
2106
+ case "1d":
2107
+ return getTexture1DSubresources(props.data);
2108
+ case "2d":
2109
+ return getTexture2DSubresources(0, props.data);
2110
+ case "3d":
2111
+ return getTexture3DSubresources(props.data);
2112
+ case "2d-array":
2113
+ return getTextureArraySubresources(props.data);
2114
+ case "cube":
2115
+ return getTextureCubeSubresources(props.data);
2116
+ case "cube-array":
2117
+ return getTextureCubeArraySubresources(props.data);
2118
+ default:
2119
+ throw new Error(`Unhandled dimension ${props.dimension}`);
2120
+ }
2121
+ }
2122
+ function analyzeTextureSubresources(device, subresources, size, options) {
2123
+ if (subresources.length === 0) {
2124
+ return {
2125
+ subresources,
2126
+ mipLevels: 1,
2127
+ format: options.format,
2128
+ hasExplicitMipChain: false
2129
+ };
2130
+ }
2131
+ const groupedSubresources = /* @__PURE__ */ new Map();
2132
+ for (const subresource of subresources) {
2133
+ const group = groupedSubresources.get(subresource.z) ?? [];
2134
+ group.push(subresource);
2135
+ groupedSubresources.set(subresource.z, group);
2136
+ }
2137
+ const hasExplicitMipChain = subresources.some((subresource) => subresource.mipLevel > 0);
2138
+ let resolvedFormat = options.format;
2139
+ let resolvedMipLevels = Number.POSITIVE_INFINITY;
2140
+ const validSubresources = [];
2141
+ for (const [z, sliceSubresources] of groupedSubresources) {
2142
+ const sortedSubresources = [...sliceSubresources].sort(
2143
+ (left, right) => left.mipLevel - right.mipLevel
2144
+ );
2145
+ const baseLevel = sortedSubresources[0];
2146
+ if (!baseLevel || baseLevel.mipLevel !== 0) {
2147
+ throw new Error(`DynamicTexture: slice ${z} is missing mip level 0`);
2148
+ }
2149
+ const baseSize = getTextureSubresourceSize(device, baseLevel);
2150
+ if (baseSize.width !== size.width || baseSize.height !== size.height) {
2151
+ throw new Error(
2152
+ `DynamicTexture: slice ${z} base level dimensions ${baseSize.width}x${baseSize.height} do not match expected ${size.width}x${size.height}`
2153
+ );
2154
+ }
2155
+ const baseFormat = getTextureSubresourceFormat(baseLevel);
2156
+ if (baseFormat) {
2157
+ if (resolvedFormat && resolvedFormat !== baseFormat) {
2158
+ throw new Error(
2159
+ `DynamicTexture: slice ${z} base level format "${baseFormat}" does not match texture format "${resolvedFormat}"`
2160
+ );
2161
+ }
2162
+ resolvedFormat = baseFormat;
2163
+ }
2164
+ const mipLevelLimit = resolvedFormat && device.isTextureFormatCompressed(resolvedFormat) ? (
2165
+ // Block-compressed formats cannot have mips smaller than a single compression block.
2166
+ getMaxCompressedMipLevels(device, baseSize.width, baseSize.height, resolvedFormat)
2167
+ ) : device.getMipLevelCount(baseSize.width, baseSize.height);
2168
+ let validMipLevelsForSlice = 0;
2169
+ for (let expectedMipLevel = 0; expectedMipLevel < sortedSubresources.length; expectedMipLevel++) {
2170
+ const subresource = sortedSubresources[expectedMipLevel];
2171
+ if (!subresource || subresource.mipLevel !== expectedMipLevel) {
2172
+ break;
2173
+ }
2174
+ if (expectedMipLevel >= mipLevelLimit) {
2175
+ break;
2176
+ }
2177
+ const subresourceSize = getTextureSubresourceSize(device, subresource);
2178
+ const expectedWidth = Math.max(1, baseSize.width >> expectedMipLevel);
2179
+ const expectedHeight = Math.max(1, baseSize.height >> expectedMipLevel);
2180
+ if (subresourceSize.width !== expectedWidth || subresourceSize.height !== expectedHeight) {
2181
+ break;
2182
+ }
2183
+ const subresourceFormat = getTextureSubresourceFormat(subresource);
2184
+ if (subresourceFormat) {
2185
+ if (!resolvedFormat) {
2186
+ resolvedFormat = subresourceFormat;
2187
+ }
2188
+ if (subresourceFormat !== resolvedFormat) {
2189
+ break;
2190
+ }
2191
+ }
2192
+ validMipLevelsForSlice++;
2193
+ validSubresources.push(subresource);
2194
+ }
2195
+ resolvedMipLevels = Math.min(resolvedMipLevels, validMipLevelsForSlice);
2196
+ }
2197
+ const mipLevels = Number.isFinite(resolvedMipLevels) ? Math.max(1, resolvedMipLevels) : 1;
2198
+ return {
2199
+ // Keep every slice trimmed to the same mip count so the texture shape stays internally consistent.
2200
+ subresources: validSubresources.filter((subresource) => subresource.mipLevel < mipLevels),
2201
+ mipLevels,
2202
+ format: resolvedFormat,
2203
+ hasExplicitMipChain
2204
+ };
2205
+ }
2206
+ function getTextureSubresourceFormat(subresource) {
2207
+ if (subresource.type !== "texture-data") {
2208
+ return void 0;
2209
+ }
2210
+ return subresource.textureFormat ?? resolveTextureImageFormat(subresource.data);
2211
+ }
2212
+ function getTextureSubresourceSize(device, subresource) {
2213
+ switch (subresource.type) {
2214
+ case "external-image":
2215
+ return device.getExternalImageSize(subresource.image);
2216
+ case "texture-data":
2217
+ return { width: subresource.data.width, height: subresource.data.height };
2218
+ default:
2219
+ throw new Error("Unsupported texture subresource");
2220
+ }
2221
+ }
2222
+ function getMaxCompressedMipLevels(device, baseWidth, baseHeight, format) {
2223
+ const { blockWidth = 1, blockHeight = 1 } = device.getTextureFormatInfo(format);
2224
+ let mipLevels = 1;
2225
+ for (let mipLevel = 1; ; mipLevel++) {
2226
+ const width = Math.max(1, baseWidth >> mipLevel);
2227
+ const height = Math.max(1, baseHeight >> mipLevel);
2228
+ if (width < blockWidth || height < blockHeight) {
2229
+ break;
2230
+ }
2231
+ mipLevels++;
2232
+ }
2233
+ return mipLevels;
2234
+ }
1975
2235
  async function awaitAllPromises(x) {
1976
2236
  x = await x;
1977
2237
  if (Array.isArray(x)) {
@@ -2147,7 +2407,7 @@ var __exports__ = (() => {
2147
2407
  if (!this._destroyed) {
2148
2408
  this.pipelineFactory.release(this.pipeline);
2149
2409
  this.shaderFactory.release(this.pipeline.vs);
2150
- if (this.pipeline.fs) {
2410
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
2151
2411
  this.shaderFactory.release(this.pipeline.fs);
2152
2412
  }
2153
2413
  this._uniformStore.destroy();
@@ -2197,9 +2457,6 @@ var __exports__ = (() => {
2197
2457
  this._logDrawCallStart();
2198
2458
  this.pipeline = this._updatePipeline();
2199
2459
  const syncBindings = this._getBindings();
2200
- this.pipeline.setBindings(syncBindings, {
2201
- disableWarnings: this.props.disableWarnings
2202
- });
2203
2460
  const { indexBuffer } = this.vertexArray;
2204
2461
  const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === "uint32" ? 4 : 2) : void 0;
2205
2462
  drawSuccess = this.pipeline.draw({
@@ -2210,6 +2467,11 @@ var __exports__ = (() => {
2210
2467
  instanceCount: this.instanceCount,
2211
2468
  indexCount,
2212
2469
  transformFeedback: this.transformFeedback || void 0,
2470
+ // Pipelines may be shared across models when caching is enabled, so bindings
2471
+ // and WebGL uniforms must be supplied on every draw instead of being stored
2472
+ // on the pipeline instance.
2473
+ bindings: syncBindings,
2474
+ uniforms: this.props.uniforms,
2213
2475
  // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
2214
2476
  // so we must provide our unique parameters to each draw
2215
2477
  // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
@@ -2526,8 +2788,9 @@ var __exports__ = (() => {
2526
2788
  );
2527
2789
  if (prevShaderVs)
2528
2790
  this.shaderFactory.release(prevShaderVs);
2529
- if (prevShaderFs)
2791
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
2530
2792
  this.shaderFactory.release(prevShaderFs);
2793
+ }
2531
2794
  }
2532
2795
  return this.pipeline;
2533
2796
  }
@@ -2611,6 +2874,8 @@ var __exports__ = (() => {
2611
2874
  indexBuffer: null,
2612
2875
  attributes: {},
2613
2876
  constantAttributes: {},
2877
+ bindings: {},
2878
+ uniforms: {},
2614
2879
  varyings: [],
2615
2880
  isInstanced: void 0,
2616
2881
  instanceCount: 0,
@@ -2915,9 +3180,9 @@ var __exports__ = (() => {
2915
3180
  var CLIPSPACE_VERTEX_SHADER_WGSL = (
2916
3181
  /* wgsl */
2917
3182
  `struct VertexInputs {
2918
- @location(0) clipSpacePosition: vec2<f32>,
2919
- @location(1) texCoord: vec2<f32>,
2920
- @location(2) coordinate: vec2<f32>
3183
+ @location(0) clipSpacePositions: vec2<f32>,
3184
+ @location(1) texCoords: vec2<f32>,
3185
+ @location(2) coordinates: vec2<f32>
2921
3186
  }
2922
3187
 
2923
3188
  struct FragmentInputs {
@@ -2930,10 +3195,10 @@ struct FragmentInputs {
2930
3195
  @vertex
2931
3196
  fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
2932
3197
  var outputs: FragmentInputs;
2933
- outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.);
2934
- outputs.position = inputs.clipSpacePosition;
2935
- outputs.coordinate = inputs.coordinate;
2936
- outputs.uv = inputs.texCoord;
3198
+ outputs.Position = vec4(inputs.clipSpacePositions, 0., 1.);
3199
+ outputs.position = inputs.clipSpacePositions;
3200
+ outputs.coordinate = inputs.coordinates;
3201
+ outputs.uv = inputs.texCoords;
2937
3202
  return outputs;
2938
3203
  }
2939
3204
  `
@@ -2999,15 +3264,15 @@ struct backgroundUniforms {
2999
3264
  };
3000
3265
  @group(0) @binding(2) var<uniform> background: backgroundUniforms;
3001
3266
 
3002
- fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> {
3267
+ fn billboardTexture_getTextureUV(uv: vec2<f32>) -> vec2<f32> {
3003
3268
  let scale: vec2<f32> = background.scale;
3004
- var position: vec2<f32> = (coordinates - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
3269
+ var position: vec2<f32> = (uv - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
3005
3270
  return position;
3006
3271
  }
3007
3272
 
3008
3273
  @fragment
3009
3274
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
3010
- let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate);
3275
+ let position: vec2<f32> = billboardTexture_getTextureUV(inputs.uv);
3011
3276
  return textureSample(backgroundTexture, backgroundTextureSampler, position);
3012
3277
  }
3013
3278
  `
@@ -3041,20 +3306,22 @@ void main(void) {
3041
3306
  backgroundTexture = null;
3042
3307
  constructor(device, props) {
3043
3308
  super(device, {
3309
+ ...props,
3044
3310
  id: props.id || "background-texture-model",
3045
3311
  source: BACKGROUND_FS_WGSL,
3046
3312
  fs: BACKGROUND_FS,
3047
- modules: [backgroundModule],
3313
+ modules: [...props.modules || [], backgroundModule],
3048
3314
  parameters: {
3049
3315
  depthWriteEnabled: false,
3316
+ ...props.parameters || {},
3050
3317
  ...props.blend ? {
3051
3318
  blend: true,
3052
3319
  blendColorOperation: "add",
3053
3320
  blendAlphaOperation: "add",
3054
- blendColorSrcFactor: "one",
3055
- blendColorDstFactor: "one-minus-src",
3056
- blendAlphaSrcFactor: "one",
3057
- blendAlphaDstFactor: "one-minus-src-alpha"
3321
+ blendColorSrcFactor: "one-minus-dst-alpha",
3322
+ blendColorDstFactor: "one",
3323
+ blendAlphaSrcFactor: "one-minus-dst-alpha",
3324
+ blendAlphaDstFactor: "one"
3058
3325
  } : {}
3059
3326
  }
3060
3327
  });
@@ -5140,6 +5407,11 @@ void main(void) {
5140
5407
  }
5141
5408
 
5142
5409
  // src/scenegraph/scenegraph-node.ts
5410
+ function assert2(condition, message) {
5411
+ if (!condition) {
5412
+ throw new Error(message);
5413
+ }
5414
+ }
5143
5415
  var ScenegraphNode = class {
5144
5416
  id;
5145
5417
  matrix = new Matrix4();
@@ -5171,14 +5443,17 @@ void main(void) {
5171
5443
  return `{type: ScenegraphNode, id: ${this.id})}`;
5172
5444
  }
5173
5445
  setPosition(position) {
5446
+ assert2(position.length === 3, "setPosition requires vector argument");
5174
5447
  this.position = position;
5175
5448
  return this;
5176
5449
  }
5177
5450
  setRotation(rotation) {
5451
+ assert2(rotation.length === 3 || rotation.length === 4, "setRotation requires vector argument");
5178
5452
  this.rotation = rotation;
5179
5453
  return this;
5180
5454
  }
5181
5455
  setScale(scale2) {
5456
+ assert2(scale2.length === 3, "setScale requires vector argument");
5182
5457
  this.scale = scale2;
5183
5458
  return this;
5184
5459
  }
@@ -5206,17 +5481,18 @@ void main(void) {
5206
5481
  return this;
5207
5482
  }
5208
5483
  updateMatrix() {
5209
- const pos = this.position;
5210
- const rot = this.rotation;
5211
- const scale2 = this.scale;
5212
5484
  this.matrix.identity();
5213
- this.matrix.translate(pos);
5214
- this.matrix.rotateXYZ(rot);
5215
- this.matrix.scale(scale2);
5485
+ this.matrix.translate(this.position);
5486
+ if (this.rotation.length === 4) {
5487
+ const rotationMatrix = new Matrix4().fromQuaternion(this.rotation);
5488
+ this.matrix.multiplyRight(rotationMatrix);
5489
+ } else {
5490
+ this.matrix.rotateXYZ(this.rotation);
5491
+ }
5492
+ this.matrix.scale(this.scale);
5216
5493
  return this;
5217
5494
  }
5218
- update(options = {}) {
5219
- const { position, rotation, scale: scale2 } = options;
5495
+ update({ position, rotation, scale: scale2 } = {}) {
5220
5496
  if (position) {
5221
5497
  this.setPosition(position);
5222
5498
  }
@@ -5266,16 +5542,17 @@ void main(void) {
5266
5542
  }
5267
5543
  */
5268
5544
  _setScenegraphNodeProps(props) {
5269
- if ("position" in props) {
5545
+ if (props?.position) {
5270
5546
  this.setPosition(props.position);
5271
5547
  }
5272
- if ("rotation" in props) {
5548
+ if (props?.rotation) {
5273
5549
  this.setRotation(props.rotation);
5274
5550
  }
5275
- if ("scale" in props) {
5551
+ if (props?.scale) {
5276
5552
  this.setScale(props.scale);
5277
5553
  }
5278
- if ("matrix" in props) {
5554
+ this.updateMatrix();
5555
+ if (props?.matrix) {
5279
5556
  this.setMatrix(props.matrix);
5280
5557
  }
5281
5558
  Object.assign(this.props, props);
@@ -5362,6 +5639,17 @@ void main(void) {
5362
5639
  }
5363
5640
  }
5364
5641
  }
5642
+ preorderTraversal(visitor, { worldMatrix = new Matrix4() } = {}) {
5643
+ const modelMatrix = new Matrix4(worldMatrix).multiplyRight(this.matrix);
5644
+ visitor(this, { worldMatrix: modelMatrix });
5645
+ for (const child of this.children) {
5646
+ if (child instanceof GroupNode) {
5647
+ child.preorderTraversal(visitor, { worldMatrix: modelMatrix });
5648
+ } else {
5649
+ visitor(child, { worldMatrix: modelMatrix });
5650
+ }
5651
+ }
5652
+ }
5365
5653
  };
5366
5654
 
5367
5655
  // src/scenegraph/model-node.ts
@@ -6639,26 +6927,16 @@ void main(void) {
6639
6927
  function getFilterShaderWGSL(func) {
6640
6928
  return (
6641
6929
  /* wgsl */
6642
- `// Binding 0:1 is reserved for shader passes
6643
- // @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
6644
- @group(0) @binding(1) var texture: texture_2d<f32>;
6645
- @group(0) @binding(2) var textureSampler: sampler;
6646
-
6647
- // This needs to be aligned with
6648
- // struct FragmentInputs {
6649
- // @location(0) fragUV: vec2f,
6650
- // @location(1) fragPosition: vec4f,
6651
- // @location(2) fragCoordinate: vec4f
6652
- // };
6930
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
6931
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
6653
6932
 
6654
6933
  @fragment
6655
6934
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
6656
- let fragUV = inputs.uv;
6657
- let fragCoordinate = inputs.coordinate;
6658
- let texSize = vec2f(textureDimensions(texture, 0));
6935
+ let texCoord = inputs.coordinate;
6936
+ let texSize = vec2f(textureDimensions(sourceTexture));
6659
6937
 
6660
- var fragColor = textureSample(texture, textureSampler, fragUV);
6661
- fragColor = ${func}(fragColor, texSize, fragCoordinate);
6938
+ var fragColor = textureSample(sourceTexture, sourceTextureSampler, texCoord);
6939
+ fragColor = ${func}(fragColor, texSize, texCoord);
6662
6940
  return fragColor;
6663
6941
  }
6664
6942
  `
@@ -6667,23 +6945,14 @@ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
6667
6945
  function getSamplerShaderWGSL(func) {
6668
6946
  return (
6669
6947
  /* wgsl */
6670
- `// Binding 0:1 is reserved for shader passes
6671
- @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
6672
- @group(0) @binding(1) var texture: texture_2d<f32>;
6673
- @group(0) @binding(2) var sampler: sampler;
6674
-
6675
- struct FragmentInputs = {
6676
- @location(0) fragUV: vec2f,
6677
- @location(1) fragPosition: vec4f,
6678
- @location(2) fragCoordinate: vec4f
6679
- };
6948
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
6949
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
6680
6950
 
6681
6951
  @fragment
6682
6952
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
6683
- let texSize = vec2f(textureDimensions(texture, 0));
6684
- var fragColor = textureSample(texture, sampler, fragUV);
6685
- fragColor = ${func}(fragColor, texSize, texCoord);
6686
- return fragColor;
6953
+ let texCoord = inputs.coordinate;
6954
+ let texSize = vec2f(textureDimensions(sourceTexture));
6955
+ return ${func}(sourceTexture, sourceTextureSampler, texSize, texCoord);
6687
6956
  }
6688
6957
  `
6689
6958
  );
@@ -6742,8 +7011,6 @@ void main() {
6742
7011
  shaderInputs;
6743
7012
  passRenderers;
6744
7013
  swapFramebuffers;
6745
- /** For rendering to the screen */
6746
- clipSpace;
6747
7014
  textureModel;
6748
7015
  constructor(device, props) {
6749
7016
  this.device = device;
@@ -6762,33 +7029,6 @@ void main() {
6762
7029
  this.textureModel = new BackgroundTextureModel(device, {
6763
7030
  backgroundTexture: this.swapFramebuffers.current.colorAttachments[0].texture
6764
7031
  });
6765
- this.clipSpace = new ClipSpace(device, {
6766
- source: (
6767
- /* wgsl */
6768
- ` @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
6769
- @group(0) @binding(1) var sourceTextureSampler: sampler;
6770
-
6771
- @fragment
6772
- fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
6773
- let texCoord: vec2<f32> = inputs.coordinate;
6774
- return textureSample(sourceTexture, sourceTextureSampler, texCoord);
6775
- }
6776
- `
6777
- ),
6778
- fs: (
6779
- /* glsl */
6780
- `#version 300 es
6781
-
6782
- uniform sampler2D sourceTexture;
6783
- in vec2 uv;
6784
- out vec4 fragColor;
6785
-
6786
- void main() {
6787
- fragColor = texture(sourceTexture, uv);
6788
- }
6789
- `
6790
- )
6791
- });
6792
7032
  this.passRenderers = props.shaderPasses.map((shaderPass) => new PassRenderer(device, shaderPass));
6793
7033
  }
6794
7034
  /** Destroys resources created by this ShaderPassRenderer */
@@ -6797,7 +7037,6 @@ void main() {
6797
7037
  subPassRenderer.destroy();
6798
7038
  }
6799
7039
  this.swapFramebuffers.destroy();
6800
- this.clipSpace.destroy();
6801
7040
  this.textureModel.destroy();
6802
7041
  }
6803
7042
  resize(size) {
@@ -6809,15 +7048,15 @@ void main() {
6809
7048
  if (!outputTexture) {
6810
7049
  return false;
6811
7050
  }
6812
- const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilAttachment: false });
7051
+ const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilFormat: false });
6813
7052
  const renderPass = this.device.beginRenderPass({
6814
7053
  id: "shader-pass-renderer-to-screen",
6815
7054
  framebuffer,
6816
7055
  // clearColor: [1, 1, 0, 1],
6817
- clearDepth: 1
7056
+ clearDepth: false
6818
7057
  });
6819
- this.clipSpace.setBindings({ sourceTexture: outputTexture });
6820
- this.clipSpace.draw(renderPass);
7058
+ this.textureModel.setProps({ backgroundTexture: outputTexture });
7059
+ this.textureModel.draw(renderPass);
6821
7060
  renderPass.end();
6822
7061
  return true;
6823
7062
  }
@@ -7329,12 +7568,6 @@ const INDEX_PICKING_MODE_INSTANCE = 0;
7329
7568
  const INDEX_PICKING_MODE_CUSTOM = 1;
7330
7569
  const INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
7331
7570
 
7332
- struct indexPickingFragmentInputs = {
7333
- objectIndex: int32;
7334
- };
7335
-
7336
- let indexPickingFragmentInputs: indexPickingFragmentInputs;
7337
-
7338
7571
  /**
7339
7572
  * Vertex shaders should call this function to set the object index.
7340
7573
  * If using instance or vertex mode, argument will be ignored, 0 can be supplied.