@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/index.cjs CHANGED
@@ -246,15 +246,25 @@ var import_core = require("@luma.gl/core");
246
246
 
247
247
  // dist/animation-loop/request-animation-frame.js
248
248
  function requestAnimationFramePolyfill(callback) {
249
- return typeof window !== "undefined" && window.requestAnimationFrame ? window.requestAnimationFrame(callback) : setTimeout(callback, 1e3 / 60);
249
+ const browserRequestAnimationFrame = typeof window !== "undefined" ? window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame : null;
250
+ if (browserRequestAnimationFrame) {
251
+ return browserRequestAnimationFrame.call(window, callback);
252
+ }
253
+ return setTimeout(() => callback(typeof performance !== "undefined" ? performance.now() : Date.now()), 1e3 / 60);
250
254
  }
251
255
  function cancelAnimationFramePolyfill(timerId) {
252
- return typeof window !== "undefined" && window.cancelAnimationFrame ? window.cancelAnimationFrame(timerId) : clearTimeout(timerId);
256
+ const browserCancelAnimationFrame = typeof window !== "undefined" ? window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame : null;
257
+ if (browserCancelAnimationFrame) {
258
+ browserCancelAnimationFrame.call(window, timerId);
259
+ return;
260
+ }
261
+ clearTimeout(timerId);
253
262
  }
254
263
 
255
264
  // dist/animation-loop/animation-loop.js
256
265
  var import_stats = require("@probe.gl/stats");
257
266
  var statIdCounter = 0;
267
+ var ANIMATION_LOOP_STATS = "Animation Loop";
258
268
  var _AnimationLoop = class {
259
269
  device = null;
260
270
  canvas = null;
@@ -262,6 +272,7 @@ var _AnimationLoop = class {
262
272
  animationProps = null;
263
273
  timeline = null;
264
274
  stats;
275
+ sharedStats;
265
276
  cpuTime;
266
277
  gpuTime;
267
278
  frameRate;
@@ -274,7 +285,7 @@ var _AnimationLoop = class {
274
285
  _resolveNextFrame = null;
275
286
  _cpuStartTime = 0;
276
287
  _error = null;
277
- // _gpuTimeQuery: Query | null = null;
288
+ _lastFrameTime = 0;
278
289
  /*
279
290
  * @param {HTMLCanvasElement} canvas - if provided, width and height will be passed to context
280
291
  */
@@ -284,10 +295,12 @@ var _AnimationLoop = class {
284
295
  if (!props.device) {
285
296
  throw new Error("No device provided");
286
297
  }
287
- this.stats = props.stats || new import_stats.Stats({ id: "animation-loop-stats" });
298
+ this.stats = props.stats || new import_stats.Stats({ id: `animation-loop-${statIdCounter++}` });
299
+ this.sharedStats = import_core.luma.stats.get(ANIMATION_LOOP_STATS);
300
+ this.frameRate = this.stats.get("Frame Rate");
301
+ this.frameRate.setSampleSize(1);
288
302
  this.cpuTime = this.stats.get("CPU Time");
289
303
  this.gpuTime = this.stats.get("GPU Time");
290
- this.frameRate = this.stats.get("Frame Rate");
291
304
  this.setProps({ autoResizeViewport: props.autoResizeViewport });
292
305
  this.start = this.start.bind(this);
293
306
  this.stop = this.stop.bind(this);
@@ -295,8 +308,10 @@ var _AnimationLoop = class {
295
308
  this._onMouseleave = this._onMouseleave.bind(this);
296
309
  }
297
310
  destroy() {
311
+ var _a;
298
312
  this.stop();
299
313
  this._setDisplay(null);
314
+ (_a = this.device) == null ? void 0 : _a._disableDebugGPUTime();
300
315
  }
301
316
  /** @deprecated Use .destroy() */
302
317
  delete() {
@@ -361,16 +376,17 @@ var _AnimationLoop = class {
361
376
  this._nextFramePromise = null;
362
377
  this._resolveNextFrame = null;
363
378
  this._running = false;
379
+ this._lastFrameTime = 0;
364
380
  }
365
381
  return this;
366
382
  }
367
383
  /** Explicitly draw a frame */
368
- redraw() {
384
+ redraw(time) {
369
385
  var _a;
370
386
  if (((_a = this.device) == null ? void 0 : _a.isLost) || this._error) {
371
387
  return this;
372
388
  }
373
- this._beginFrameTimers();
389
+ this._beginFrameTimers(time);
374
390
  this._setupFrame();
375
391
  this._updateAnimationProps();
376
392
  this._renderFrame(this._getAnimationProps());
@@ -413,10 +429,12 @@ var _AnimationLoop = class {
413
429
  }
414
430
  // PRIVATE METHODS
415
431
  _initialize() {
432
+ var _a;
416
433
  this._startEventHandling();
417
434
  this._initializeAnimationProps();
418
435
  this._updateAnimationProps();
419
436
  this._resizeViewport();
437
+ (_a = this.device) == null ? void 0 : _a._enableDebugGPUTime();
420
438
  }
421
439
  _setDisplay(display) {
422
440
  if (this.display) {
@@ -441,11 +459,11 @@ var _AnimationLoop = class {
441
459
  cancelAnimationFramePolyfill(this._animationFrameId);
442
460
  this._animationFrameId = null;
443
461
  }
444
- _animationFrame() {
462
+ _animationFrame(time) {
445
463
  if (!this._running) {
446
464
  return;
447
465
  }
448
- this.redraw();
466
+ this.redraw(time);
449
467
  this._requestAnimationFrame();
450
468
  }
451
469
  // Called on each frame, can be overridden to call onRender multiple times
@@ -558,18 +576,11 @@ var _AnimationLoop = class {
558
576
  }
559
577
  }
560
578
  _getSizeAndAspect() {
561
- var _a, _b;
562
579
  if (!this.device) {
563
580
  return { width: 1, height: 1, aspect: 1 };
564
581
  }
565
- const [width, height] = ((_a = this.device) == null ? void 0 : _a.getDefaultCanvasContext().getDevicePixelSize()) || [1, 1];
566
- let aspect = 1;
567
- const canvas2 = (_b = this.device) == null ? void 0 : _b.getDefaultCanvasContext().canvas;
568
- if (canvas2 && canvas2.clientHeight) {
569
- aspect = canvas2.clientWidth / canvas2.clientHeight;
570
- } else if (width > 0 && height > 0) {
571
- aspect = width / height;
572
- }
582
+ const [width, height] = this.device.getDefaultCanvasContext().getDrawingBufferSize();
583
+ const aspect = width > 0 && height > 0 ? width / height : 1;
573
584
  return { width, height, aspect };
574
585
  }
575
586
  /** @deprecated Default viewport setup */
@@ -585,13 +596,63 @@ var _AnimationLoop = class {
585
596
  );
586
597
  }
587
598
  }
588
- _beginFrameTimers() {
589
- this.frameRate.timeEnd();
590
- this.frameRate.timeStart();
599
+ _beginFrameTimers(time) {
600
+ var _a;
601
+ const now = time ?? (typeof performance !== "undefined" ? performance.now() : Date.now());
602
+ if (this._lastFrameTime) {
603
+ const frameTime = now - this._lastFrameTime;
604
+ if (frameTime > 0) {
605
+ this.frameRate.addTime(frameTime);
606
+ }
607
+ }
608
+ this._lastFrameTime = now;
609
+ if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) {
610
+ this._consumeEncodedGpuTime();
611
+ }
591
612
  this.cpuTime.timeStart();
592
613
  }
593
614
  _endFrameTimers() {
615
+ var _a;
616
+ if ((_a = this.device) == null ? void 0 : _a._isDebugGPUTimeEnabled()) {
617
+ this._consumeEncodedGpuTime();
618
+ }
594
619
  this.cpuTime.timeEnd();
620
+ this._updateSharedStats();
621
+ }
622
+ _consumeEncodedGpuTime() {
623
+ if (!this.device) {
624
+ return;
625
+ }
626
+ const gpuTimeMs = this.device.commandEncoder._gpuTimeMs;
627
+ if (gpuTimeMs !== void 0) {
628
+ this.gpuTime.addTime(gpuTimeMs);
629
+ this.device.commandEncoder._gpuTimeMs = void 0;
630
+ }
631
+ }
632
+ _updateSharedStats() {
633
+ if (this.stats === this.sharedStats) {
634
+ return;
635
+ }
636
+ for (const name of Object.keys(this.sharedStats.stats)) {
637
+ if (!this.stats.stats[name]) {
638
+ delete this.sharedStats.stats[name];
639
+ }
640
+ }
641
+ this.stats.forEach((sourceStat) => {
642
+ const targetStat = this.sharedStats.get(sourceStat.name, sourceStat.type);
643
+ targetStat.sampleSize = sourceStat.sampleSize;
644
+ targetStat.time = sourceStat.time;
645
+ targetStat.count = sourceStat.count;
646
+ targetStat.samples = sourceStat.samples;
647
+ targetStat.lastTiming = sourceStat.lastTiming;
648
+ targetStat.lastSampleTime = sourceStat.lastSampleTime;
649
+ targetStat.lastSampleCount = sourceStat.lastSampleCount;
650
+ targetStat._count = sourceStat._count;
651
+ targetStat._time = sourceStat._time;
652
+ targetStat._samples = sourceStat._samples;
653
+ targetStat._startTime = sourceStat._startTime;
654
+ targetStat._timerPending = sourceStat._timerPending;
655
+ });
595
656
  }
596
657
  // Event handling
597
658
  _startEventHandling() {
@@ -620,7 +681,7 @@ __publicField(AnimationLoop, "defaultAnimationLoopProps", {
620
681
  },
621
682
  onError: (error) => console.error(error),
622
683
  // eslint-disable-line no-console
623
- stats: import_core.luma.stats.get(`animation-loop-${statIdCounter++}`),
684
+ stats: void 0,
624
685
  // view parameters
625
686
  autoResizeViewport: false
626
687
  });
@@ -653,7 +714,10 @@ function makeAnimationLoop(AnimationLoopTemplateCtor, props) {
653
714
  }
654
715
  function setError(device, error) {
655
716
  var _a;
656
- const canvas2 = device == null ? void 0 : device.getDefaultCanvasContext().canvas;
717
+ if (!device) {
718
+ return;
719
+ }
720
+ const canvas2 = device.getDefaultCanvasContext().canvas;
657
721
  if (canvas2 instanceof HTMLCanvasElement) {
658
722
  canvas2.style.overflow = "visible";
659
723
  let errorDiv = document.getElementById("animation-loop-error");
@@ -670,6 +734,9 @@ function setError(device, error) {
670
734
  }
671
735
  }
672
736
  function clearError(device) {
737
+ if (!device) {
738
+ return;
739
+ }
673
740
  const errorDiv = document.getElementById("animation-loop-error");
674
741
  if (errorDiv) {
675
742
  errorDiv.remove();
@@ -798,13 +865,11 @@ var _PipelineFactory = class {
798
865
  return moduleData.defaultPipelineFactory;
799
866
  }
800
867
  device;
801
- cachingEnabled;
802
- destroyPolicy;
803
- debug;
804
868
  _hashCounter = 0;
805
869
  _hashes = {};
806
870
  _renderPipelineCache = {};
807
871
  _computePipelineCache = {};
872
+ _sharedRenderPipelineCache = {};
808
873
  get [Symbol.toStringTag]() {
809
874
  return "PipelineFactory";
810
875
  }
@@ -813,34 +878,33 @@ var _PipelineFactory = class {
813
878
  }
814
879
  constructor(device) {
815
880
  this.device = device;
816
- this.cachingEnabled = device.props._cachePipelines;
817
- this.destroyPolicy = device.props._cacheDestroyPolicy;
818
- this.debug = device.props.debugFactories;
819
881
  }
820
882
  /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
821
883
  createRenderPipeline(props) {
822
884
  var _a;
823
- if (!this.cachingEnabled) {
885
+ if (!this.device.props._cachePipelines) {
824
886
  return this.device.createRenderPipeline(props);
825
887
  }
826
888
  const allProps = { ...import_core4.RenderPipeline.defaultProps, ...props };
827
889
  const cache = this._renderPipelineCache;
828
890
  const hash = this._hashRenderPipeline(allProps);
829
- let pipeline = (_a = cache[hash]) == null ? void 0 : _a.pipeline;
891
+ let pipeline = (_a = cache[hash]) == null ? void 0 : _a.resource;
830
892
  if (!pipeline) {
893
+ const sharedRenderPipeline = this.device.type === "webgl" && this.device.props._sharePipelines ? this.createSharedRenderPipeline(allProps) : void 0;
831
894
  pipeline = this.device.createRenderPipeline({
832
895
  ...allProps,
833
- id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached")
896
+ id: allProps.id ? `${allProps.id}-cached` : uid("unnamed-cached"),
897
+ _sharedRenderPipeline: sharedRenderPipeline
834
898
  });
835
899
  pipeline.hash = hash;
836
- cache[hash] = { pipeline, useCount: 1 };
837
- if (this.debug) {
900
+ cache[hash] = { resource: pipeline, useCount: 1 };
901
+ if (this.device.props.debugFactories) {
838
902
  import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
839
903
  }
840
904
  } else {
841
905
  cache[hash].useCount++;
842
- if (this.debug) {
843
- import_core4.log.log(3, `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
906
+ if (this.device.props.debugFactories) {
907
+ import_core4.log.log(3, `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
844
908
  }
845
909
  }
846
910
  return pipeline;
@@ -848,33 +912,33 @@ var _PipelineFactory = class {
848
912
  /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
849
913
  createComputePipeline(props) {
850
914
  var _a;
851
- if (!this.cachingEnabled) {
915
+ if (!this.device.props._cachePipelines) {
852
916
  return this.device.createComputePipeline(props);
853
917
  }
854
918
  const allProps = { ...import_core4.ComputePipeline.defaultProps, ...props };
855
919
  const cache = this._computePipelineCache;
856
920
  const hash = this._hashComputePipeline(allProps);
857
- let pipeline = (_a = cache[hash]) == null ? void 0 : _a.pipeline;
921
+ let pipeline = (_a = cache[hash]) == null ? void 0 : _a.resource;
858
922
  if (!pipeline) {
859
923
  pipeline = this.device.createComputePipeline({
860
924
  ...allProps,
861
925
  id: allProps.id ? `${allProps.id}-cached` : void 0
862
926
  });
863
927
  pipeline.hash = hash;
864
- cache[hash] = { pipeline, useCount: 1 };
865
- if (this.debug) {
928
+ cache[hash] = { resource: pipeline, useCount: 1 };
929
+ if (this.device.props.debugFactories) {
866
930
  import_core4.log.log(3, `${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
867
931
  }
868
932
  } else {
869
933
  cache[hash].useCount++;
870
- if (this.debug) {
871
- import_core4.log.log(3, `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
934
+ if (this.device.props.debugFactories) {
935
+ import_core4.log.log(3, `${this}: ${cache[hash].resource} reused, count=${cache[hash].useCount}, (id=${props.id})`)();
872
936
  }
873
937
  }
874
938
  return pipeline;
875
939
  }
876
940
  release(pipeline) {
877
- if (!this.cachingEnabled) {
941
+ if (!this.device.props._cachePipelines) {
878
942
  pipeline.destroy();
879
943
  return;
880
944
  }
@@ -883,28 +947,55 @@ var _PipelineFactory = class {
883
947
  cache[hash].useCount--;
884
948
  if (cache[hash].useCount === 0) {
885
949
  this._destroyPipeline(pipeline);
886
- if (this.debug) {
950
+ if (this.device.props.debugFactories) {
887
951
  import_core4.log.log(3, `${this}: ${pipeline} released and destroyed`)();
888
952
  }
889
953
  } else if (cache[hash].useCount < 0) {
890
954
  import_core4.log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
891
955
  cache[hash].useCount = 0;
892
- } else if (this.debug) {
956
+ } else if (this.device.props.debugFactories) {
893
957
  import_core4.log.log(3, `${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
894
958
  }
895
959
  }
960
+ createSharedRenderPipeline(props) {
961
+ const sharedPipelineHash = this._hashSharedRenderPipeline(props);
962
+ let sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
963
+ if (!sharedCacheItem) {
964
+ const sharedRenderPipeline = this.device._createSharedRenderPipelineWebGL(props);
965
+ sharedCacheItem = { resource: sharedRenderPipeline, useCount: 0 };
966
+ this._sharedRenderPipelineCache[sharedPipelineHash] = sharedCacheItem;
967
+ }
968
+ sharedCacheItem.useCount++;
969
+ return sharedCacheItem.resource;
970
+ }
971
+ releaseSharedRenderPipeline(pipeline) {
972
+ if (!pipeline.sharedRenderPipeline) {
973
+ return;
974
+ }
975
+ const sharedPipelineHash = this._hashSharedRenderPipeline(pipeline.sharedRenderPipeline.props);
976
+ const sharedCacheItem = this._sharedRenderPipelineCache[sharedPipelineHash];
977
+ if (!sharedCacheItem) {
978
+ return;
979
+ }
980
+ sharedCacheItem.useCount--;
981
+ if (sharedCacheItem.useCount === 0) {
982
+ sharedCacheItem.resource.destroy();
983
+ delete this._sharedRenderPipelineCache[sharedPipelineHash];
984
+ }
985
+ }
896
986
  // PRIVATE
897
- /** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */
987
+ /** Destroy a cached pipeline, removing it from the cache if configured to do so. */
898
988
  _destroyPipeline(pipeline) {
899
989
  const cache = this._getCache(pipeline);
900
- switch (this.destroyPolicy) {
901
- case "never":
902
- return false;
903
- case "unused":
904
- delete cache[pipeline.hash];
905
- pipeline.destroy();
906
- return true;
990
+ if (!this.device.props._destroyPipelines) {
991
+ return false;
907
992
  }
993
+ delete cache[pipeline.hash];
994
+ pipeline.destroy();
995
+ if (pipeline instanceof import_core4.RenderPipeline) {
996
+ this.releaseSharedRenderPipeline(pipeline);
997
+ }
998
+ return true;
908
999
  }
909
1000
  /** Get the appropriate cache for the type of pipeline */
910
1001
  _getCache(pipeline) {
@@ -933,24 +1024,35 @@ var _PipelineFactory = class {
933
1024
  _hashRenderPipeline(props) {
934
1025
  const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
935
1026
  const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
936
- const varyingHash = "-";
1027
+ const varyingHash = this._getWebGLVaryingHash(props);
937
1028
  const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
938
1029
  const { type } = this.device;
939
1030
  switch (type) {
940
1031
  case "webgl":
941
- return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
1032
+ const webglParameterHash = this._getHash(JSON.stringify(props.parameters));
1033
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${webglParameterHash}BL${bufferLayoutHash}`;
942
1034
  case "webgpu":
943
1035
  default:
944
1036
  const parameterHash = this._getHash(JSON.stringify(props.parameters));
945
1037
  return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
946
1038
  }
947
1039
  }
1040
+ _hashSharedRenderPipeline(props) {
1041
+ const vsHash = props.vs ? this._getHash(props.vs.source) : 0;
1042
+ const fsHash = props.fs ? this._getHash(props.fs.source) : 0;
1043
+ const varyingHash = this._getWebGLVaryingHash(props);
1044
+ return `webgl/S/${vsHash}/${fsHash}V${varyingHash}`;
1045
+ }
948
1046
  _getHash(key) {
949
1047
  if (this._hashes[key] === void 0) {
950
1048
  this._hashes[key] = this._hashCounter++;
951
1049
  }
952
1050
  return this._hashes[key];
953
1051
  }
1052
+ _getWebGLVaryingHash(props) {
1053
+ const { varyings = [], bufferMode = null } = props;
1054
+ return this._getHash(JSON.stringify({ varyings, bufferMode }));
1055
+ }
954
1056
  };
955
1057
  var PipelineFactory = _PipelineFactory;
956
1058
  __publicField(PipelineFactory, "defaultProps", { ...import_core4.RenderPipeline.defaultProps });
@@ -965,9 +1067,6 @@ var _ShaderFactory = class {
965
1067
  return moduleData.defaultShaderFactory;
966
1068
  }
967
1069
  device;
968
- cachingEnabled;
969
- destroyPolicy;
970
- debug;
971
1070
  _cache = {};
972
1071
  get [Symbol.toStringTag]() {
973
1072
  return "ShaderFactory";
@@ -978,37 +1077,34 @@ var _ShaderFactory = class {
978
1077
  /** @internal */
979
1078
  constructor(device) {
980
1079
  this.device = device;
981
- this.cachingEnabled = device.props._cacheShaders;
982
- this.destroyPolicy = device.props._cacheDestroyPolicy;
983
- this.debug = true;
984
1080
  }
985
1081
  /** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */
986
1082
  createShader(props) {
987
- if (!this.cachingEnabled) {
1083
+ if (!this.device.props._cacheShaders) {
988
1084
  return this.device.createShader(props);
989
1085
  }
990
1086
  const key = this._hashShader(props);
991
1087
  let cacheEntry = this._cache[key];
992
1088
  if (!cacheEntry) {
993
- const shader = this.device.createShader({
1089
+ const resource = this.device.createShader({
994
1090
  ...props,
995
1091
  id: props.id ? `${props.id}-cached` : void 0
996
1092
  });
997
- this._cache[key] = cacheEntry = { shader, useCount: 1 };
998
- if (this.debug) {
999
- import_core5.log.log(3, `${this}: Created new shader ${shader.id}`)();
1093
+ this._cache[key] = cacheEntry = { resource, useCount: 1 };
1094
+ if (this.device.props.debugFactories) {
1095
+ import_core5.log.log(3, `${this}: Created new shader ${resource.id}`)();
1000
1096
  }
1001
1097
  } else {
1002
1098
  cacheEntry.useCount++;
1003
- if (this.debug) {
1004
- import_core5.log.log(3, `${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`)();
1099
+ if (this.device.props.debugFactories) {
1100
+ import_core5.log.log(3, `${this}: Reusing shader ${cacheEntry.resource.id} count=${cacheEntry.useCount}`)();
1005
1101
  }
1006
1102
  }
1007
- return cacheEntry.shader;
1103
+ return cacheEntry.resource;
1008
1104
  }
1009
1105
  /** Releases a previously-requested {@link Shader}, destroying it if no users remain. */
1010
1106
  release(shader) {
1011
- if (!this.cachingEnabled) {
1107
+ if (!this.device.props._cacheShaders) {
1012
1108
  shader.destroy();
1013
1109
  return;
1014
1110
  }
@@ -1017,16 +1113,16 @@ var _ShaderFactory = class {
1017
1113
  if (cacheEntry) {
1018
1114
  cacheEntry.useCount--;
1019
1115
  if (cacheEntry.useCount === 0) {
1020
- if (this.destroyPolicy === "unused") {
1116
+ if (this.device.props._destroyShaders) {
1021
1117
  delete this._cache[key];
1022
- cacheEntry.shader.destroy();
1023
- if (this.debug) {
1118
+ cacheEntry.resource.destroy();
1119
+ if (this.device.props.debugFactories) {
1024
1120
  import_core5.log.log(3, `${this}: Releasing shader ${shader.id}, destroyed`)();
1025
1121
  }
1026
1122
  }
1027
1123
  } else if (cacheEntry.useCount < 0) {
1028
1124
  throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`);
1029
- } else if (this.debug) {
1125
+ } else if (this.device.props.debugFactories) {
1030
1126
  import_core5.log.log(3, `${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)();
1031
1127
  }
1032
1128
  }
@@ -1175,14 +1271,24 @@ var BufferLayoutHelper = class {
1175
1271
  };
1176
1272
 
1177
1273
  // dist/utils/buffer-layout-order.js
1274
+ function getMinLocation(attributeNames, shaderLayoutMap) {
1275
+ let minLocation = Infinity;
1276
+ for (const name of attributeNames) {
1277
+ const location = shaderLayoutMap[name];
1278
+ if (location !== void 0) {
1279
+ minLocation = Math.min(minLocation, location);
1280
+ }
1281
+ }
1282
+ return minLocation;
1283
+ }
1178
1284
  function sortedBufferLayoutByShaderSourceLocations(shaderLayout, bufferLayout) {
1179
1285
  const shaderLayoutMap = Object.fromEntries(shaderLayout.attributes.map((attr) => [attr.name, attr.location]));
1180
1286
  const sortedLayout = bufferLayout.slice();
1181
1287
  sortedLayout.sort((a, b) => {
1182
1288
  const attributeNamesA = a.attributes ? a.attributes.map((attr) => attr.attribute) : [a.name];
1183
1289
  const attributeNamesB = b.attributes ? b.attributes.map((attr) => attr.attribute) : [b.name];
1184
- const minLocationA = Math.min(...attributeNamesA.map((name) => shaderLayoutMap[name]));
1185
- const minLocationB = Math.min(...attributeNamesB.map((name) => shaderLayoutMap[name]));
1290
+ const minLocationA = getMinLocation(attributeNamesA, shaderLayoutMap);
1291
+ const minLocationB = getMinLocation(attributeNamesB, shaderLayoutMap);
1186
1292
  return minLocationA - minLocationB;
1187
1293
  });
1188
1294
  return sortedLayout;
@@ -1384,6 +1490,13 @@ function getTextureMipLevelSize(data) {
1384
1490
  function isTextureImageData(data) {
1385
1491
  return typeof data === "object" && data !== null && "data" in data && "width" in data && "height" in data;
1386
1492
  }
1493
+ function resolveTextureImageFormat(data) {
1494
+ const { textureFormat, format } = data;
1495
+ if (textureFormat && format && textureFormat !== format) {
1496
+ throw new Error(`Conflicting texture formats "${textureFormat}" and "${format}" provided for the same mip level`);
1497
+ }
1498
+ return textureFormat ?? format;
1499
+ }
1387
1500
  function getCubeFaceIndex(face) {
1388
1501
  const idx = TEXTURE_CUBE_FACE_MAP[face];
1389
1502
  if (idx === void 0)
@@ -1416,6 +1529,7 @@ function getTexture2DSubresources(slice, lodData) {
1416
1529
  subresources.push({
1417
1530
  type: "texture-data",
1418
1531
  data: imageData,
1532
+ textureFormat: resolveTextureImageFormat(imageData),
1419
1533
  z,
1420
1534
  mipLevel
1421
1535
  });
@@ -1452,7 +1566,7 @@ function getTextureCubeArraySubresources(data) {
1452
1566
  data.forEach((cubeData, cubeIndex) => {
1453
1567
  for (const [face, faceData] of Object.entries(cubeData)) {
1454
1568
  const faceDepth = getCubeArrayFaceIndex(cubeIndex, face);
1455
- getTexture2DSubresources(faceDepth, faceData);
1569
+ subresources.push(...getTexture2DSubresources(faceDepth, faceData));
1456
1570
  }
1457
1571
  });
1458
1572
  return subresources;
@@ -1514,6 +1628,9 @@ var _DynamicTexture = class {
1514
1628
  try {
1515
1629
  const propsWithSyncData = await this._loadAllData(originalPropsWithAsyncData);
1516
1630
  this._checkNotDestroyed();
1631
+ const subresources = propsWithSyncData.data ? getTextureSubresources(propsWithSyncData) : [];
1632
+ const userProvidedFormat = "format" in originalPropsWithAsyncData && originalPropsWithAsyncData.format !== void 0;
1633
+ const userProvidedUsage = "usage" in originalPropsWithAsyncData && originalPropsWithAsyncData.usage !== void 0;
1517
1634
  const deduceSize = () => {
1518
1635
  if (this.props.width && this.props.height) {
1519
1636
  return { width: this.props.width, height: this.props.height };
@@ -1528,45 +1645,39 @@ var _DynamicTexture = class {
1528
1645
  if (!size || size.width <= 0 || size.height <= 0) {
1529
1646
  throw new Error(`${this} size could not be determined or was zero`);
1530
1647
  }
1648
+ const textureData = analyzeTextureSubresources(this.device, subresources, size, {
1649
+ format: userProvidedFormat ? originalPropsWithAsyncData.format : void 0
1650
+ });
1651
+ const resolvedFormat = textureData.format ?? this.props.format;
1531
1652
  const baseTextureProps = {
1532
1653
  ...this.props,
1533
1654
  ...size,
1655
+ format: resolvedFormat,
1534
1656
  mipLevels: 1,
1535
1657
  // temporary; updated below
1536
1658
  data: void 0
1537
1659
  };
1660
+ if (this.device.isTextureFormatCompressed(resolvedFormat) && !userProvidedUsage) {
1661
+ baseTextureProps.usage = import_core9.Texture.SAMPLE | import_core9.Texture.COPY_DST;
1662
+ }
1663
+ const shouldGenerateMipmaps = this.props.mipmaps && !textureData.hasExplicitMipChain && !this.device.isTextureFormatCompressed(resolvedFormat);
1664
+ if (this.device.type === "webgpu" && shouldGenerateMipmaps) {
1665
+ 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;
1666
+ baseTextureProps.usage |= requiredUsage;
1667
+ }
1538
1668
  const maxMips = this.device.getMipLevelCount(baseTextureProps.width, baseTextureProps.height);
1539
- const desired = this.props.mipLevels === "auto" ? maxMips : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
1669
+ const desired = textureData.hasExplicitMipChain ? textureData.mipLevels : this.props.mipLevels === "auto" ? maxMips : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
1540
1670
  const finalTextureProps = { ...baseTextureProps, mipLevels: desired };
1541
1671
  this._texture = this.device.createTexture(finalTextureProps);
1542
1672
  this._sampler = this.texture.sampler;
1543
1673
  this._view = this.texture.view;
1544
- if (propsWithSyncData.data) {
1545
- switch (propsWithSyncData.dimension) {
1546
- case "1d":
1547
- this.setTexture1DData(propsWithSyncData.data);
1548
- break;
1549
- case "2d":
1550
- this.setTexture2DData(propsWithSyncData.data);
1551
- break;
1552
- case "3d":
1553
- this.setTexture3DData(propsWithSyncData.data);
1554
- break;
1555
- case "2d-array":
1556
- this.setTextureArrayData(propsWithSyncData.data);
1557
- break;
1558
- case "cube":
1559
- this.setTextureCubeData(propsWithSyncData.data);
1560
- break;
1561
- case "cube-array":
1562
- this.setTextureCubeArrayData(propsWithSyncData.data);
1563
- break;
1564
- default: {
1565
- throw new Error(`Unhandled dimension ${propsWithSyncData.dimension}`);
1566
- }
1567
- }
1674
+ if (textureData.subresources.length) {
1675
+ this._setTextureSubresources(textureData.subresources);
1676
+ }
1677
+ if (this.props.mipmaps && !textureData.hasExplicitMipChain && !shouldGenerateMipmaps) {
1678
+ import_core9.log.warn(`${this} skipping auto-generated mipmaps for compressed texture format`)();
1568
1679
  }
1569
- if (this.props.mipmaps) {
1680
+ if (shouldGenerateMipmaps) {
1570
1681
  this.generateMipmaps();
1571
1682
  }
1572
1683
  this.isReady = true;
@@ -1590,8 +1701,10 @@ var _DynamicTexture = class {
1590
1701
  generateMipmaps() {
1591
1702
  if (this.device.type === "webgl") {
1592
1703
  this.texture.generateMipmapsWebGL();
1704
+ } else if (this.device.type === "webgpu") {
1705
+ this.device.generateMipmapsWebGPU(this.texture);
1593
1706
  } else {
1594
- import_core9.log.warn("Mipmap generation not yet implemented on WebGPU: your texture data will not be correctly initialized");
1707
+ import_core9.log.warn(`${this} mipmaps not supported on ${this.device.type}`);
1595
1708
  }
1596
1709
  }
1597
1710
  /** Set sampler or create one from props */
@@ -1689,8 +1802,19 @@ var _DynamicTexture = class {
1689
1802
  this.texture.copyExternalImage({ image, z, mipLevel, flipY });
1690
1803
  break;
1691
1804
  case "texture-data":
1692
- const { data } = subresource;
1693
- this.texture.copyImageData({ data: data.data, z, mipLevel });
1805
+ const { data, textureFormat } = subresource;
1806
+ if (textureFormat && textureFormat !== this.texture.format) {
1807
+ throw new Error(`${this} mip level ${mipLevel} uses format "${textureFormat}" but texture format is "${this.texture.format}"`);
1808
+ }
1809
+ this.texture.writeData(data.data, {
1810
+ x: 0,
1811
+ y: 0,
1812
+ z,
1813
+ width: data.width,
1814
+ height: data.height,
1815
+ depthOrArrayLayers: 1,
1816
+ mipLevel
1817
+ });
1694
1818
  break;
1695
1819
  default:
1696
1820
  throw new Error("Unsupported 2D mip-level payload");
@@ -1722,6 +1846,134 @@ __publicField(DynamicTexture, "defaultProps", {
1722
1846
  data: null,
1723
1847
  mipmaps: false
1724
1848
  });
1849
+ function getTextureSubresources(props) {
1850
+ if (!props.data) {
1851
+ return [];
1852
+ }
1853
+ switch (props.dimension) {
1854
+ case "1d":
1855
+ return getTexture1DSubresources(props.data);
1856
+ case "2d":
1857
+ return getTexture2DSubresources(0, props.data);
1858
+ case "3d":
1859
+ return getTexture3DSubresources(props.data);
1860
+ case "2d-array":
1861
+ return getTextureArraySubresources(props.data);
1862
+ case "cube":
1863
+ return getTextureCubeSubresources(props.data);
1864
+ case "cube-array":
1865
+ return getTextureCubeArraySubresources(props.data);
1866
+ default:
1867
+ throw new Error(`Unhandled dimension ${props.dimension}`);
1868
+ }
1869
+ }
1870
+ function analyzeTextureSubresources(device, subresources, size, options) {
1871
+ if (subresources.length === 0) {
1872
+ return {
1873
+ subresources,
1874
+ mipLevels: 1,
1875
+ format: options.format,
1876
+ hasExplicitMipChain: false
1877
+ };
1878
+ }
1879
+ const groupedSubresources = /* @__PURE__ */ new Map();
1880
+ for (const subresource of subresources) {
1881
+ const group = groupedSubresources.get(subresource.z) ?? [];
1882
+ group.push(subresource);
1883
+ groupedSubresources.set(subresource.z, group);
1884
+ }
1885
+ const hasExplicitMipChain = subresources.some((subresource) => subresource.mipLevel > 0);
1886
+ let resolvedFormat = options.format;
1887
+ let resolvedMipLevels = Number.POSITIVE_INFINITY;
1888
+ const validSubresources = [];
1889
+ for (const [z, sliceSubresources] of groupedSubresources) {
1890
+ const sortedSubresources = [...sliceSubresources].sort((left, right) => left.mipLevel - right.mipLevel);
1891
+ const baseLevel = sortedSubresources[0];
1892
+ if (!baseLevel || baseLevel.mipLevel !== 0) {
1893
+ throw new Error(`DynamicTexture: slice ${z} is missing mip level 0`);
1894
+ }
1895
+ const baseSize = getTextureSubresourceSize(device, baseLevel);
1896
+ if (baseSize.width !== size.width || baseSize.height !== size.height) {
1897
+ throw new Error(`DynamicTexture: slice ${z} base level dimensions ${baseSize.width}x${baseSize.height} do not match expected ${size.width}x${size.height}`);
1898
+ }
1899
+ const baseFormat = getTextureSubresourceFormat(baseLevel);
1900
+ if (baseFormat) {
1901
+ if (resolvedFormat && resolvedFormat !== baseFormat) {
1902
+ throw new Error(`DynamicTexture: slice ${z} base level format "${baseFormat}" does not match texture format "${resolvedFormat}"`);
1903
+ }
1904
+ resolvedFormat = baseFormat;
1905
+ }
1906
+ const mipLevelLimit = resolvedFormat && device.isTextureFormatCompressed(resolvedFormat) ? (
1907
+ // Block-compressed formats cannot have mips smaller than a single compression block.
1908
+ getMaxCompressedMipLevels(device, baseSize.width, baseSize.height, resolvedFormat)
1909
+ ) : device.getMipLevelCount(baseSize.width, baseSize.height);
1910
+ let validMipLevelsForSlice = 0;
1911
+ for (let expectedMipLevel = 0; expectedMipLevel < sortedSubresources.length; expectedMipLevel++) {
1912
+ const subresource = sortedSubresources[expectedMipLevel];
1913
+ if (!subresource || subresource.mipLevel !== expectedMipLevel) {
1914
+ break;
1915
+ }
1916
+ if (expectedMipLevel >= mipLevelLimit) {
1917
+ break;
1918
+ }
1919
+ const subresourceSize = getTextureSubresourceSize(device, subresource);
1920
+ const expectedWidth = Math.max(1, baseSize.width >> expectedMipLevel);
1921
+ const expectedHeight = Math.max(1, baseSize.height >> expectedMipLevel);
1922
+ if (subresourceSize.width !== expectedWidth || subresourceSize.height !== expectedHeight) {
1923
+ break;
1924
+ }
1925
+ const subresourceFormat = getTextureSubresourceFormat(subresource);
1926
+ if (subresourceFormat) {
1927
+ if (!resolvedFormat) {
1928
+ resolvedFormat = subresourceFormat;
1929
+ }
1930
+ if (subresourceFormat !== resolvedFormat) {
1931
+ break;
1932
+ }
1933
+ }
1934
+ validMipLevelsForSlice++;
1935
+ validSubresources.push(subresource);
1936
+ }
1937
+ resolvedMipLevels = Math.min(resolvedMipLevels, validMipLevelsForSlice);
1938
+ }
1939
+ const mipLevels = Number.isFinite(resolvedMipLevels) ? Math.max(1, resolvedMipLevels) : 1;
1940
+ return {
1941
+ // Keep every slice trimmed to the same mip count so the texture shape stays internally consistent.
1942
+ subresources: validSubresources.filter((subresource) => subresource.mipLevel < mipLevels),
1943
+ mipLevels,
1944
+ format: resolvedFormat,
1945
+ hasExplicitMipChain
1946
+ };
1947
+ }
1948
+ function getTextureSubresourceFormat(subresource) {
1949
+ if (subresource.type !== "texture-data") {
1950
+ return void 0;
1951
+ }
1952
+ return subresource.textureFormat ?? resolveTextureImageFormat(subresource.data);
1953
+ }
1954
+ function getTextureSubresourceSize(device, subresource) {
1955
+ switch (subresource.type) {
1956
+ case "external-image":
1957
+ return device.getExternalImageSize(subresource.image);
1958
+ case "texture-data":
1959
+ return { width: subresource.data.width, height: subresource.data.height };
1960
+ default:
1961
+ throw new Error("Unsupported texture subresource");
1962
+ }
1963
+ }
1964
+ function getMaxCompressedMipLevels(device, baseWidth, baseHeight, format) {
1965
+ const { blockWidth = 1, blockHeight = 1 } = device.getTextureFormatInfo(format);
1966
+ let mipLevels = 1;
1967
+ for (let mipLevel = 1; ; mipLevel++) {
1968
+ const width = Math.max(1, baseWidth >> mipLevel);
1969
+ const height = Math.max(1, baseHeight >> mipLevel);
1970
+ if (width < blockWidth || height < blockHeight) {
1971
+ break;
1972
+ }
1973
+ mipLevels++;
1974
+ }
1975
+ return mipLevels;
1976
+ }
1725
1977
  async function awaitAllPromises(x) {
1726
1978
  x = await x;
1727
1979
  if (Array.isArray(x)) {
@@ -1897,7 +2149,7 @@ var _Model = class {
1897
2149
  if (!this._destroyed) {
1898
2150
  this.pipelineFactory.release(this.pipeline);
1899
2151
  this.shaderFactory.release(this.pipeline.vs);
1900
- if (this.pipeline.fs) {
2152
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
1901
2153
  this.shaderFactory.release(this.pipeline.fs);
1902
2154
  }
1903
2155
  this._uniformStore.destroy();
@@ -1947,9 +2199,6 @@ var _Model = class {
1947
2199
  this._logDrawCallStart();
1948
2200
  this.pipeline = this._updatePipeline();
1949
2201
  const syncBindings = this._getBindings();
1950
- this.pipeline.setBindings(syncBindings, {
1951
- disableWarnings: this.props.disableWarnings
1952
- });
1953
2202
  const { indexBuffer } = this.vertexArray;
1954
2203
  const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === "uint32" ? 4 : 2) : void 0;
1955
2204
  drawSuccess = this.pipeline.draw({
@@ -1960,6 +2209,11 @@ var _Model = class {
1960
2209
  instanceCount: this.instanceCount,
1961
2210
  indexCount,
1962
2211
  transformFeedback: this.transformFeedback || void 0,
2212
+ // Pipelines may be shared across models when caching is enabled, so bindings
2213
+ // and WebGL uniforms must be supplied on every draw instead of being stored
2214
+ // on the pipeline instance.
2215
+ bindings: syncBindings,
2216
+ uniforms: this.props.uniforms,
1963
2217
  // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
1964
2218
  // so we must provide our unique parameters to each draw
1965
2219
  // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
@@ -2259,8 +2513,9 @@ var _Model = class {
2259
2513
  this._attributeInfos = (0, import_core10.getAttributeInfosFromLayouts)(this.pipeline.shaderLayout, this.bufferLayout);
2260
2514
  if (prevShaderVs)
2261
2515
  this.shaderFactory.release(prevShaderVs);
2262
- if (prevShaderFs)
2516
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
2263
2517
  this.shaderFactory.release(prevShaderFs);
2518
+ }
2264
2519
  }
2265
2520
  return this.pipeline;
2266
2521
  }
@@ -2344,6 +2599,8 @@ __publicField(Model, "defaultProps", {
2344
2599
  indexBuffer: null,
2345
2600
  attributes: {},
2346
2601
  constantAttributes: {},
2602
+ bindings: {},
2603
+ uniforms: {},
2347
2604
  varyings: [],
2348
2605
  isInstanced: void 0,
2349
2606
  instanceCount: 0,
@@ -2648,9 +2905,9 @@ var Geometry = class {
2648
2905
  var CLIPSPACE_VERTEX_SHADER_WGSL = (
2649
2906
  /* wgsl */
2650
2907
  `struct VertexInputs {
2651
- @location(0) clipSpacePosition: vec2<f32>,
2652
- @location(1) texCoord: vec2<f32>,
2653
- @location(2) coordinate: vec2<f32>
2908
+ @location(0) clipSpacePositions: vec2<f32>,
2909
+ @location(1) texCoords: vec2<f32>,
2910
+ @location(2) coordinates: vec2<f32>
2654
2911
  }
2655
2912
 
2656
2913
  struct FragmentInputs {
@@ -2663,10 +2920,10 @@ struct FragmentInputs {
2663
2920
  @vertex
2664
2921
  fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
2665
2922
  var outputs: FragmentInputs;
2666
- outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.);
2667
- outputs.position = inputs.clipSpacePosition;
2668
- outputs.coordinate = inputs.coordinate;
2669
- outputs.uv = inputs.texCoord;
2923
+ outputs.Position = vec4(inputs.clipSpacePositions, 0., 1.);
2924
+ outputs.position = inputs.clipSpacePositions;
2925
+ outputs.coordinate = inputs.coordinates;
2926
+ outputs.uv = inputs.texCoords;
2670
2927
  return outputs;
2671
2928
  }
2672
2929
  `
@@ -2732,15 +2989,15 @@ struct backgroundUniforms {
2732
2989
  };
2733
2990
  @group(0) @binding(2) var<uniform> background: backgroundUniforms;
2734
2991
 
2735
- fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> {
2992
+ fn billboardTexture_getTextureUV(uv: vec2<f32>) -> vec2<f32> {
2736
2993
  let scale: vec2<f32> = background.scale;
2737
- var position: vec2<f32> = (coordinates - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
2994
+ var position: vec2<f32> = (uv - vec2<f32>(0.5, 0.5)) / scale + vec2<f32>(0.5, 0.5);
2738
2995
  return position;
2739
2996
  }
2740
2997
 
2741
2998
  @fragment
2742
2999
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
2743
- let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate);
3000
+ let position: vec2<f32> = billboardTexture_getTextureUV(inputs.uv);
2744
3001
  return textureSample(backgroundTexture, backgroundTextureSampler, position);
2745
3002
  }
2746
3003
  `
@@ -2774,20 +3031,22 @@ var BackgroundTextureModel = class extends ClipSpace {
2774
3031
  backgroundTexture = null;
2775
3032
  constructor(device, props) {
2776
3033
  super(device, {
3034
+ ...props,
2777
3035
  id: props.id || "background-texture-model",
2778
3036
  source: BACKGROUND_FS_WGSL,
2779
3037
  fs: BACKGROUND_FS,
2780
- modules: [backgroundModule],
3038
+ modules: [...props.modules || [], backgroundModule],
2781
3039
  parameters: {
2782
3040
  depthWriteEnabled: false,
3041
+ ...props.parameters || {},
2783
3042
  ...props.blend ? {
2784
3043
  blend: true,
2785
3044
  blendColorOperation: "add",
2786
3045
  blendAlphaOperation: "add",
2787
- blendColorSrcFactor: "one",
2788
- blendColorDstFactor: "one-minus-src",
2789
- blendAlphaSrcFactor: "one",
2790
- blendAlphaDstFactor: "one-minus-src-alpha"
3046
+ blendColorSrcFactor: "one-minus-dst-alpha",
3047
+ blendColorDstFactor: "one",
3048
+ blendAlphaSrcFactor: "one-minus-dst-alpha",
3049
+ blendAlphaDstFactor: "one"
2791
3050
  } : {}
2792
3051
  }
2793
3052
  });
@@ -2839,6 +3098,11 @@ var BackgroundTextureModel = class extends ClipSpace {
2839
3098
 
2840
3099
  // dist/scenegraph/scenegraph-node.js
2841
3100
  var import_core12 = require("@math.gl/core");
3101
+ function assert(condition, message) {
3102
+ if (!condition) {
3103
+ throw new Error(message);
3104
+ }
3105
+ }
2842
3106
  var ScenegraphNode = class {
2843
3107
  id;
2844
3108
  matrix = new import_core12.Matrix4();
@@ -2870,14 +3134,17 @@ var ScenegraphNode = class {
2870
3134
  return `{type: ScenegraphNode, id: ${this.id})}`;
2871
3135
  }
2872
3136
  setPosition(position) {
3137
+ assert(position.length === 3, "setPosition requires vector argument");
2873
3138
  this.position = position;
2874
3139
  return this;
2875
3140
  }
2876
3141
  setRotation(rotation) {
3142
+ assert(rotation.length === 3 || rotation.length === 4, "setRotation requires vector argument");
2877
3143
  this.rotation = rotation;
2878
3144
  return this;
2879
3145
  }
2880
3146
  setScale(scale) {
3147
+ assert(scale.length === 3, "setScale requires vector argument");
2881
3148
  this.scale = scale;
2882
3149
  return this;
2883
3150
  }
@@ -2905,17 +3172,18 @@ var ScenegraphNode = class {
2905
3172
  return this;
2906
3173
  }
2907
3174
  updateMatrix() {
2908
- const pos = this.position;
2909
- const rot = this.rotation;
2910
- const scale = this.scale;
2911
3175
  this.matrix.identity();
2912
- this.matrix.translate(pos);
2913
- this.matrix.rotateXYZ(rot);
2914
- this.matrix.scale(scale);
3176
+ this.matrix.translate(this.position);
3177
+ if (this.rotation.length === 4) {
3178
+ const rotationMatrix = new import_core12.Matrix4().fromQuaternion(this.rotation);
3179
+ this.matrix.multiplyRight(rotationMatrix);
3180
+ } else {
3181
+ this.matrix.rotateXYZ(this.rotation);
3182
+ }
3183
+ this.matrix.scale(this.scale);
2915
3184
  return this;
2916
3185
  }
2917
- update(options = {}) {
2918
- const { position, rotation, scale } = options;
3186
+ update({ position, rotation, scale } = {}) {
2919
3187
  if (position) {
2920
3188
  this.setPosition(position);
2921
3189
  }
@@ -2965,16 +3233,17 @@ var ScenegraphNode = class {
2965
3233
  }
2966
3234
  */
2967
3235
  _setScenegraphNodeProps(props) {
2968
- if ("position" in props) {
3236
+ if (props == null ? void 0 : props.position) {
2969
3237
  this.setPosition(props.position);
2970
3238
  }
2971
- if ("rotation" in props) {
3239
+ if (props == null ? void 0 : props.rotation) {
2972
3240
  this.setRotation(props.rotation);
2973
3241
  }
2974
- if ("scale" in props) {
3242
+ if (props == null ? void 0 : props.scale) {
2975
3243
  this.setScale(props.scale);
2976
3244
  }
2977
- if ("matrix" in props) {
3245
+ this.updateMatrix();
3246
+ if (props == null ? void 0 : props.matrix) {
2978
3247
  this.setMatrix(props.matrix);
2979
3248
  }
2980
3249
  Object.assign(this.props, props);
@@ -3059,6 +3328,17 @@ var GroupNode = class extends ScenegraphNode {
3059
3328
  }
3060
3329
  }
3061
3330
  }
3331
+ preorderTraversal(visitor, { worldMatrix = new import_core13.Matrix4() } = {}) {
3332
+ const modelMatrix = new import_core13.Matrix4(worldMatrix).multiplyRight(this.matrix);
3333
+ visitor(this, { worldMatrix: modelMatrix });
3334
+ for (const child of this.children) {
3335
+ if (child instanceof GroupNode) {
3336
+ child.preorderTraversal(visitor, { worldMatrix: modelMatrix });
3337
+ } else {
3338
+ visitor(child, { worldMatrix: modelMatrix });
3339
+ }
3340
+ }
3341
+ }
3062
3342
  };
3063
3343
 
3064
3344
  // dist/scenegraph/model-node.js
@@ -4324,26 +4604,16 @@ function getFragmentShaderForRenderPass(options) {
4324
4604
  function getFilterShaderWGSL(func) {
4325
4605
  return (
4326
4606
  /* wgsl */
4327
- `// Binding 0:1 is reserved for shader passes
4328
- // @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
4329
- @group(0) @binding(1) var texture: texture_2d<f32>;
4330
- @group(0) @binding(2) var textureSampler: sampler;
4331
-
4332
- // This needs to be aligned with
4333
- // struct FragmentInputs {
4334
- // @location(0) fragUV: vec2f,
4335
- // @location(1) fragPosition: vec4f,
4336
- // @location(2) fragCoordinate: vec4f
4337
- // };
4607
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
4608
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
4338
4609
 
4339
4610
  @fragment
4340
4611
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4341
- let fragUV = inputs.uv;
4342
- let fragCoordinate = inputs.coordinate;
4343
- let texSize = vec2f(textureDimensions(texture, 0));
4612
+ let texCoord = inputs.coordinate;
4613
+ let texSize = vec2f(textureDimensions(sourceTexture));
4344
4614
 
4345
- var fragColor = textureSample(texture, textureSampler, fragUV);
4346
- fragColor = ${func}(fragColor, texSize, fragCoordinate);
4615
+ var fragColor = textureSample(sourceTexture, sourceTextureSampler, texCoord);
4616
+ fragColor = ${func}(fragColor, texSize, texCoord);
4347
4617
  return fragColor;
4348
4618
  }
4349
4619
  `
@@ -4352,23 +4622,14 @@ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4352
4622
  function getSamplerShaderWGSL(func) {
4353
4623
  return (
4354
4624
  /* wgsl */
4355
- `// Binding 0:1 is reserved for shader passes
4356
- @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
4357
- @group(0) @binding(1) var texture: texture_2d<f32>;
4358
- @group(0) @binding(2) var sampler: sampler;
4359
-
4360
- struct FragmentInputs = {
4361
- @location(0) fragUV: vec2f,
4362
- @location(1) fragPosition: vec4f,
4363
- @location(2) fragCoordinate: vec4f
4364
- };
4625
+ `@group(0) @binding(0) var sourceTexture: texture_2d<f32>;
4626
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
4365
4627
 
4366
4628
  @fragment
4367
4629
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4368
- let texSize = vec2f(textureDimensions(texture, 0));
4369
- var fragColor = textureSample(texture, sampler, fragUV);
4370
- fragColor = ${func}(fragColor, texSize, texCoord);
4371
- return fragColor;
4630
+ let texCoord = inputs.coordinate;
4631
+ let texSize = vec2f(textureDimensions(sourceTexture));
4632
+ return ${func}(sourceTexture, sourceTextureSampler, texSize, texCoord);
4372
4633
  }
4373
4634
  `
4374
4635
  );
@@ -4427,8 +4688,6 @@ var ShaderPassRenderer = class {
4427
4688
  shaderInputs;
4428
4689
  passRenderers;
4429
4690
  swapFramebuffers;
4430
- /** For rendering to the screen */
4431
- clipSpace;
4432
4691
  textureModel;
4433
4692
  constructor(device, props) {
4434
4693
  this.device = device;
@@ -4444,33 +4703,6 @@ var ShaderPassRenderer = class {
4444
4703
  this.textureModel = new BackgroundTextureModel(device, {
4445
4704
  backgroundTexture: this.swapFramebuffers.current.colorAttachments[0].texture
4446
4705
  });
4447
- this.clipSpace = new ClipSpace(device, {
4448
- source: (
4449
- /* wgsl */
4450
- ` @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
4451
- @group(0) @binding(1) var sourceTextureSampler: sampler;
4452
-
4453
- @fragment
4454
- fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> {
4455
- let texCoord: vec2<f32> = inputs.coordinate;
4456
- return textureSample(sourceTexture, sourceTextureSampler, texCoord);
4457
- }
4458
- `
4459
- ),
4460
- fs: (
4461
- /* glsl */
4462
- `#version 300 es
4463
-
4464
- uniform sampler2D sourceTexture;
4465
- in vec2 uv;
4466
- out vec4 fragColor;
4467
-
4468
- void main() {
4469
- fragColor = texture(sourceTexture, uv);
4470
- }
4471
- `
4472
- )
4473
- });
4474
4706
  this.passRenderers = props.shaderPasses.map((shaderPass) => new PassRenderer(device, shaderPass));
4475
4707
  }
4476
4708
  /** Destroys resources created by this ShaderPassRenderer */
@@ -4479,7 +4711,6 @@ void main() {
4479
4711
  subPassRenderer.destroy();
4480
4712
  }
4481
4713
  this.swapFramebuffers.destroy();
4482
- this.clipSpace.destroy();
4483
4714
  this.textureModel.destroy();
4484
4715
  }
4485
4716
  resize(size) {
@@ -4491,15 +4722,15 @@ void main() {
4491
4722
  if (!outputTexture) {
4492
4723
  return false;
4493
4724
  }
4494
- const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilAttachment: false });
4725
+ const framebuffer = this.device.getDefaultCanvasContext().getCurrentFramebuffer({ depthStencilFormat: false });
4495
4726
  const renderPass = this.device.beginRenderPass({
4496
4727
  id: "shader-pass-renderer-to-screen",
4497
4728
  framebuffer,
4498
4729
  // clearColor: [1, 1, 0, 1],
4499
- clearDepth: 1
4730
+ clearDepth: false
4500
4731
  });
4501
- this.clipSpace.setBindings({ sourceTexture: outputTexture });
4502
- this.clipSpace.draw(renderPass);
4732
+ this.textureModel.setProps({ backgroundTexture: outputTexture });
4733
+ this.textureModel.draw(renderPass);
4503
4734
  renderPass.end();
4504
4735
  return true;
4505
4736
  }
@@ -5011,12 +5242,6 @@ const INDEX_PICKING_MODE_INSTANCE = 0;
5011
5242
  const INDEX_PICKING_MODE_CUSTOM = 1;
5012
5243
  const INDEX_PICKING_INVALID_INDEX = ${INVALID_INDEX}; // 2^32 - 1
5013
5244
 
5014
- struct indexPickingFragmentInputs = {
5015
- objectIndex: int32;
5016
- };
5017
-
5018
- let indexPickingFragmentInputs: indexPickingFragmentInputs;
5019
-
5020
5245
  /**
5021
5246
  * Vertex shaders should call this function to set the object index.
5022
5247
  * If using instance or vertex mode, argument will be ignored, 0 can be supplied.