@zephyr3d/editor 0.3.7 → 0.3.8

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 (61) hide show
  1. package/dist/assets/{index-ySiLtSxz.js → index-BZ1L8ggE.js} +43 -3
  2. package/dist/assistant/zephyr-types-index.json +13230 -9488
  3. package/dist/images/icon1024.png +0 -0
  4. package/dist/index.html +1 -1
  5. package/dist/modules/zephyr3d_backend-webgl.js +2 -0
  6. package/dist/modules/zephyr3d_backend-webgl.js.map +1 -1
  7. package/dist/modules/zephyr3d_backend-webgpu.js +457 -16
  8. package/dist/modules/zephyr3d_backend-webgpu.js.map +1 -1
  9. package/dist/modules/zephyr3d_device.js +27 -0
  10. package/dist/modules/zephyr3d_device.js.map +1 -1
  11. package/dist/modules/zephyr3d_loaders.js +208 -1
  12. package/dist/modules/zephyr3d_loaders.js.map +1 -1
  13. package/dist/modules/zephyr3d_scene.js +774 -80
  14. package/dist/modules/zephyr3d_scene.js.map +1 -1
  15. package/dist/vendor/zephyr3d/backend-webgl/dist/capabilities_webgl.js +2 -0
  16. package/dist/vendor/zephyr3d/backend-webgl/dist/capabilities_webgl.js.map +1 -1
  17. package/dist/vendor/zephyr3d/backend-webgpu/dist/capabilities_webgpu.js +2 -0
  18. package/dist/vendor/zephyr3d/backend-webgpu/dist/capabilities_webgpu.js.map +1 -1
  19. package/dist/vendor/zephyr3d/backend-webgpu/dist/commandqueue.js +55 -10
  20. package/dist/vendor/zephyr3d/backend-webgpu/dist/commandqueue.js.map +1 -1
  21. package/dist/vendor/zephyr3d/backend-webgpu/dist/computepass_webgpu.js +4 -0
  22. package/dist/vendor/zephyr3d/backend-webgpu/dist/computepass_webgpu.js.map +1 -1
  23. package/dist/vendor/zephyr3d/backend-webgpu/dist/device.js +44 -1
  24. package/dist/vendor/zephyr3d/backend-webgpu/dist/device.js.map +1 -1
  25. package/dist/vendor/zephyr3d/backend-webgpu/dist/renderpass_webgpu.js +58 -5
  26. package/dist/vendor/zephyr3d/backend-webgpu/dist/renderpass_webgpu.js.map +1 -1
  27. package/dist/vendor/zephyr3d/backend-webgpu/dist/timestamp_query.js +297 -0
  28. package/dist/vendor/zephyr3d/backend-webgpu/dist/timestamp_query.js.map +1 -0
  29. package/dist/vendor/zephyr3d/device/dist/base_types.js.map +1 -1
  30. package/dist/vendor/zephyr3d/device/dist/device.js +27 -0
  31. package/dist/vendor/zephyr3d/device/dist/device.js.map +1 -1
  32. package/dist/vendor/zephyr3d/device/dist/index.d.ts +81 -1
  33. package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js +208 -1
  34. package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js.map +1 -1
  35. package/dist/vendor/zephyr3d/loaders/dist/index.d.ts +8 -0
  36. package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js +334 -63
  37. package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js.map +1 -1
  38. package/dist/vendor/zephyr3d/scene/dist/animation/morphtargetgrouptrack.js +66 -0
  39. package/dist/vendor/zephyr3d/scene/dist/animation/morphtargetgrouptrack.js.map +1 -0
  40. package/dist/vendor/zephyr3d/scene/dist/asset/model.js +9 -0
  41. package/dist/vendor/zephyr3d/scene/dist/asset/model.js.map +1 -1
  42. package/dist/vendor/zephyr3d/scene/dist/index.d.ts +260 -4
  43. package/dist/vendor/zephyr3d/scene/dist/index.js +7 -0
  44. package/dist/vendor/zephyr3d/scene/dist/index.js.map +1 -1
  45. package/dist/vendor/zephyr3d/scene/dist/posteffect/sao.js +1 -1
  46. package/dist/vendor/zephyr3d/scene/dist/posteffect/sao.js.map +1 -1
  47. package/dist/vendor/zephyr3d/scene/dist/render/rendergraph/executor.js +270 -1
  48. package/dist/vendor/zephyr3d/scene/dist/render/rendergraph/executor.js.map +1 -1
  49. package/dist/vendor/zephyr3d/scene/dist/render/rendergraph/forward_plus_builder.js +19 -1
  50. package/dist/vendor/zephyr3d/scene/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  51. package/dist/vendor/zephyr3d/scene/dist/render/rendergraph/types.js.map +1 -1
  52. package/dist/vendor/zephyr3d/scene/dist/scene/scene.js +1 -0
  53. package/dist/vendor/zephyr3d/scene/dist/scene/scene.js.map +1 -1
  54. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js +2 -1
  55. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js.map +1 -1
  56. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js +70 -1
  57. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js.map +1 -1
  58. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js.map +1 -1
  59. package/package.json +6 -6
  60. package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js +0 -320
  61. package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
@@ -2773,6 +2773,7 @@ class WebGPUMiscCaps {
2773
2773
  supportDepthClamp;
2774
2774
  maxBindGroups;
2775
2775
  maxTexCoordIndex;
2776
+ supportTimestampQuery;
2776
2777
  constructor(device){
2777
2778
  this.supportOversizedViewport = false;
2778
2779
  this.supportBlendMinMax = true;
@@ -2780,6 +2781,7 @@ class WebGPUMiscCaps {
2780
2781
  this.supportDepthClamp = device.device.features.has('depth-clip-control');
2781
2782
  this.maxBindGroups = 4;
2782
2783
  this.maxTexCoordIndex = 8;
2784
+ this.supportTimestampQuery = device.device.features.has('timestamp-query');
2783
2785
  }
2784
2786
  }
2785
2787
  class WebGPUShaderCaps {
@@ -4879,6 +4881,7 @@ class WebGPURenderPass {
4879
4881
  _currentViewport;
4880
4882
  _currentScissor;
4881
4883
  _frameBufferInfo;
4884
+ _deferredMipmapTextures;
4882
4885
  constructor(device){
4883
4886
  this._device = device;
4884
4887
  this._renderCommandEncoder = null;
@@ -4887,6 +4890,7 @@ class WebGPURenderPass {
4887
4890
  this._currentViewport = null;
4888
4891
  this._currentScissor = null;
4889
4892
  this._frameBufferInfo = this.createFrameBufferInfo(null);
4893
+ this._deferredMipmapTextures = [];
4890
4894
  }
4891
4895
  get active() {
4892
4896
  return !!this._renderPassEncoder;
@@ -5034,6 +5038,10 @@ class WebGPURenderPass {
5034
5038
  if (validation & VALIDATION_FAILED$1) {
5035
5039
  return;
5036
5040
  }
5041
+ if (this.hasDeferredMipmapsForBindGroups(bindGroups)) {
5042
+ this.end();
5043
+ this.flushDeferredMipmapsForBindGroups(bindGroups);
5044
+ }
5037
5045
  if (validation & VALIDATION_NEED_NEW_PASS) {
5038
5046
  this.end();
5039
5047
  }
@@ -5153,7 +5161,7 @@ class WebGPURenderPass {
5153
5161
  this.setViewport(this._currentViewport);
5154
5162
  this.setScissor(this._currentScissor);
5155
5163
  }
5156
- end() {
5164
+ end(generateMipmaps = true) {
5157
5165
  if (!this.active) {
5158
5166
  return;
5159
5167
  }
@@ -5168,15 +5176,38 @@ class WebGPURenderPass {
5168
5176
  const options = this._frameBufferInfo.frameBuffer.getOptions();
5169
5177
  if (options.colorAttachments) {
5170
5178
  for (const attachment of options.colorAttachments){
5171
- attachment.texture._markAsCurrentFB(false);
5172
- if (attachment.generateMipmaps && attachment.texture.mipLevelCount > 1) {
5173
- attachment.texture.generateMipmaps();
5174
- }
5179
+ const texture = attachment.texture;
5180
+ texture._markAsCurrentFB(false);
5181
+ this.handleColorAttachmentMipmap(texture, !!attachment.generateMipmaps, generateMipmaps);
5175
5182
  }
5176
5183
  }
5177
5184
  options.depthAttachment?.texture?._markAsCurrentFB(false);
5178
5185
  }
5179
5186
  }
5187
+ hasDeferredMipmapsForBindGroups(bindGroups) {
5188
+ return bindGroups?.some((bindGroup)=>{
5189
+ return bindGroup?.textureList.some((texture)=>this._deferredMipmapTextures.indexOf(texture) >= 0);
5190
+ });
5191
+ }
5192
+ flushDeferredMipmapsForBindGroups(bindGroups) {
5193
+ if (!bindGroups) {
5194
+ this.flushDeferredMipmaps();
5195
+ return;
5196
+ }
5197
+ for (const bindGroup of bindGroups){
5198
+ for (const texture of bindGroup?.textureList ?? []){
5199
+ this.flushDeferredMipmap(texture);
5200
+ }
5201
+ }
5202
+ }
5203
+ flushDeferredMipmaps() {
5204
+ const textures = this._deferredMipmapTextures.splice(0);
5205
+ for (const texture of textures){
5206
+ if (!texture.disposed) {
5207
+ texture.generateMipmaps();
5208
+ }
5209
+ }
5210
+ }
5180
5211
  capture(renderBundleEncoder, program, vertexData, stateSet, bindGroups, bindGroupOffsets, primitiveType, first, count, numInstances) {
5181
5212
  const validation = this.validateDraw(program, bindGroups, stateSet, primitiveType, first, count, numInstances);
5182
5213
  if (validation & VALIDATION_FAILED$1) {
@@ -5287,6 +5318,30 @@ class WebGPURenderPass {
5287
5318
  }
5288
5319
  return validation;
5289
5320
  }
5321
+ handleColorAttachmentMipmap(texture, attachmentGenerateMipmaps, generateMipmaps) {
5322
+ if (!attachmentGenerateMipmaps || texture.mipLevelCount <= 1) {
5323
+ return;
5324
+ }
5325
+ if (generateMipmaps) {
5326
+ this.removeDeferredMipmap(texture);
5327
+ texture.generateMipmaps();
5328
+ } else if (this._deferredMipmapTextures.indexOf(texture) < 0) {
5329
+ this._deferredMipmapTextures.push(texture);
5330
+ }
5331
+ }
5332
+ flushDeferredMipmap(texture) {
5333
+ if (this.removeDeferredMipmap(texture) && !texture.disposed) {
5334
+ texture.generateMipmaps();
5335
+ }
5336
+ }
5337
+ removeDeferredMipmap(texture) {
5338
+ const index = this._deferredMipmapTextures.indexOf(texture);
5339
+ if (index >= 0) {
5340
+ this._deferredMipmapTextures.splice(index, 1);
5341
+ return true;
5342
+ }
5343
+ return false;
5344
+ }
5290
5345
  setBindGroupsForRender(renderPassEncoder, program, bindGroups, bindGroupOffsets, renderBundleEncoder) {
5291
5346
  if (bindGroups) {
5292
5347
  for(let i = 0; i < 4; i++){
@@ -5331,6 +5386,10 @@ class WebGPUComputePass {
5331
5386
  if (validation & VALIDATION_FAILED) {
5332
5387
  return;
5333
5388
  }
5389
+ if (this._device.commandQueue.hasDeferredMipmapsForBindGroups(bindGroups)) {
5390
+ this.end();
5391
+ this._device.commandQueue.flushDeferredMipmapsForBindGroups(bindGroups);
5392
+ }
5334
5393
  if (!this.active) {
5335
5394
  this.begin();
5336
5395
  }
@@ -5437,10 +5496,7 @@ class CommandQueueImmediate {
5437
5496
  getEncoder() {
5438
5497
  const segment = this.getOrCreateCurrentSegment();
5439
5498
  segment.hasBodyCommands = true;
5440
- if (!segment.bodyEncoder) {
5441
- segment.bodyEncoder = this._device.device.createCommandEncoder();
5442
- }
5443
- return segment.bodyEncoder;
5499
+ return this.getOrCreateBodyEncoder(segment);
5444
5500
  }
5445
5501
  flushUploads() {
5446
5502
  this.finalizeCurrentSegmentUploads();
@@ -5557,6 +5613,26 @@ class CommandQueueImmediate {
5557
5613
  this.submit();
5558
5614
  return this._device.device.queue.onSubmittedWorkDone();
5559
5615
  }
5616
+ insertTimestampMarker(querySet, queryIndex, includePendingUploads) {
5617
+ this.endAllBodyPasses(false);
5618
+ if (includePendingUploads) {
5619
+ this.finalizeCurrentSegmentUploads();
5620
+ const encoder = this.getEncoder();
5621
+ this.encodeTimestampMarker(encoder, querySet, queryIndex);
5622
+ } else {
5623
+ const segment = this.getOrCreateCurrentSegment();
5624
+ const pendingBuffers = segment.buffersWithPendingUploads.splice(0);
5625
+ const pendingTextures = segment.texturesWithPendingUploads.splice(0);
5626
+ segment.hasBodyCommands = true;
5627
+ const encoder = this.getOrCreateBodyEncoder(segment);
5628
+ this.encodeTimestampMarker(encoder, querySet, queryIndex);
5629
+ if (pendingBuffers.length > 0 || pendingTextures.length > 0) {
5630
+ const nextSegment = this.createSegment();
5631
+ nextSegment.buffersWithPendingUploads.push(...pendingBuffers);
5632
+ nextSegment.texturesWithPendingUploads.push(...pendingTextures);
5633
+ }
5634
+ }
5635
+ }
5560
5636
  createSegment() {
5561
5637
  const segment = {
5562
5638
  uploadEncoder: null,
@@ -5581,6 +5657,12 @@ class CommandQueueImmediate {
5581
5657
  }
5582
5658
  return segment.uploadEncoder;
5583
5659
  }
5660
+ getOrCreateBodyEncoder(segment) {
5661
+ if (!segment.bodyEncoder) {
5662
+ segment.bodyEncoder = this._device.device.createCommandEncoder();
5663
+ }
5664
+ return segment.bodyEncoder;
5665
+ }
5584
5666
  ensureRenderBodyReady() {
5585
5667
  if (this._computePass.active) {
5586
5668
  this._computePass.end();
@@ -5591,9 +5673,15 @@ class CommandQueueImmediate {
5591
5673
  this._renderPass.end();
5592
5674
  }
5593
5675
  }
5594
- endAllBodyPasses() {
5676
+ flushDeferredMipmapsForBindGroups(bindGroups) {
5677
+ this._renderPass.flushDeferredMipmapsForBindGroups(bindGroups);
5678
+ }
5679
+ hasDeferredMipmapsForBindGroups(bindGroups) {
5680
+ return this._renderPass.hasDeferredMipmapsForBindGroups(bindGroups);
5681
+ }
5682
+ endAllBodyPasses(generateMipmaps = true) {
5595
5683
  if (this._renderPass.active) {
5596
- this._renderPass.end();
5684
+ this._renderPass.end(generateMipmaps);
5597
5685
  }
5598
5686
  if (this._computePass.active) {
5599
5687
  this._computePass.end();
@@ -5601,9 +5689,10 @@ class CommandQueueImmediate {
5601
5689
  }
5602
5690
  submit() {
5603
5691
  this.endAllBodyPasses();
5692
+ this._renderPass.flushDeferredMipmaps();
5604
5693
  this.finalizeCurrentSegmentUploads();
5694
+ const commandBuffers = [];
5605
5695
  if (this._segments.length > 0) {
5606
- const commandBuffers = [];
5607
5696
  for (const segment of this._segments){
5608
5697
  if (segment.uploadEncoder && segment.hasUploadCommands) {
5609
5698
  commandBuffers.push(segment.uploadEncoder.finish());
@@ -5612,9 +5701,14 @@ class CommandQueueImmediate {
5612
5701
  commandBuffers.push(segment.bodyEncoder.finish());
5613
5702
  }
5614
5703
  }
5615
- if (commandBuffers.length > 0) {
5616
- this._device.device.queue.submit(commandBuffers);
5617
- }
5704
+ }
5705
+ const timestampResolveCommandBuffer = this._device.createTimestampResolveCommandBuffer();
5706
+ if (timestampResolveCommandBuffer) {
5707
+ commandBuffers.push(timestampResolveCommandBuffer);
5708
+ }
5709
+ if (commandBuffers.length > 0) {
5710
+ this._device.device.queue.submit(commandBuffers);
5711
+ this._device.onTimestampCommandBuffersSubmitted();
5618
5712
  }
5619
5713
  for (const buffer of this._buffersAwaitingSyncEnd){
5620
5714
  buffer.endSyncChanges();
@@ -5627,6 +5721,16 @@ class CommandQueueImmediate {
5627
5721
  this._segments.length = 0;
5628
5722
  this._drawcallCounter = 0;
5629
5723
  }
5724
+ encodeTimestampMarker(encoder, querySet, queryIndex) {
5725
+ const pass = encoder.beginComputePass({
5726
+ label: `timestamp-marker:${queryIndex}`,
5727
+ timestampWrites: {
5728
+ querySet,
5729
+ beginningOfPassWriteIndex: queryIndex
5730
+ }
5731
+ });
5732
+ pass.end();
5733
+ }
5630
5734
  markBufferAwaitingSyncEnd(buffer) {
5631
5735
  if (this._buffersAwaitingSyncEnd.indexOf(buffer) < 0) {
5632
5736
  this._buffersAwaitingSyncEnd.push(buffer);
@@ -5675,6 +5779,301 @@ class CommandQueueImmediate {
5675
5779
  }
5676
5780
  }
5677
5781
 
5782
+ const TIMESTAMP_QUERY_COUNT = 4096;
5783
+ const TIMESTAMP_BYTES_PER_QUERY = 8;
5784
+ const TIMESTAMP_QUERIES_PER_SCOPE = 2;
5785
+ const TIMESTAMP_RESOLVE_STRIDE = 256;
5786
+ const MAX_RESOLVED_RESULT_HISTORY = 4096;
5787
+ const defaultTimestampQueryOptions = {
5788
+ includePendingUploads: true,
5789
+ allowCrossFrame: false,
5790
+ strict: false
5791
+ };
5792
+ class WebGPUTimestampQueryManager {
5793
+ _device;
5794
+ _supported;
5795
+ _querySet;
5796
+ _freeQueryPairs;
5797
+ _records;
5798
+ _openRecords;
5799
+ _endedRecords;
5800
+ _pendingSubmitBatches;
5801
+ _resolvedResults;
5802
+ _nextId;
5803
+ constructor(device){
5804
+ this._device = device;
5805
+ this._supported = device.device.features.has('timestamp-query');
5806
+ this._querySet = this._supported ? device.device.createQuerySet({
5807
+ type: 'timestamp',
5808
+ count: TIMESTAMP_QUERY_COUNT
5809
+ }) : null;
5810
+ this._freeQueryPairs = [];
5811
+ for(let i = TIMESTAMP_QUERY_COUNT - 2; i >= 0; i -= 2){
5812
+ this._freeQueryPairs.push(i);
5813
+ }
5814
+ this._records = new Map();
5815
+ this._openRecords = new Map();
5816
+ this._endedRecords = [];
5817
+ this._pendingSubmitBatches = [];
5818
+ this._resolvedResults = [];
5819
+ this._nextId = 0;
5820
+ }
5821
+ get supported() {
5822
+ return this._supported;
5823
+ }
5824
+ begin(label, options) {
5825
+ if (!this._supported || !this._querySet) {
5826
+ return 0;
5827
+ }
5828
+ const id = ++this._nextId;
5829
+ const resolvedOptions = {
5830
+ ...defaultTimestampQueryOptions,
5831
+ ...options ?? {}
5832
+ };
5833
+ const startIndex = this._freeQueryPairs.pop();
5834
+ if (startIndex === undefined) {
5835
+ const result = this.createResult(id, label ?? '', this._device.frameInfo.frameCounter, 0n, 0n, 'exhausted', false, 'GPU timestamp query pool exhausted');
5836
+ this._records.set(id, {
5837
+ id,
5838
+ label: result.label,
5839
+ frameId: result.frameId,
5840
+ options: resolvedOptions,
5841
+ startIndex: -1,
5842
+ endIndex: -1,
5843
+ status: 'exhausted',
5844
+ autoClosed: false,
5845
+ result,
5846
+ waiters: []
5847
+ });
5848
+ this.pushResolvedResult(result);
5849
+ return id;
5850
+ }
5851
+ const record = {
5852
+ id,
5853
+ label: label ?? '',
5854
+ frameId: this._device.frameInfo.frameCounter,
5855
+ options: resolvedOptions,
5856
+ startIndex,
5857
+ endIndex: startIndex + 1,
5858
+ status: 'open',
5859
+ autoClosed: false,
5860
+ result: null,
5861
+ waiters: []
5862
+ };
5863
+ this._records.set(id, record);
5864
+ this._openRecords.set(id, record);
5865
+ this._device.commandQueue.insertTimestampMarker(this._querySet, record.startIndex, record.options.includePendingUploads);
5866
+ return id;
5867
+ }
5868
+ end(id) {
5869
+ if (!this._supported) {
5870
+ return;
5871
+ }
5872
+ const record = this._records.get(id);
5873
+ if (!record) {
5874
+ this.reportInvalidEnd(id, 'endTimestampQuery() ignored: invalid query id');
5875
+ return;
5876
+ }
5877
+ if (record.status !== 'open') {
5878
+ if (record.status !== 'exhausted' && record.status !== 'unsupported') {
5879
+ this.reportInvalidEnd(id, 'endTimestampQuery() ignored: query is not open');
5880
+ }
5881
+ return;
5882
+ }
5883
+ this.endRecord(record, false);
5884
+ }
5885
+ autoCloseOpenScopes() {
5886
+ for (const record of Array.from(this._openRecords.values())){
5887
+ if (!record.options.allowCrossFrame) {
5888
+ this.endRecord(record, true);
5889
+ }
5890
+ }
5891
+ }
5892
+ createResolveCommandBuffer() {
5893
+ if (!this._querySet || this._endedRecords.length === 0) {
5894
+ return null;
5895
+ }
5896
+ const records = this._endedRecords.splice(0);
5897
+ const entries = records.map((record, index)=>{
5898
+ record.status = 'resolving';
5899
+ return {
5900
+ record,
5901
+ readbackOffset: index * TIMESTAMP_RESOLVE_STRIDE
5902
+ };
5903
+ });
5904
+ const byteLength = entries.length * TIMESTAMP_RESOLVE_STRIDE;
5905
+ const resolveBuffer = this._device.device.createBuffer({
5906
+ label: 'timestamp-query-resolve',
5907
+ size: byteLength,
5908
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
5909
+ });
5910
+ const readbackBuffer = this._device.device.createBuffer({
5911
+ label: 'timestamp-query-readback',
5912
+ size: byteLength,
5913
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
5914
+ });
5915
+ const encoder = this._device.device.createCommandEncoder({
5916
+ label: 'timestamp-query-resolve'
5917
+ });
5918
+ for (const entry of entries){
5919
+ encoder.resolveQuerySet(this._querySet, entry.record.startIndex, TIMESTAMP_QUERIES_PER_SCOPE, resolveBuffer, entry.readbackOffset);
5920
+ }
5921
+ encoder.copyBufferToBuffer(resolveBuffer, 0, readbackBuffer, 0, byteLength);
5922
+ this._pendingSubmitBatches.push({
5923
+ entries,
5924
+ resolveBuffer,
5925
+ readbackBuffer
5926
+ });
5927
+ return encoder.finish();
5928
+ }
5929
+ onSubmitted() {
5930
+ const batches = this._pendingSubmitBatches.splice(0);
5931
+ for (const batch of batches){
5932
+ batch.readbackBuffer.mapAsync(GPUMapMode.READ).then(()=>{
5933
+ this.completeBatch(batch);
5934
+ }).catch((err)=>{
5935
+ this.failBatch(batch, err instanceof Error ? err.message : String(err));
5936
+ });
5937
+ }
5938
+ }
5939
+ poll(query) {
5940
+ if (!this._supported) {
5941
+ return this.createUnsupportedResult(query);
5942
+ }
5943
+ const record = this.findRecord(query);
5944
+ if (!record) {
5945
+ return this.createInvalidResult(query, 'Timestamp query not found');
5946
+ }
5947
+ return record.result;
5948
+ }
5949
+ resolve(query) {
5950
+ if (!this._supported) {
5951
+ return Promise.resolve(this.createUnsupportedResult(query));
5952
+ }
5953
+ const record = this.findRecord(query);
5954
+ if (!record) {
5955
+ return Promise.resolve(this.createInvalidResult(query, 'Timestamp query not found'));
5956
+ }
5957
+ if (record.result) {
5958
+ return Promise.resolve(record.result);
5959
+ }
5960
+ return new Promise((resolve)=>{
5961
+ record.waiters.push(resolve);
5962
+ });
5963
+ }
5964
+ collect(filter) {
5965
+ return this._resolvedResults.filter((result)=>{
5966
+ return (filter?.label === undefined || result.label === filter.label) && (filter?.frameId === undefined || result.frameId === filter.frameId);
5967
+ });
5968
+ }
5969
+ endRecord(record, autoClosed) {
5970
+ if (!this._querySet) {
5971
+ return;
5972
+ }
5973
+ record.autoClosed = autoClosed;
5974
+ this._openRecords.delete(record.id);
5975
+ this._device.commandQueue.insertTimestampMarker(this._querySet, record.endIndex, record.options.includePendingUploads);
5976
+ record.status = 'ended';
5977
+ this._endedRecords.push(record);
5978
+ if (autoClosed) {
5979
+ console.warn(`GPU timestamp query '${record.label}' was automatically closed at frame end`);
5980
+ }
5981
+ }
5982
+ completeBatch(batch) {
5983
+ const values = new BigUint64Array(batch.readbackBuffer.getMappedRange());
5984
+ for (const entry of batch.entries){
5985
+ const record = entry.record;
5986
+ const valueOffset = entry.readbackOffset / TIMESTAMP_BYTES_PER_QUERY;
5987
+ const start = values[valueOffset];
5988
+ const end = values[valueOffset + 1];
5989
+ const duration = end >= start ? end - start : 0n;
5990
+ const result = this.createResult(record.id, record.label, record.frameId, start, end, record.autoClosed ? 'auto-closed' : 'resolved', record.autoClosed);
5991
+ result.durationMs = Number(duration) / 1000000;
5992
+ record.status = result.status;
5993
+ record.result = result;
5994
+ this.freeQueryPair(record.startIndex);
5995
+ this.resolveWaiters(record);
5996
+ this.pushResolvedResult(result);
5997
+ }
5998
+ batch.readbackBuffer.unmap();
5999
+ batch.readbackBuffer.destroy();
6000
+ batch.resolveBuffer.destroy();
6001
+ }
6002
+ failBatch(batch, message) {
6003
+ for (const entry of batch.entries){
6004
+ const record = entry.record;
6005
+ const result = this.createResult(record.id, record.label, record.frameId, 0n, 0n, 'failed', record.autoClosed, message);
6006
+ record.status = 'failed';
6007
+ record.result = result;
6008
+ this.freeQueryPair(record.startIndex);
6009
+ this.resolveWaiters(record);
6010
+ this.pushResolvedResult(result);
6011
+ }
6012
+ batch.readbackBuffer.destroy();
6013
+ batch.resolveBuffer.destroy();
6014
+ }
6015
+ freeQueryPair(startIndex) {
6016
+ if (startIndex >= 0 && this._freeQueryPairs.indexOf(startIndex) < 0) {
6017
+ this._freeQueryPairs.push(startIndex);
6018
+ }
6019
+ }
6020
+ resolveWaiters(record) {
6021
+ for (const resolve of record.waiters.splice(0)){
6022
+ resolve(record.result);
6023
+ }
6024
+ }
6025
+ pushResolvedResult(result) {
6026
+ this._resolvedResults.push(result);
6027
+ while(this._resolvedResults.length > MAX_RESOLVED_RESULT_HISTORY){
6028
+ const oldResult = this._resolvedResults.shift();
6029
+ if (oldResult) {
6030
+ const oldRecord = this._records.get(oldResult.id);
6031
+ if (oldRecord?.result === oldResult) {
6032
+ this._records.delete(oldResult.id);
6033
+ }
6034
+ }
6035
+ }
6036
+ }
6037
+ reportInvalidEnd(id, message) {
6038
+ const record = this._records.get(id);
6039
+ if (record?.options.strict) {
6040
+ throw new Error(message);
6041
+ }
6042
+ console.warn(message);
6043
+ }
6044
+ findRecord(query) {
6045
+ if (typeof query === 'number') {
6046
+ return this._records.get(query) ?? null;
6047
+ }
6048
+ let latest = null;
6049
+ for (const record of this._records.values()){
6050
+ if (record.label === query) {
6051
+ latest = record;
6052
+ }
6053
+ }
6054
+ return latest;
6055
+ }
6056
+ createUnsupportedResult(query) {
6057
+ return this.createResult(typeof query === 'number' ? query : 0, typeof query === 'string' ? query : '', this._device.frameInfo.frameCounter, 0n, 0n, 'unsupported', false, 'GPU timestamp queries are not supported by this WebGPU device');
6058
+ }
6059
+ createInvalidResult(query, message) {
6060
+ return this.createResult(typeof query === 'number' ? query : 0, typeof query === 'string' ? query : '', this._device.frameInfo.frameCounter, 0n, 0n, 'invalid', false, message);
6061
+ }
6062
+ createResult(id, label, frameId, start, end, status, autoClosed, message) {
6063
+ return {
6064
+ id,
6065
+ label,
6066
+ frameId,
6067
+ durationMs: 0,
6068
+ start,
6069
+ end,
6070
+ status,
6071
+ autoClosed: autoClosed || undefined,
6072
+ message
6073
+ };
6074
+ }
6075
+ }
6076
+
5678
6077
  class WebGPUDevice extends BaseDevice {
5679
6078
  _context;
5680
6079
  _device;
@@ -5705,6 +6104,7 @@ class WebGPUDevice extends BaseDevice {
5705
6104
  _emptyBindGroup;
5706
6105
  _captureRenderBundle;
5707
6106
  _adapterInfo;
6107
+ _timestampQueries;
5708
6108
  constructor(backend, cvs, options){
5709
6109
  super(cvs, backend, options?.dpr);
5710
6110
  this._reverseWindingOrder = false;
@@ -5815,7 +6215,25 @@ class WebGPUDevice extends BaseDevice {
5815
6215
  ...this._adapter.features
5816
6216
  ],
5817
6217
  requiredLimits: {
5818
- ...this._adapter.limits
6218
+ maxBufferSize: this._adapter.limits.maxBufferSize,
6219
+ maxBindingsPerBindGroup: this._adapter.limits.maxBindingsPerBindGroup,
6220
+ maxTextureDimension2D: this._adapter.limits.maxTextureDimension2D,
6221
+ maxComputeWorkgroupSizeX: this._adapter.limits.maxComputeWorkgroupSizeX,
6222
+ maxComputeWorkgroupSizeY: this._adapter.limits.maxComputeWorkgroupSizeY,
6223
+ maxComputeWorkgroupSizeZ: this._adapter.limits.maxComputeWorkgroupSizeZ,
6224
+ maxSampledTexturesPerShaderStage: this._adapter.limits.maxSampledTexturesPerShaderStage,
6225
+ maxSamplersPerShaderStage: this._adapter.limits.maxSamplersPerShaderStage,
6226
+ maxStorageBuffersPerShaderStage: this._adapter.limits.maxStorageBuffersPerShaderStage,
6227
+ maxStorageTexturesPerShaderStage: this._adapter.limits.maxStorageTexturesPerShaderStage,
6228
+ maxUniformBuffersPerShaderStage: this._adapter.limits.maxUniformBuffersPerShaderStage,
6229
+ maxUniformBufferBindingSize: this._adapter.limits.maxUniformBufferBindingSize,
6230
+ maxStorageBufferBindingSize: this._adapter.limits.maxStorageBufferBindingSize,
6231
+ maxInterStageShaderVariables: this._adapter.limits.maxInterStageShaderVariables,
6232
+ maxColorAttachments: this._adapter.limits.maxColorAttachments,
6233
+ maxColorAttachmentBytesPerSample: this._adapter.limits.maxColorAttachmentBytesPerSample,
6234
+ maxComputeWorkgroupStorageSize: this._adapter.limits.maxComputeWorkgroupStorageSize,
6235
+ maxComputeInvocationsPerWorkgroup: this._adapter.limits.maxComputeInvocationsPerWorkgroup,
6236
+ maxComputeWorkgroupsPerDimension: this._adapter.limits.maxComputeWorkgroupsPerDimension
5819
6237
  }
5820
6238
  });
5821
6239
  console.info('WebGPU device features:');
@@ -5851,6 +6269,7 @@ class WebGPUDevice extends BaseDevice {
5851
6269
  this._bindGroupCache = new BindGroupCache(this);
5852
6270
  this._vertexLayoutCache = new VertexLayoutCache();
5853
6271
  this._commandQueue = new CommandQueueImmediate(this);
6272
+ this._timestampQueries = new WebGPUTimestampQueryManager(this);
5854
6273
  this._canRender = true;
5855
6274
  this.setViewport(null);
5856
6275
  this.setScissor(null);
@@ -5885,6 +6304,21 @@ class WebGPUDevice extends BaseDevice {
5885
6304
  // throw new Error('not implemented');
5886
6305
  return null;
5887
6306
  }
6307
+ beginTimestampQuery(label, options) {
6308
+ return this._timestampQueries.begin(label, options);
6309
+ }
6310
+ endTimestampQuery(id) {
6311
+ this._timestampQueries.end(id);
6312
+ }
6313
+ pollTimestampQuery(query) {
6314
+ return this._timestampQueries.poll(query);
6315
+ }
6316
+ resolveTimestampQuery(query) {
6317
+ return this._timestampQueries.resolve(query);
6318
+ }
6319
+ collectTimestampQueries(filter) {
6320
+ return this._timestampQueries.collect(filter);
6321
+ }
5888
6322
  createRenderStateSet() {
5889
6323
  return new WebGPURenderStateSet(this);
5890
6324
  }
@@ -6300,6 +6734,12 @@ class WebGPUDevice extends BaseDevice {
6300
6734
  flushUploads() {
6301
6735
  this._commandQueue.flushUploads();
6302
6736
  }
6737
+ /** @internal */ createTimestampResolveCommandBuffer() {
6738
+ return this._timestampQueries.createResolveCommandBuffer();
6739
+ }
6740
+ /** @internal */ onTimestampCommandBuffersSubmitted() {
6741
+ this._timestampQueries.onSubmitted();
6742
+ }
6303
6743
  /** @internal */ _setFramebuffer(rt) {
6304
6744
  this._commandQueue.setFramebuffer(rt);
6305
6745
  }
@@ -6312,6 +6752,7 @@ class WebGPUDevice extends BaseDevice {
6312
6752
  }
6313
6753
  }
6314
6754
  /** @internal */ onEndFrame() {
6755
+ this._timestampQueries.autoCloseOpenScopes();
6315
6756
  this._commandQueue.endFrame();
6316
6757
  }
6317
6758
  /** @internal */ _draw(primitiveType, first, count) {