@luma.gl/webgpu 9.3.0-alpha.8 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luma.gl/webgpu",
3
- "version": "9.3.0-alpha.8",
3
+ "version": "9.3.0",
4
4
  "description": "WebGPU adapter for the luma.gl core API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,5 +44,5 @@
44
44
  "@webgpu/types": "^0.1.69",
45
45
  "wgsl_reflect": "^1.2.3"
46
46
  },
47
- "gitHead": "371f0979c1cd01563badc36118f314365a66e1a1"
47
+ "gitHead": "c5e44faacd07b315a8622b5927e724f15a3d40ae"
48
48
  }
@@ -2,7 +2,13 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {Binding, Bindings, ComputeShaderLayout, ShaderLayout} from '@luma.gl/core';
5
+ import type {
6
+ Binding,
7
+ BindingDeclaration,
8
+ Bindings,
9
+ ComputeShaderLayout,
10
+ ShaderLayout
11
+ } from '@luma.gl/core';
6
12
  import {Buffer, Sampler, Texture, TextureView, getShaderLayoutBinding, log} from '@luma.gl/core';
7
13
  import type {WebGPUDevice} from '../webgpu-device';
8
14
  import type {WebGPUBuffer} from '../resources/webgpu-buffer';
@@ -10,6 +16,13 @@ import type {WebGPUSampler} from '../resources/webgpu-sampler';
10
16
  import type {WebGPUTexture} from '../resources/webgpu-texture';
11
17
  import type {WebGPUTextureView} from '../resources/webgpu-texture-view';
12
18
 
19
+ type AnyShaderLayout = ShaderLayout | ComputeShaderLayout;
20
+ type BindGroupBindingSummary = {
21
+ name: string;
22
+ location: number;
23
+ type: string;
24
+ };
25
+
13
26
  /**
14
27
  * Create a WebGPU "bind group layout" from an array of luma.gl bindings
15
28
  * @note bind groups can be automatically generated by WebGPU.
@@ -34,7 +47,8 @@ export function getBindGroup(
34
47
  bindGroupLayout: GPUBindGroupLayout,
35
48
  shaderLayout: ShaderLayout | ComputeShaderLayout,
36
49
  bindings: Bindings,
37
- group: number
50
+ group: number,
51
+ label?: string
38
52
  ): GPUBindGroup | null {
39
53
  const entries = getBindGroupEntries(bindings, shaderLayout, group);
40
54
  if (entries.length === 0) {
@@ -42,35 +56,93 @@ export function getBindGroup(
42
56
  }
43
57
  device.pushErrorScope('validation');
44
58
  const bindGroup = device.handle.createBindGroup({
59
+ label,
45
60
  layout: bindGroupLayout,
46
61
  entries
47
62
  });
48
63
  device.popErrorScope((error: GPUError) => {
49
- log.error(`bindGroup creation: ${error.message}`, bindGroup)();
64
+ const summary = formatBindGroupCreationErrorSummary(shaderLayout, bindings, entries, group);
65
+ log.error(`bindGroup creation: ${summary}\nRaw WebGPU error: ${error.message}`, bindGroup)();
50
66
  });
51
67
  return bindGroup;
52
68
  }
53
69
 
70
+ export function formatBindGroupCreationErrorSummary(
71
+ shaderLayout: AnyShaderLayout,
72
+ bindings: Bindings,
73
+ entries: GPUBindGroupEntry[],
74
+ group: number
75
+ ): string {
76
+ const expectedBindings = getExpectedBindingsForGroup(shaderLayout, group);
77
+ const expectedByLocation = new Map(
78
+ expectedBindings.map(bindingSummary => [bindingSummary.location, bindingSummary])
79
+ );
80
+ const providedBindings = entries
81
+ .map(entry => expectedByLocation.get(entry.binding) || getUnexpectedEntrySummary(entry))
82
+ .sort(compareBindingSummaries);
83
+ const missingBindings = expectedBindings.filter(
84
+ bindingSummary =>
85
+ !providedBindings.some(provided => provided.location === bindingSummary.location)
86
+ );
87
+ const unexpectedBindings = providedBindings.filter(
88
+ bindingSummary => !expectedByLocation.has(bindingSummary.location)
89
+ );
90
+ const unmatchedLogicalBindings = Object.keys(bindings)
91
+ .filter(bindingName => !resolveGroupBinding(bindingName, bindings, shaderLayout, group))
92
+ .sort();
93
+
94
+ const lines = [
95
+ `bindGroup creation failed for group ${group}: expected ${expectedBindings.length}, provided ${providedBindings.length}`,
96
+ `expected: ${formatBindingSummaryList(expectedBindings)}`,
97
+ `provided: ${formatBindingSummaryList(providedBindings)}`
98
+ ];
99
+
100
+ if (missingBindings.length > 0) {
101
+ lines.push(`missing: ${formatBindingSummaryList(missingBindings)}`);
102
+ }
103
+ if (unexpectedBindings.length > 0) {
104
+ lines.push(`unexpected entries: ${formatBindingSummaryList(unexpectedBindings)}`);
105
+ }
106
+ if (unmatchedLogicalBindings.length > 0) {
107
+ lines.push(`unmatched logical bindings: ${unmatchedLogicalBindings.join(', ')}`);
108
+ }
109
+
110
+ return lines.join('\n');
111
+ }
112
+
113
+ export function getBindGroupLabel(
114
+ pipelineId: string,
115
+ shaderLayout: AnyShaderLayout,
116
+ group: number
117
+ ): string {
118
+ const expectedBindings = getExpectedBindingsForGroup(shaderLayout, group);
119
+ const bindingSuffix =
120
+ expectedBindings.length > 0 ? expectedBindings.map(binding => binding.name).join(',') : 'empty';
121
+ return `${pipelineId}/group${group}[${bindingSuffix}]`;
122
+ }
123
+
54
124
  /**
55
125
  * @param bindings
56
126
  * @returns
57
127
  */
58
128
  function getBindGroupEntries(
59
129
  bindings: Bindings,
60
- shaderLayout: ShaderLayout | ComputeShaderLayout,
130
+ shaderLayout: AnyShaderLayout,
61
131
  group: number
62
132
  ): GPUBindGroupEntry[] {
63
133
  const entries: GPUBindGroupEntry[] = [];
64
134
 
65
135
  for (const [bindingName, value] of Object.entries(bindings)) {
66
- const exactBindingLayout = shaderLayout.bindings.find(binding => binding.name === bindingName);
67
- const bindingLayout = exactBindingLayout || getShaderLayoutBinding(shaderLayout, bindingName);
68
- const isShadowedAlias =
69
- !exactBindingLayout && bindingLayout ? bindingLayout.name in bindings : false;
136
+ const {bindingLayout, isShadowedAlias} = resolveGroupBinding(
137
+ bindingName,
138
+ bindings,
139
+ shaderLayout,
140
+ group
141
+ ) || {bindingLayout: null, isShadowedAlias: false};
70
142
 
71
143
  // Mirror the WebGL path: when both `foo` and `fooUniforms` exist in the bindings map,
72
144
  // prefer the exact shader binding name and ignore the alias entry.
73
- if (!isShadowedAlias && bindingLayout?.group === group) {
145
+ if (!isShadowedAlias && bindingLayout) {
74
146
  const entry = bindingLayout
75
147
  ? getBindGroupEntry(value, bindingLayout.location, undefined, bindingName)
76
148
  : null;
@@ -139,3 +211,65 @@ function getBindGroupEntry(
139
211
  log.warn(`invalid binding ${bindingName}`, binding);
140
212
  return null;
141
213
  }
214
+
215
+ function getExpectedBindingsForGroup(
216
+ shaderLayout: AnyShaderLayout,
217
+ group: number
218
+ ): BindGroupBindingSummary[] {
219
+ return shaderLayout.bindings
220
+ .filter(bindingLayout => bindingLayout.group === group)
221
+ .map(bindingLayout => toBindingSummary(bindingLayout))
222
+ .sort(compareBindingSummaries);
223
+ }
224
+
225
+ function resolveGroupBinding(
226
+ bindingName: string,
227
+ bindings: Bindings,
228
+ shaderLayout: AnyShaderLayout,
229
+ group: number
230
+ ): {bindingLayout: BindingDeclaration; isShadowedAlias: boolean} | null {
231
+ const exactBindingLayout = shaderLayout.bindings.find(binding => binding.name === bindingName);
232
+ const bindingLayout =
233
+ exactBindingLayout || getShaderLayoutBinding(shaderLayout, bindingName, {ignoreWarnings: true});
234
+ const isShadowedAlias =
235
+ !exactBindingLayout && bindingLayout ? bindingLayout.name in bindings : false;
236
+
237
+ if (isShadowedAlias || !bindingLayout || bindingLayout.group !== group) {
238
+ return null;
239
+ }
240
+
241
+ return {bindingLayout, isShadowedAlias};
242
+ }
243
+
244
+ function toBindingSummary(bindingLayout: BindingDeclaration): BindGroupBindingSummary {
245
+ return {
246
+ name: bindingLayout.name,
247
+ location: bindingLayout.location,
248
+ type: bindingLayout.type
249
+ };
250
+ }
251
+
252
+ function getUnexpectedEntrySummary(entry: GPUBindGroupEntry): BindGroupBindingSummary {
253
+ return {
254
+ name: '?',
255
+ location: entry.binding,
256
+ type: 'unknown'
257
+ };
258
+ }
259
+
260
+ function compareBindingSummaries(
261
+ left: BindGroupBindingSummary,
262
+ right: BindGroupBindingSummary
263
+ ): number {
264
+ if (left.location !== right.location) {
265
+ return left.location - right.location;
266
+ }
267
+ return left.name.localeCompare(right.name);
268
+ }
269
+
270
+ function formatBindingSummaryList(bindings: BindGroupBindingSummary[]): string {
271
+ if (bindings.length === 0) {
272
+ return 'none';
273
+ }
274
+ return bindings.map(binding => `${binding.name}@${binding.location}`).join(', ');
275
+ }
@@ -56,12 +56,14 @@ export class WebGPURenderPipeline extends RenderPipeline {
56
56
  this.device.pushErrorScope('validation');
57
57
  this.handle = this.device.handle.createRenderPipeline(descriptor);
58
58
  this.device.popErrorScope((error: GPUError) => {
59
+ this.linkStatus = 'error';
59
60
  this.device.reportError(new Error(`${this} creation failed:\n"${error.message}"`), this)();
60
61
  this.device.debug();
61
62
  });
62
63
  }
63
64
  this.descriptor = descriptor;
64
65
  this.handle.label = this.props.id;
66
+ this.linkStatus = 'success';
65
67
 
66
68
  // Note: Often the same shader in WebGPU
67
69
  this.vs = props.vs as WebGPUShader;
@@ -117,6 +119,11 @@ export class WebGPURenderPipeline extends RenderPipeline {
117
119
  _bindGroupCacheKeys?: Partial<Record<number, object>>;
118
120
  uniforms?: Record<string, unknown>;
119
121
  }): boolean {
122
+ if (this.isErrored) {
123
+ log.info(2, `RenderPipeline:${this.id}.draw() aborted - pipeline initialization failed`)();
124
+ return false;
125
+ }
126
+
120
127
  const webgpuRenderPass = options.renderPass as WebGPURenderPass;
121
128
  const instanceCount =
122
129
  options.instanceCount && options.instanceCount > 0 ? options.instanceCount : 1;
@@ -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 {Adapter, DeviceProps, log} from '@luma.gl/core';
@@ -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';
@@ -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 {
@@ -128,10 +128,9 @@ export class WebGPUDevice extends Device {
128
128
  });
129
129
 
130
130
  // "Context" loss handling
131
- this.lost = new Promise<{reason: 'destroyed'; message: string}>(async resolve => {
132
- const lostInfo = await this.handle.lost;
131
+ this.lost = this.handle.lost.then(lostInfo => {
133
132
  this._isLost = true;
134
- resolve({reason: 'destroyed', message: lostInfo.message});
133
+ return {reason: 'destroyed', message: lostInfo.message};
135
134
  });
136
135
 
137
136
  // Note: WebGPU devices can be created without a canvas, for compute shader purposes
@@ -259,16 +258,25 @@ export class WebGPUDevice extends Device {
259
258
  bindGroupLayout: unknown,
260
259
  shaderLayout: ShaderLayout | ComputeShaderLayout,
261
260
  bindings: Bindings,
262
- group: number
261
+ group: number,
262
+ label?: string
263
263
  ): GPUBindGroup | null {
264
264
  if (Object.keys(bindings).length === 0) {
265
265
  return this.handle.createBindGroup({
266
+ label,
266
267
  layout: bindGroupLayout as GPUBindGroupLayout,
267
268
  entries: []
268
269
  });
269
270
  }
270
271
 
271
- return getBindGroup(this, bindGroupLayout as GPUBindGroupLayout, shaderLayout, bindings, group);
272
+ return getBindGroup(
273
+ this,
274
+ bindGroupLayout as GPUBindGroupLayout,
275
+ shaderLayout,
276
+ bindings,
277
+ group,
278
+ label
279
+ );
272
280
  }
273
281
 
274
282
  submit(commandBuffer?: WebGPUCommandBuffer): void {
@@ -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 {PresentationContextProps, TextureFormatDepthStencil} from '@luma.gl/core';