@luma.gl/webgpu 9.2.6 → 9.3.0-alpha.11

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 (137) hide show
  1. package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts +54 -0
  2. package/dist/adapter/helpers/cpu-hotspot-profiler.d.ts.map +1 -0
  3. package/dist/adapter/helpers/cpu-hotspot-profiler.js +26 -0
  4. package/dist/adapter/helpers/cpu-hotspot-profiler.js.map +1 -0
  5. package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts +7 -0
  6. package/dist/adapter/helpers/generate-mipmaps-webgpu.d.ts.map +1 -0
  7. package/dist/adapter/helpers/generate-mipmaps-webgpu.js +490 -0
  8. package/dist/adapter/helpers/generate-mipmaps-webgpu.js.map +1 -0
  9. package/dist/adapter/helpers/get-bind-group.d.ts +8 -6
  10. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  11. package/dist/adapter/helpers/get-bind-group.js +110 -30
  12. package/dist/adapter/helpers/get-bind-group.js.map +1 -1
  13. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +3 -1
  14. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -1
  15. package/dist/adapter/helpers/get-vertex-buffer-layout.js +17 -12
  16. package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -1
  17. package/dist/adapter/helpers/webgpu-parameters.d.ts.map +1 -1
  18. package/dist/adapter/helpers/webgpu-parameters.js +1 -0
  19. package/dist/adapter/helpers/webgpu-parameters.js.map +1 -1
  20. package/dist/adapter/resources/webgpu-buffer.d.ts +7 -0
  21. package/dist/adapter/resources/webgpu-buffer.d.ts.map +1 -1
  22. package/dist/adapter/resources/webgpu-buffer.js +58 -15
  23. package/dist/adapter/resources/webgpu-buffer.js.map +1 -1
  24. package/dist/adapter/resources/webgpu-command-buffer.js +1 -1
  25. package/dist/adapter/resources/webgpu-command-buffer.js.map +1 -1
  26. package/dist/adapter/resources/webgpu-command-encoder.d.ts +7 -16
  27. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  28. package/dist/adapter/resources/webgpu-command-encoder.js +89 -32
  29. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  30. package/dist/adapter/resources/webgpu-compute-pass.d.ts +3 -3
  31. package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
  32. package/dist/adapter/resources/webgpu-compute-pass.js +30 -12
  33. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  34. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +7 -9
  35. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  36. package/dist/adapter/resources/webgpu-compute-pipeline.js +30 -17
  37. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  38. package/dist/adapter/resources/webgpu-fence.d.ts +13 -0
  39. package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -0
  40. package/dist/adapter/resources/webgpu-fence.js +33 -0
  41. package/dist/adapter/resources/webgpu-fence.js.map +1 -0
  42. package/dist/adapter/resources/webgpu-framebuffer.d.ts +6 -0
  43. package/dist/adapter/resources/webgpu-framebuffer.d.ts.map +1 -1
  44. package/dist/adapter/resources/webgpu-framebuffer.js +16 -0
  45. package/dist/adapter/resources/webgpu-framebuffer.js.map +1 -1
  46. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +1 -1
  47. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  48. package/dist/adapter/resources/webgpu-pipeline-layout.js +11 -18
  49. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  50. package/dist/adapter/resources/webgpu-query-set.d.ts +33 -4
  51. package/dist/adapter/resources/webgpu-query-set.d.ts.map +1 -1
  52. package/dist/adapter/resources/webgpu-query-set.js +145 -4
  53. package/dist/adapter/resources/webgpu-query-set.js.map +1 -1
  54. package/dist/adapter/resources/webgpu-render-pass.d.ts +6 -3
  55. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  56. package/dist/adapter/resources/webgpu-render-pass.js +78 -34
  57. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  58. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +14 -10
  59. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  60. package/dist/adapter/resources/webgpu-render-pipeline.js +62 -35
  61. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  62. package/dist/adapter/resources/webgpu-sampler.d.ts.map +1 -1
  63. package/dist/adapter/resources/webgpu-sampler.js +4 -0
  64. package/dist/adapter/resources/webgpu-sampler.js.map +1 -1
  65. package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
  66. package/dist/adapter/resources/webgpu-shader.js +17 -1
  67. package/dist/adapter/resources/webgpu-shader.js.map +1 -1
  68. package/dist/adapter/resources/webgpu-texture-view.d.ts +6 -0
  69. package/dist/adapter/resources/webgpu-texture-view.d.ts.map +1 -1
  70. package/dist/adapter/resources/webgpu-texture-view.js +47 -11
  71. package/dist/adapter/resources/webgpu-texture-view.js.map +1 -1
  72. package/dist/adapter/resources/webgpu-texture.d.ts +25 -3
  73. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  74. package/dist/adapter/resources/webgpu-texture.js +211 -43
  75. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  76. package/dist/adapter/resources/webgpu-vertex-array.js +1 -1
  77. package/dist/adapter/resources/webgpu-vertex-array.js.map +1 -1
  78. package/dist/adapter/webgpu-adapter.d.ts.map +1 -1
  79. package/dist/adapter/webgpu-adapter.js +35 -35
  80. package/dist/adapter/webgpu-adapter.js.map +1 -1
  81. package/dist/adapter/webgpu-canvas-context.d.ts +6 -3
  82. package/dist/adapter/webgpu-canvas-context.d.ts.map +1 -1
  83. package/dist/adapter/webgpu-canvas-context.js +90 -30
  84. package/dist/adapter/webgpu-canvas-context.js.map +1 -1
  85. package/dist/adapter/webgpu-device.d.ts +12 -2
  86. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  87. package/dist/adapter/webgpu-device.js +176 -19
  88. package/dist/adapter/webgpu-device.js.map +1 -1
  89. package/dist/adapter/webgpu-presentation-context.d.ts +25 -0
  90. package/dist/adapter/webgpu-presentation-context.d.ts.map +1 -0
  91. package/dist/adapter/webgpu-presentation-context.js +144 -0
  92. package/dist/adapter/webgpu-presentation-context.js.map +1 -0
  93. package/dist/dist.dev.js +8160 -551
  94. package/dist/dist.min.js +171 -6
  95. package/dist/index.cjs +2001 -414
  96. package/dist/index.cjs.map +4 -4
  97. package/dist/index.d.ts +2 -0
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +2 -0
  100. package/dist/index.js.map +1 -1
  101. package/dist/wgsl/get-shader-layout-wgsl.d.ts +8 -0
  102. package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -0
  103. package/dist/wgsl/get-shader-layout-wgsl.js +144 -0
  104. package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -0
  105. package/package.json +6 -5
  106. package/src/adapter/helpers/cpu-hotspot-profiler.ts +70 -0
  107. package/src/adapter/helpers/generate-mipmaps-webgpu.ts +583 -0
  108. package/src/adapter/helpers/get-bind-group.ts +182 -42
  109. package/src/adapter/helpers/get-vertex-buffer-layout.ts +31 -12
  110. package/src/adapter/helpers/webgpu-parameters.ts +2 -0
  111. package/src/adapter/resources/webgpu-buffer.ts +61 -15
  112. package/src/adapter/resources/webgpu-command-buffer.ts +1 -1
  113. package/src/adapter/resources/webgpu-command-encoder.ts +129 -50
  114. package/src/adapter/resources/webgpu-compute-pass.ts +48 -13
  115. package/src/adapter/resources/webgpu-compute-pipeline.ts +49 -18
  116. package/src/adapter/resources/webgpu-fence.ts +38 -0
  117. package/src/adapter/resources/webgpu-framebuffer.ts +21 -0
  118. package/src/adapter/resources/webgpu-pipeline-layout.ts +18 -17
  119. package/src/adapter/resources/webgpu-query-set.ts +185 -9
  120. package/src/adapter/resources/webgpu-render-pass.ts +92 -40
  121. package/src/adapter/resources/webgpu-render-pipeline.ts +90 -44
  122. package/src/adapter/resources/webgpu-sampler.ts +5 -0
  123. package/src/adapter/resources/webgpu-shader.ts +16 -1
  124. package/src/adapter/resources/webgpu-texture-view.ts +51 -11
  125. package/src/adapter/resources/webgpu-texture.ts +281 -101
  126. package/src/adapter/resources/webgpu-vertex-array.ts +1 -1
  127. package/src/adapter/webgpu-adapter.ts +41 -43
  128. package/src/adapter/webgpu-canvas-context.ts +108 -41
  129. package/src/adapter/webgpu-device.ts +243 -25
  130. package/src/adapter/webgpu-presentation-context.ts +180 -0
  131. package/src/index.ts +3 -0
  132. package/src/wgsl/get-shader-layout-wgsl.ts +165 -0
  133. package/dist/adapter/helpers/accessor-to-format.d.ts +0 -1
  134. package/dist/adapter/helpers/accessor-to-format.d.ts.map +0 -1
  135. package/dist/adapter/helpers/accessor-to-format.js +0 -105
  136. package/dist/adapter/helpers/accessor-to-format.js.map +0 -1
  137. package/src/adapter/helpers/accessor-to-format.ts +0 -104
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- // prettier-ignore
5
+ // biome-ignore format: preserve layout
6
6
  // / <reference types="@webgpu/types" />
7
7
 
8
8
  import type {TextureFormatDepthStencil, CanvasContextProps} from '@luma.gl/core';
@@ -10,6 +10,7 @@ import {CanvasContext, Texture, log} from '@luma.gl/core';
10
10
  import {WebGPUDevice} from './webgpu-device';
11
11
  import {WebGPUFramebuffer} from './resources/webgpu-framebuffer';
12
12
  import {WebGPUTexture} from './resources/webgpu-texture';
13
+ import {getCpuHotspotProfiler, getTimestamp} from './helpers/cpu-hotspot-profiler';
13
14
 
14
15
  /**
15
16
  * Holds a WebGPU Canvas Context
@@ -20,7 +21,9 @@ export class WebGPUCanvasContext extends CanvasContext {
20
21
  readonly device: WebGPUDevice;
21
22
  readonly handle: GPUCanvasContext;
22
23
 
24
+ private colorAttachment: WebGPUTexture | null = null;
23
25
  private depthStencilAttachment: WebGPUTexture | null = null;
26
+ private framebuffer: WebGPUFramebuffer | null = null;
24
27
 
25
28
  get [Symbol.toStringTag](): string {
26
29
  return 'WebGPUCanvasContext';
@@ -38,58 +41,38 @@ export class WebGPUCanvasContext extends CanvasContext {
38
41
 
39
42
  // Base class constructor cannot access derived methods/fields, so we need to call these functions in the subclass constructor
40
43
  this._setAutoCreatedCanvasId(`${this.device.id}-canvas`);
41
- this._updateDevice();
44
+ this._configureDevice();
45
+ this._startObservers();
42
46
  }
43
47
 
44
48
  /** Destroy any textures produced while configured and remove the context configuration. */
45
49
  override destroy(): void {
46
- this.handle.unconfigure();
47
- super.destroy();
48
- }
49
-
50
- /** Update framebuffer with properly resized "swap chain" texture views */
51
- getCurrentFramebuffer(
52
- options: {depthStencilFormat?: TextureFormatDepthStencil | false} = {
53
- depthStencilFormat: 'depth24plus'
50
+ if (this.framebuffer) {
51
+ this.framebuffer.destroy();
52
+ this.framebuffer = null;
54
53
  }
55
- ): WebGPUFramebuffer {
56
- // Wrap the current canvas context texture in a luma.gl texture
57
- const currentColorAttachment = this.getCurrentTexture();
58
- // TODO - temporary debug code
59
- if (
60
- currentColorAttachment.width !== this.drawingBufferWidth ||
61
- currentColorAttachment.height !== this.drawingBufferHeight
62
- ) {
63
- const [oldWidth, oldHeight] = this.getDrawingBufferSize();
64
- this.drawingBufferWidth = currentColorAttachment.width;
65
- this.drawingBufferHeight = currentColorAttachment.height;
66
- log.log(
67
- 1,
68
- `${this}: Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`
69
- )();
54
+ if (this.colorAttachment) {
55
+ this.colorAttachment.destroy();
56
+ this.colorAttachment = null;
70
57
  }
71
-
72
- // Resize the depth stencil attachment
73
- if (options?.depthStencilFormat) {
74
- this._createDepthStencilAttachment(options?.depthStencilFormat);
58
+ if (this.depthStencilAttachment) {
59
+ this.depthStencilAttachment.destroy();
60
+ this.depthStencilAttachment = null;
75
61
  }
76
-
77
- return new WebGPUFramebuffer(this.device, {
78
- colorAttachments: [currentColorAttachment],
79
- depthStencilAttachment: this.depthStencilAttachment
80
- });
62
+ this.handle.unconfigure();
63
+ super.destroy();
81
64
  }
82
65
 
83
66
  // IMPLEMENTATION OF ABSTRACT METHODS
84
67
 
85
- _updateDevice(): void {
68
+ /** @see https://www.w3.org/TR/webgpu/#canvas-configuration */
69
+ _configureDevice(): void {
86
70
  if (this.depthStencilAttachment) {
87
71
  this.depthStencilAttachment.destroy();
88
72
  this.depthStencilAttachment = null;
89
73
  }
90
74
 
91
75
  // Reconfigure the canvas size.
92
- // https://www.w3.org/TR/webgpu/#canvas-configuration
93
76
  this.handle.configure({
94
77
  device: this.device.handle,
95
78
  format: this.device.preferredColorFormat,
@@ -98,23 +81,107 @@ export class WebGPUCanvasContext extends CanvasContext {
98
81
  colorSpace: this.props.colorSpace,
99
82
  alphaMode: this.props.alphaMode
100
83
  });
84
+
85
+ this._createDepthStencilAttachment(this.device.preferredDepthFormat);
101
86
  }
102
87
 
88
+ /** Update framebuffer with properly resized "swap chain" texture views */
89
+ _getCurrentFramebuffer(
90
+ options: {depthStencilFormat?: TextureFormatDepthStencil | false} = {
91
+ depthStencilFormat: 'depth24plus'
92
+ }
93
+ ): WebGPUFramebuffer {
94
+ const profiler = getCpuHotspotProfiler(this.device);
95
+ const startTime = profiler ? getTimestamp() : 0;
96
+ if (profiler) {
97
+ profiler.framebufferAcquireCount = (profiler.framebufferAcquireCount || 0) + 1;
98
+ profiler.activeDefaultFramebufferAcquireDepth =
99
+ (profiler.activeDefaultFramebufferAcquireDepth || 0) + 1;
100
+ }
101
+
102
+ try {
103
+ // Wrap the current canvas context texture in a luma.gl texture
104
+ const currentColorAttachment = this._getCurrentTexture();
105
+ // TODO - temporary debug code
106
+ if (
107
+ currentColorAttachment.width !== this.drawingBufferWidth ||
108
+ currentColorAttachment.height !== this.drawingBufferHeight
109
+ ) {
110
+ const [oldWidth, oldHeight] = this.getDrawingBufferSize();
111
+ this.drawingBufferWidth = currentColorAttachment.width;
112
+ this.drawingBufferHeight = currentColorAttachment.height;
113
+ log.log(
114
+ 1,
115
+ `${this}: Resized to compensate for initial canvas size mismatch ${oldWidth}x${oldHeight} => ${this.drawingBufferWidth}x${this.drawingBufferHeight}px`
116
+ )();
117
+ }
118
+
119
+ // Resize the depth stencil attachment
120
+ if (options?.depthStencilFormat) {
121
+ this._createDepthStencilAttachment(options?.depthStencilFormat);
122
+ }
123
+
124
+ this.framebuffer ||= new WebGPUFramebuffer(this.device, {
125
+ id: `${this.id}#framebuffer`,
126
+ colorAttachments: [currentColorAttachment],
127
+ depthStencilAttachment: null
128
+ });
129
+ this.framebuffer._reinitialize(
130
+ currentColorAttachment.view,
131
+ options?.depthStencilFormat ? this.depthStencilAttachment?.view || null : null
132
+ );
133
+ return this.framebuffer;
134
+ } finally {
135
+ if (profiler) {
136
+ profiler.activeDefaultFramebufferAcquireDepth =
137
+ (profiler.activeDefaultFramebufferAcquireDepth || 1) - 1;
138
+ profiler.framebufferAcquireTimeMs =
139
+ (profiler.framebufferAcquireTimeMs || 0) + (getTimestamp() - startTime);
140
+ }
141
+ }
142
+ }
143
+
144
+ // PRIMARY METHODS
145
+
103
146
  /** Wrap the current canvas context texture in a luma.gl texture */
104
- getCurrentTexture(): WebGPUTexture {
147
+ _getCurrentTexture(): WebGPUTexture {
148
+ const profiler = getCpuHotspotProfiler(this.device);
149
+ const currentTextureStartTime = profiler ? getTimestamp() : 0;
105
150
  const handle = this.handle.getCurrentTexture();
106
- return this.device.createTexture({
107
- id: `${this.id}#color-texture`,
151
+ if (profiler) {
152
+ profiler.currentTextureAcquireCount = (profiler.currentTextureAcquireCount || 0) + 1;
153
+ profiler.currentTextureAcquireTimeMs =
154
+ (profiler.currentTextureAcquireTimeMs || 0) + (getTimestamp() - currentTextureStartTime);
155
+ }
156
+ if (!this.colorAttachment) {
157
+ this.colorAttachment = this.device.createTexture({
158
+ id: `${this.id}#color-texture`,
159
+ handle,
160
+ format: this.device.preferredColorFormat,
161
+ width: handle.width,
162
+ height: handle.height
163
+ });
164
+ return this.colorAttachment;
165
+ }
166
+
167
+ this.colorAttachment._reinitialize(handle, {
108
168
  handle,
109
169
  format: this.device.preferredColorFormat,
110
170
  width: handle.width,
111
171
  height: handle.height
112
172
  });
173
+ return this.colorAttachment;
113
174
  }
114
175
 
115
176
  /** We build render targets on demand (i.e. not when size changes but when about to render) */
116
177
  _createDepthStencilAttachment(depthStencilFormat: TextureFormatDepthStencil): WebGPUTexture {
117
- if (!this.depthStencilAttachment) {
178
+ const needsNewDepthStencilAttachment =
179
+ !this.depthStencilAttachment ||
180
+ this.depthStencilAttachment.width !== this.drawingBufferWidth ||
181
+ this.depthStencilAttachment.height !== this.drawingBufferHeight ||
182
+ this.depthStencilAttachment.format !== depthStencilFormat;
183
+ if (needsNewDepthStencilAttachment) {
184
+ this.depthStencilAttachment?.destroy();
118
185
  this.depthStencilAttachment = this.device.createTexture({
119
186
  id: `${this.id}#depth-stencil-texture`,
120
187
  usage: Texture.RENDER_ATTACHMENT,
@@ -123,6 +190,6 @@ export class WebGPUCanvasContext extends CanvasContext {
123
190
  height: this.drawingBufferHeight
124
191
  });
125
192
  }
126
- return this.depthStencilAttachment;
193
+ return this.depthStencilAttachment!;
127
194
  }
128
195
  }
@@ -2,20 +2,26 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- // prettier-ignore
5
+ // biome-ignore format: preserve layout
6
6
  // / <reference types="@webgpu/types" />
7
7
 
8
8
  import type {
9
+ Bindings,
10
+ ComputePipeline,
11
+ ComputeShaderLayout,
9
12
  DeviceInfo,
10
13
  DeviceLimits,
11
14
  DeviceFeature,
12
15
  DeviceTextureFormatCapabilities,
13
16
  VertexFormat,
14
17
  CanvasContextProps,
18
+ PresentationContextProps,
19
+ PresentationContext,
15
20
  BufferProps,
16
21
  SamplerProps,
17
22
  ShaderProps,
18
23
  TextureProps,
24
+ Texture,
19
25
  ExternalTextureProps,
20
26
  FramebufferProps,
21
27
  RenderPipelineProps,
@@ -28,6 +34,8 @@ import type {
28
34
  DeviceProps,
29
35
  CommandEncoderProps,
30
36
  PipelineLayoutProps,
37
+ RenderPipeline,
38
+ ShaderLayout
31
39
  } from '@luma.gl/core';
32
40
  import {Device, DeviceFeatures} from '@luma.gl/core';
33
41
  import {WebGPUBuffer} from './resources/webgpu-buffer';
@@ -41,10 +49,21 @@ import {WebGPUComputePipeline} from './resources/webgpu-compute-pipeline';
41
49
  import {WebGPUVertexArray} from './resources/webgpu-vertex-array';
42
50
 
43
51
  import {WebGPUCanvasContext} from './webgpu-canvas-context';
52
+ import {WebGPUPresentationContext} from './webgpu-presentation-context';
44
53
  import {WebGPUCommandEncoder} from './resources/webgpu-command-encoder';
45
54
  import {WebGPUCommandBuffer} from './resources/webgpu-command-buffer';
46
55
  import {WebGPUQuerySet} from './resources/webgpu-query-set';
47
56
  import {WebGPUPipelineLayout} from './resources/webgpu-pipeline-layout';
57
+ import {WebGPUFence} from './resources/webgpu-fence';
58
+
59
+ import {getShaderLayoutFromWGSL} from '../wgsl/get-shader-layout-wgsl';
60
+ import {generateMipmapsWebGPU} from './helpers/generate-mipmaps-webgpu';
61
+ import {getBindGroup} from './helpers/get-bind-group';
62
+ import {
63
+ getCpuHotspotProfiler as getWebGPUCpuHotspotProfiler,
64
+ getCpuHotspotSubmitReason as getWebGPUCpuHotspotSubmitReason,
65
+ getTimestamp
66
+ } from './helpers/cpu-hotspot-profiler';
48
67
 
49
68
  /** WebGPU Device implementation */
50
69
  export class WebGPUDevice extends Device {
@@ -72,6 +91,7 @@ export class WebGPUDevice extends Device {
72
91
  override canvasContext: WebGPUCanvasContext | null = null;
73
92
 
74
93
  private _isLost: boolean = false;
94
+ private _defaultSampler: WebGPUSampler | null = null;
75
95
  commandEncoder: WebGPUCommandEncoder;
76
96
 
77
97
  override get [Symbol.toStringTag](): string {
@@ -108,10 +128,9 @@ export class WebGPUDevice extends Device {
108
128
  });
109
129
 
110
130
  // "Context" loss handling
111
- this.lost = new Promise<{reason: 'destroyed'; message: string}>(async resolve => {
112
- const lostInfo = await this.handle.lost;
131
+ this.lost = this.handle.lost.then(lostInfo => {
113
132
  this._isLost = true;
114
- resolve({reason: 'destroyed', message: lostInfo.message});
133
+ return {reason: 'destroyed', message: lostInfo.message};
115
134
  });
116
135
 
117
136
  // Note: WebGPU devices can be created without a canvas, for compute shader purposes
@@ -129,6 +148,9 @@ export class WebGPUDevice extends Device {
129
148
  // this.glslang = glsl && await loadGlslangModule();
130
149
 
131
150
  destroy(): void {
151
+ this.commandEncoder?.destroy();
152
+ this._defaultSampler?.destroy();
153
+ this._defaultSampler = null;
132
154
  this.handle.destroy();
133
155
  }
134
156
 
@@ -136,15 +158,15 @@ export class WebGPUDevice extends Device {
136
158
  return this._isLost;
137
159
  }
138
160
 
161
+ getShaderLayout(source: string) {
162
+ return getShaderLayoutFromWGSL(source);
163
+ }
164
+
139
165
  override isVertexFormatSupported(format: VertexFormat): boolean {
140
166
  const info = this.getVertexFormatInfo(format);
141
167
  return !info.webglOnly;
142
168
  }
143
169
 
144
- getTextureByteAlignment(): number {
145
- return 1;
146
- }
147
-
148
170
  createBuffer(props: BufferProps | ArrayBuffer | ArrayBufferView): WebGPUBuffer {
149
171
  const newProps = this._normalizeBufferProps(props);
150
172
  return new WebGPUBuffer(this, newProps);
@@ -166,6 +188,13 @@ export class WebGPUDevice extends Device {
166
188
  return new WebGPUSampler(this, props);
167
189
  }
168
190
 
191
+ getDefaultSampler(): WebGPUSampler {
192
+ this._defaultSampler ||= new WebGPUSampler(this, {
193
+ id: `${this.id}-default-sampler`
194
+ });
195
+ return this._defaultSampler;
196
+ }
197
+
169
198
  createRenderPipeline(props: RenderPipelineProps): WebGPURenderPipeline {
170
199
  return new WebGPURenderPipeline(this, props);
171
200
  }
@@ -196,41 +225,189 @@ export class WebGPUDevice extends Device {
196
225
  return new WebGPUQuerySet(this, props);
197
226
  }
198
227
 
228
+ override createFence(): WebGPUFence {
229
+ return new WebGPUFence(this);
230
+ }
231
+
199
232
  createCanvasContext(props: CanvasContextProps): WebGPUCanvasContext {
200
233
  return new WebGPUCanvasContext(this, this.adapter, props);
201
234
  }
202
235
 
236
+ createPresentationContext(props?: PresentationContextProps): PresentationContext {
237
+ return new WebGPUPresentationContext(this, props);
238
+ }
239
+
203
240
  createPipelineLayout(props: PipelineLayoutProps): WebGPUPipelineLayout {
204
241
  return new WebGPUPipelineLayout(this, props);
205
242
  }
206
243
 
244
+ override generateMipmapsWebGPU(texture: Texture): void {
245
+ generateMipmapsWebGPU(this, texture);
246
+ }
247
+
248
+ override _createBindGroupLayoutWebGPU(
249
+ pipeline: RenderPipeline | ComputePipeline,
250
+ group: number
251
+ ): GPUBindGroupLayout {
252
+ return (pipeline as WebGPURenderPipeline | WebGPUComputePipeline).handle.getBindGroupLayout(
253
+ group
254
+ );
255
+ }
256
+
257
+ override _createBindGroupWebGPU(
258
+ bindGroupLayout: unknown,
259
+ shaderLayout: ShaderLayout | ComputeShaderLayout,
260
+ bindings: Bindings,
261
+ group: number,
262
+ label?: string
263
+ ): GPUBindGroup | null {
264
+ if (Object.keys(bindings).length === 0) {
265
+ return this.handle.createBindGroup({
266
+ label,
267
+ layout: bindGroupLayout as GPUBindGroupLayout,
268
+ entries: []
269
+ });
270
+ }
271
+
272
+ return getBindGroup(
273
+ this,
274
+ bindGroupLayout as GPUBindGroupLayout,
275
+ shaderLayout,
276
+ bindings,
277
+ group,
278
+ label
279
+ );
280
+ }
281
+
207
282
  submit(commandBuffer?: WebGPUCommandBuffer): void {
283
+ let submittedCommandEncoder: WebGPUCommandEncoder | null = null;
208
284
  if (!commandBuffer) {
209
- commandBuffer = this.commandEncoder.finish();
210
- this.commandEncoder.destroy();
211
- this.commandEncoder = this.createCommandEncoder({id: `${this.id}-default-encoder`});
285
+ ({submittedCommandEncoder, commandBuffer} = this._finalizeDefaultCommandEncoderForSubmit());
212
286
  }
213
287
 
214
- this.pushErrorScope('validation');
215
- this.handle.queue.submit([commandBuffer.handle]);
216
- this.popErrorScope((error: GPUError) => {
217
- this.reportError(new Error(`${this} command submission: ${error.message}`), this)();
218
- this.debug();
288
+ const profiler = getWebGPUCpuHotspotProfiler(this);
289
+ const startTime = profiler ? getTimestamp() : 0;
290
+ const submitReason = getWebGPUCpuHotspotSubmitReason(this);
291
+ try {
292
+ this.pushErrorScope('validation');
293
+ const queueSubmitStartTime = profiler ? getTimestamp() : 0;
294
+ this.handle.queue.submit([commandBuffer.handle]);
295
+ if (profiler) {
296
+ profiler.queueSubmitCount = (profiler.queueSubmitCount || 0) + 1;
297
+ profiler.queueSubmitTimeMs =
298
+ (profiler.queueSubmitTimeMs || 0) + (getTimestamp() - queueSubmitStartTime);
299
+ }
300
+ this.popErrorScope((error: GPUError) => {
301
+ this.reportError(new Error(`${this} command submission: ${error.message}`), this)();
302
+ this.debug();
303
+ });
304
+
305
+ if (submittedCommandEncoder) {
306
+ const submitResolveKickoffStartTime = profiler ? getTimestamp() : 0;
307
+ scheduleMicrotask(() => {
308
+ submittedCommandEncoder
309
+ .resolveTimeProfilingQuerySet()
310
+ .then(() => {
311
+ this.commandEncoder._gpuTimeMs = submittedCommandEncoder._gpuTimeMs;
312
+ })
313
+ .catch(() => {});
314
+ });
315
+ if (profiler) {
316
+ profiler.submitResolveKickoffCount = (profiler.submitResolveKickoffCount || 0) + 1;
317
+ profiler.submitResolveKickoffTimeMs =
318
+ (profiler.submitResolveKickoffTimeMs || 0) +
319
+ (getTimestamp() - submitResolveKickoffStartTime);
320
+ }
321
+ }
322
+ } finally {
323
+ if (profiler) {
324
+ profiler.submitCount = (profiler.submitCount || 0) + 1;
325
+ profiler.submitTimeMs = (profiler.submitTimeMs || 0) + (getTimestamp() - startTime);
326
+ const reasonCountKey =
327
+ submitReason === 'query-readback' ? 'queryReadbackSubmitCount' : 'defaultSubmitCount';
328
+ const reasonTimeKey =
329
+ submitReason === 'query-readback' ? 'queryReadbackSubmitTimeMs' : 'defaultSubmitTimeMs';
330
+ profiler[reasonCountKey] = (profiler[reasonCountKey] || 0) + 1;
331
+ profiler[reasonTimeKey] = (profiler[reasonTimeKey] || 0) + (getTimestamp() - startTime);
332
+ }
333
+ const commandBufferDestroyStartTime = profiler ? getTimestamp() : 0;
334
+ commandBuffer.destroy();
335
+ if (profiler) {
336
+ profiler.commandBufferDestroyCount = (profiler.commandBufferDestroyCount || 0) + 1;
337
+ profiler.commandBufferDestroyTimeMs =
338
+ (profiler.commandBufferDestroyTimeMs || 0) +
339
+ (getTimestamp() - commandBufferDestroyStartTime);
340
+ }
341
+ }
342
+ }
343
+
344
+ private _finalizeDefaultCommandEncoderForSubmit(): {
345
+ submittedCommandEncoder: WebGPUCommandEncoder;
346
+ commandBuffer: WebGPUCommandBuffer;
347
+ } {
348
+ const submittedCommandEncoder = this.commandEncoder;
349
+ if (
350
+ submittedCommandEncoder.getTimeProfilingSlotCount() > 0 &&
351
+ submittedCommandEncoder.getTimeProfilingQuerySet() instanceof WebGPUQuerySet
352
+ ) {
353
+ const querySet = submittedCommandEncoder.getTimeProfilingQuerySet() as WebGPUQuerySet;
354
+ querySet._encodeResolveToReadBuffer(submittedCommandEncoder, {
355
+ firstQuery: 0,
356
+ queryCount: submittedCommandEncoder.getTimeProfilingSlotCount()
357
+ });
358
+ }
359
+
360
+ const commandBuffer = submittedCommandEncoder.finish();
361
+ this.commandEncoder.destroy();
362
+ this.commandEncoder = this.createCommandEncoder({
363
+ id: submittedCommandEncoder.props.id,
364
+ timeProfilingQuerySet: submittedCommandEncoder.getTimeProfilingQuerySet()
219
365
  });
366
+
367
+ return {submittedCommandEncoder, commandBuffer};
220
368
  }
221
369
 
222
370
  // WebGPU specific
223
371
 
224
372
  pushErrorScope(scope: 'validation' | 'out-of-memory'): void {
373
+ if (!this.props.debug) {
374
+ return;
375
+ }
376
+ const profiler = getWebGPUCpuHotspotProfiler(this);
377
+ const startTime = profiler ? getTimestamp() : 0;
225
378
  this.handle.pushErrorScope(scope);
379
+ if (profiler) {
380
+ profiler.errorScopePushCount = (profiler.errorScopePushCount || 0) + 1;
381
+ profiler.errorScopeTimeMs = (profiler.errorScopeTimeMs || 0) + (getTimestamp() - startTime);
382
+ }
226
383
  }
227
384
 
228
385
  popErrorScope(handler: (error: GPUError) => void): void {
229
- this.handle.popErrorScope().then((error: GPUError | null) => {
230
- if (error) {
231
- handler(error);
232
- }
233
- });
386
+ if (!this.props.debug) {
387
+ return;
388
+ }
389
+ const profiler = getWebGPUCpuHotspotProfiler(this);
390
+ const startTime = profiler ? getTimestamp() : 0;
391
+ this.handle
392
+ .popErrorScope()
393
+ .then((error: GPUError | null) => {
394
+ if (error) {
395
+ handler(error);
396
+ }
397
+ })
398
+ .catch((error: unknown) => {
399
+ if (this.shouldIgnoreDroppedInstanceError(error, 'popErrorScope')) {
400
+ return;
401
+ }
402
+
403
+ const errorMessage = error instanceof Error ? error.message : String(error);
404
+ this.reportError(new Error(`${this} popErrorScope failed: ${errorMessage}`), this)();
405
+ this.debug();
406
+ });
407
+ if (profiler) {
408
+ profiler.errorScopePopCount = (profiler.errorScopePopCount || 0) + 1;
409
+ profiler.errorScopeTimeMs = (profiler.errorScopeTimeMs || 0) + (getTimestamp() - startTime);
410
+ }
234
411
  }
235
412
 
236
413
  // PRIVATE METHODS
@@ -242,11 +419,22 @@ export class WebGPUDevice extends Device {
242
419
  const vendor = this.adapterInfo.vendor || this.adapter.__brand || 'unknown';
243
420
  const renderer = driver || '';
244
421
  const version = driverVersion || '';
245
-
246
- const gpu = vendor === 'apple' ? 'apple' : 'unknown'; // 'nvidia' | 'amd' | 'intel' | 'apple' | 'unknown',
422
+ const fallback = Boolean(
423
+ (this.adapterInfo as any).isFallbackAdapter ??
424
+ (this.adapter as any).isFallbackAdapter ??
425
+ false
426
+ );
427
+ const softwareRenderer = /SwiftShader/i.test(
428
+ `${vendor} ${renderer} ${this.adapterInfo.architecture || ''}`
429
+ );
430
+
431
+ const gpu =
432
+ vendor === 'apple' ? 'apple' : softwareRenderer || fallback ? 'software' : 'unknown'; // 'nvidia' | 'amd' | 'intel' | 'apple' | 'unknown',
247
433
  const gpuArchitecture = this.adapterInfo.architecture || 'unknown';
248
434
  const gpuBackend = (this.adapterInfo as any).backend || 'unknown';
249
- const gpuType = ((this.adapterInfo as any).type || '').split(' ')[0].toLowerCase() || 'unknown';
435
+ const gpuType =
436
+ ((this.adapterInfo as any).type || '').split(' ')[0].toLowerCase() ||
437
+ (softwareRenderer || fallback ? 'cpu' : 'unknown');
250
438
 
251
439
  return {
252
440
  type: 'webgpu',
@@ -257,11 +445,24 @@ export class WebGPUDevice extends Device {
257
445
  gpuType,
258
446
  gpuBackend,
259
447
  gpuArchitecture,
448
+ fallback,
260
449
  shadingLanguage: 'wgsl',
261
450
  shadingLanguageVersion: 100
262
451
  };
263
452
  }
264
453
 
454
+ shouldIgnoreDroppedInstanceError(error: unknown, operation?: string): boolean {
455
+ const errorMessage = error instanceof Error ? error.message : String(error);
456
+ return (
457
+ errorMessage.includes('Instance dropped') &&
458
+ (!operation || errorMessage.includes(operation)) &&
459
+ (this._isLost ||
460
+ this.info.gpu === 'software' ||
461
+ this.info.gpuType === 'cpu' ||
462
+ Boolean(this.info.fallback))
463
+ );
464
+ }
465
+
265
466
  protected _getFeatures(): DeviceFeatures {
266
467
  // Initialize with actual WebGPU Features (note that unknown features may not be in DeviceFeature type)
267
468
  const features = new Set<DeviceFeature>(this.handle.features as Set<DeviceFeature>);
@@ -278,8 +479,15 @@ export class WebGPUDevice extends Device {
278
479
  features.add('texture-compression-bc5-webgl');
279
480
  }
280
481
 
482
+ if (this.handle.features.has('chromium-experimental-norm16-texture-formats')) {
483
+ features.add('norm16-renderable-webgl');
484
+ }
485
+
486
+ if (this.handle.features.has('chromium-experimental-snorm16-texture-formats')) {
487
+ features.add('snorm16-renderable-webgl');
488
+ }
489
+
281
490
  const WEBGPU_ALWAYS_FEATURES: DeviceFeature[] = [
282
- 'timer-query-webgl',
283
491
  'compilation-status-async-webgl',
284
492
  'float32-renderable-webgl',
285
493
  'float16-renderable-webgl',
@@ -305,3 +513,13 @@ export class WebGPUDevice extends Device {
305
513
  return capabilities;
306
514
  }
307
515
  }
516
+
517
+ function scheduleMicrotask(callback: () => void): void {
518
+ if (globalThis.queueMicrotask) {
519
+ globalThis.queueMicrotask(callback);
520
+ return;
521
+ }
522
+ Promise.resolve()
523
+ .then(callback)
524
+ .catch(() => {});
525
+ }