@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,11 +2,26 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {ComputeShaderLayout, BindingDeclaration, Binding} from '@luma.gl/core';
6
- import {Buffer, Sampler, Texture, log} from '@luma.gl/core';
5
+ import type {
6
+ Binding,
7
+ BindingDeclaration,
8
+ Bindings,
9
+ ComputeShaderLayout,
10
+ ShaderLayout
11
+ } from '@luma.gl/core';
12
+ import {Buffer, Sampler, Texture, TextureView, getShaderLayoutBinding, log} from '@luma.gl/core';
13
+ import type {WebGPUDevice} from '../webgpu-device';
7
14
  import type {WebGPUBuffer} from '../resources/webgpu-buffer';
8
15
  import type {WebGPUSampler} from '../resources/webgpu-sampler';
9
16
  import type {WebGPUTexture} from '../resources/webgpu-texture';
17
+ import type {WebGPUTextureView} from '../resources/webgpu-texture-view';
18
+
19
+ type AnyShaderLayout = ShaderLayout | ComputeShaderLayout;
20
+ type BindGroupBindingSummary = {
21
+ name: string;
22
+ location: number;
23
+ type: string;
24
+ };
10
25
 
11
26
  /**
12
27
  * Create a WebGPU "bind group layout" from an array of luma.gl bindings
@@ -15,7 +30,7 @@ import type {WebGPUTexture} from '../resources/webgpu-texture';
15
30
  export function makeBindGroupLayout(
16
31
  device: GPUDevice,
17
32
  layout: GPUBindGroupLayout,
18
- bindings: Binding[]
33
+ bindings: Bindings
19
34
  ): GPUBindGroupLayout {
20
35
  throw new Error('not implemented');
21
36
  // return device.createBindGroupLayout({
@@ -28,39 +43,82 @@ export function makeBindGroupLayout(
28
43
  * Create a WebGPU "bind group" from an array of luma.gl bindings
29
44
  */
30
45
  export function getBindGroup(
31
- device: GPUDevice,
46
+ device: WebGPUDevice,
32
47
  bindGroupLayout: GPUBindGroupLayout,
33
- shaderLayout: ComputeShaderLayout,
34
- bindings: Record<string, Binding>
35
- ): GPUBindGroup {
36
- const entries = getBindGroupEntries(bindings, shaderLayout);
48
+ shaderLayout: ShaderLayout | ComputeShaderLayout,
49
+ bindings: Bindings,
50
+ group: number,
51
+ label?: string
52
+ ): GPUBindGroup | null {
53
+ const entries = getBindGroupEntries(bindings, shaderLayout, group);
54
+ if (entries.length === 0) {
55
+ return null;
56
+ }
37
57
  device.pushErrorScope('validation');
38
- const bindGroup = device.createBindGroup({
58
+ const bindGroup = device.handle.createBindGroup({
59
+ label,
39
60
  layout: bindGroupLayout,
40
61
  entries
41
62
  });
42
- device.popErrorScope().then((error: GPUError | null) => {
43
- if (error) {
44
- log.error(`bindGroup creation: ${error.message}`, bindGroup)();
45
- }
63
+ device.popErrorScope((error: GPUError) => {
64
+ const summary = formatBindGroupCreationErrorSummary(shaderLayout, bindings, entries, group);
65
+ log.error(`bindGroup creation: ${summary}\nRaw WebGPU error: ${error.message}`, bindGroup)();
46
66
  });
47
67
  return bindGroup;
48
68
  }
49
69
 
50
- export function getShaderLayoutBinding(
51
- shaderLayout: ComputeShaderLayout,
52
- bindingName: string,
53
- options?: {ignoreWarnings?: boolean}
54
- ): BindingDeclaration | null {
55
- const bindingLayout = shaderLayout.bindings.find(
56
- binding =>
57
- binding.name === bindingName ||
58
- `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
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])
59
79
  );
60
- if (!bindingLayout && !options?.ignoreWarnings) {
61
- log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
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)}`);
62
105
  }
63
- return bindingLayout || null;
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}]`;
64
122
  }
65
123
 
66
124
  /**
@@ -68,29 +126,42 @@ export function getShaderLayoutBinding(
68
126
  * @returns
69
127
  */
70
128
  function getBindGroupEntries(
71
- bindings: Record<string, Binding>,
72
- shaderLayout: ComputeShaderLayout
129
+ bindings: Bindings,
130
+ shaderLayout: AnyShaderLayout,
131
+ group: number
73
132
  ): GPUBindGroupEntry[] {
74
133
  const entries: GPUBindGroupEntry[] = [];
75
134
 
76
135
  for (const [bindingName, value] of Object.entries(bindings)) {
77
- let bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
78
- if (bindingLayout) {
79
- const entry = getBindGroupEntry(value, bindingLayout.location);
136
+ const {bindingLayout, isShadowedAlias} = resolveGroupBinding(
137
+ bindingName,
138
+ bindings,
139
+ shaderLayout,
140
+ group
141
+ ) || {bindingLayout: null, isShadowedAlias: false};
142
+
143
+ // Mirror the WebGL path: when both `foo` and `fooUniforms` exist in the bindings map,
144
+ // prefer the exact shader binding name and ignore the alias entry.
145
+ if (!isShadowedAlias && bindingLayout) {
146
+ const entry = bindingLayout
147
+ ? getBindGroupEntry(value, bindingLayout.location, undefined, bindingName)
148
+ : null;
80
149
  if (entry) {
81
150
  entries.push(entry);
82
151
  }
83
- }
84
152
 
85
- // TODO - hack to automatically bind samplers to supplied texture default samplers
86
- if (value instanceof Texture) {
87
- bindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
88
- ignoreWarnings: true
89
- });
90
- if (bindingLayout) {
91
- const entry = getBindGroupEntry(value, bindingLayout.location, {sampler: true});
92
- if (entry) {
93
- entries.push(entry);
153
+ // TODO - hack to automatically bind samplers to supplied texture default samplers
154
+ if (value instanceof Texture) {
155
+ const samplerBindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
156
+ ignoreWarnings: true
157
+ });
158
+ const samplerEntry = samplerBindingLayout
159
+ ? samplerBindingLayout.group === group
160
+ ? getBindGroupEntry(value, samplerBindingLayout.location, {sampler: true}, bindingName)
161
+ : null
162
+ : null;
163
+ if (samplerEntry) {
164
+ entries.push(samplerEntry);
94
165
  }
95
166
  }
96
167
  }
@@ -102,7 +173,8 @@ function getBindGroupEntries(
102
173
  function getBindGroupEntry(
103
174
  binding: Binding,
104
175
  index: number,
105
- options?: {sampler?: boolean}
176
+ options?: {sampler?: boolean},
177
+ bindingName: string = 'unknown'
106
178
  ): GPUBindGroupEntry | null {
107
179
  if (binding instanceof Buffer) {
108
180
  return {
@@ -118,6 +190,12 @@ function getBindGroupEntry(
118
190
  resource: (binding as WebGPUSampler).handle
119
191
  };
120
192
  }
193
+ if (binding instanceof TextureView) {
194
+ return {
195
+ binding: index,
196
+ resource: (binding as WebGPUTextureView).handle
197
+ };
198
+ }
121
199
  if (binding instanceof Texture) {
122
200
  if (options?.sampler) {
123
201
  return {
@@ -130,6 +208,68 @@ function getBindGroupEntry(
130
208
  resource: (binding as WebGPUTexture).view.handle
131
209
  };
132
210
  }
133
- log.warn(`invalid binding ${name}`, binding);
211
+ log.warn(`invalid binding ${bindingName}`, binding);
134
212
  return null;
135
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
+ }
@@ -3,7 +3,7 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {ShaderLayout, BufferLayout, AttributeDeclaration, VertexFormat} from '@luma.gl/core';
6
- import {log, getVertexFormatInfo} from '@luma.gl/core';
6
+ import {log, vertexFormatDecoder} from '@luma.gl/core';
7
7
  // import {getAttributeInfosFromLayouts} from '@luma.gl/core';
8
8
 
9
9
  /** Throw error on any WebGL-only vertex formats */
@@ -23,10 +23,12 @@ function getWebGPUVertexFormat(format: VertexFormat): GPUVertexFormat {
23
23
  */
24
24
  export function getVertexBufferLayout(
25
25
  shaderLayout: ShaderLayout,
26
- bufferLayout: BufferLayout[]
26
+ bufferLayout: BufferLayout[],
27
+ options?: {pipelineId?: string}
27
28
  ): GPUVertexBufferLayout[] {
28
29
  const vertexBufferLayouts: GPUVertexBufferLayout[] = [];
29
30
  const usedAttributes = new Set<string>();
31
+ const shaderAttributes = shaderLayout.attributes || [];
30
32
 
31
33
  // First handle any buffers mentioned in `bufferLayout`
32
34
  for (const mapping of bufferLayout) {
@@ -44,7 +46,12 @@ export function getVertexBufferLayout(
44
46
  // const arrayStride = mapping.byteStride; TODO
45
47
  for (const attributeMapping of mapping.attributes) {
46
48
  const attributeName = attributeMapping.attribute;
47
- const attributeLayout = findAttributeLayout(shaderLayout, attributeName, usedAttributes);
49
+ const attributeLayout = findAttributeLayout(
50
+ shaderLayout,
51
+ attributeName,
52
+ usedAttributes,
53
+ options
54
+ );
48
55
 
49
56
  // @ts-ignore
50
57
  const location: number = attributeLayout?.location;
@@ -59,15 +66,20 @@ export function getVertexBufferLayout(
59
66
  shaderLocation: location
60
67
  });
61
68
 
62
- byteStride += getVertexFormatInfo(format).byteLength;
69
+ byteStride += vertexFormatDecoder.getVertexFormatInfo(format).byteLength;
63
70
  }
64
71
  // non-interleaved mapping (just set offset and stride)
65
72
  } else {
66
- const attributeLayout = findAttributeLayout(shaderLayout, mapping.name, usedAttributes);
73
+ const attributeLayout = findAttributeLayout(
74
+ shaderLayout,
75
+ mapping.name,
76
+ usedAttributes,
77
+ options
78
+ );
67
79
  if (!attributeLayout) {
68
80
  continue; // eslint-disable-line no-continue
69
81
  }
70
- byteStride = getVertexFormatInfo(format).byteLength;
82
+ byteStride = vertexFormatDecoder.getVertexFormatInfo(format).byteLength;
71
83
 
72
84
  stepMode =
73
85
  attributeLayout.stepMode ||
@@ -89,10 +101,10 @@ export function getVertexBufferLayout(
89
101
  }
90
102
 
91
103
  // Add any non-mapped attributes - TODO - avoid hardcoded types
92
- for (const attribute of shaderLayout.attributes) {
104
+ for (const attribute of shaderAttributes) {
93
105
  if (!usedAttributes.has(attribute.name)) {
94
106
  vertexBufferLayouts.push({
95
- arrayStride: getVertexFormatInfo('float32x3').byteLength,
107
+ arrayStride: vertexFormatDecoder.getVertexFormatInfo('float32x3').byteLength,
96
108
  stepMode:
97
109
  attribute.stepMode || (attribute.name.startsWith('instance') ? 'instance' : 'vertex'),
98
110
  attributes: [
@@ -124,6 +136,7 @@ export function getBufferSlots(
124
136
  bufferLayout: BufferLayout[]
125
137
  ): Record<string, number> {
126
138
  const usedAttributes = new Set<string>();
139
+ const shaderAttributes = shaderLayout.attributes || [];
127
140
  let bufferSlot = 0;
128
141
  const bufferSlots: Record<string, number> = {};
129
142
 
@@ -142,7 +155,7 @@ export function getBufferSlots(
142
155
  }
143
156
 
144
157
  // Add any non-mapped attributes
145
- for (const attribute of shaderLayout.attributes) {
158
+ for (const attribute of shaderAttributes) {
146
159
  if (!usedAttributes.has(attribute.name)) {
147
160
  bufferSlots[attribute.name] = bufferSlot++;
148
161
  }
@@ -159,11 +172,17 @@ export function getBufferSlots(
159
172
  function findAttributeLayout(
160
173
  shaderLayout: ShaderLayout,
161
174
  name: string,
162
- attributeNames?: Set<string>
175
+ attributeNames?: Set<string>,
176
+ options?: {pipelineId?: string}
163
177
  ): AttributeDeclaration | null {
164
- const attribute = shaderLayout.attributes.find(attribute_ => attribute_.name === name);
178
+ const attribute = shaderLayout.attributes?.find(attribute_ => attribute_.name === name);
165
179
  if (!attribute) {
166
- log.warn(`Supplied attribute not present in shader layout: ${name}`)();
180
+ const pipelineContext = options?.pipelineId
181
+ ? `RenderPipeline(${options.pipelineId})`
182
+ : 'RenderPipeline';
183
+ log.warn(
184
+ `${pipelineContext}: Ignoring "${name}" attribute, since it is not present in shader layout.`
185
+ )();
167
186
  return null;
168
187
  }
169
188
  if (attributeNames) {
@@ -65,6 +65,8 @@ export const PARAMETER_TABLE: Record<keyof Parameters, Function> = {
65
65
  depthStencil.format = value;
66
66
  },
67
67
 
68
+ clearDepth: notSupported,
69
+
68
70
  depthBias: (_: keyof Parameters, value: any, descriptor: GPURenderPipelineDescriptor) => {
69
71
  const depthStencil = addDepthStencil(descriptor);
70
72
  depthStencil.depthBias = value;
@@ -5,20 +5,28 @@
5
5
  import {log, Buffer, type BufferProps, type BufferMapCallback} from '@luma.gl/core';
6
6
  import {type WebGPUDevice} from '../webgpu-device';
7
7
 
8
+ /**
9
+ * WebGPU implementation of Buffer
10
+ * For byte alignment requirements see:
11
+ * @see https://www.w3.org/TR/webgpu/#dom-gpubuffer-mapasync
12
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer/mapAsync
13
+ */
8
14
  export class WebGPUBuffer extends Buffer {
9
15
  readonly device: WebGPUDevice;
10
16
  readonly handle: GPUBuffer;
11
17
  readonly byteLength: number;
18
+ readonly paddedByteLength: number;
12
19
 
13
20
  constructor(device: WebGPUDevice, props: BufferProps) {
14
21
  super(device, props);
15
22
  this.device = device;
16
23
 
17
24
  this.byteLength = props.byteLength || props.data?.byteLength || 0;
25
+ this.paddedByteLength = Math.ceil(this.byteLength / 4) * 4;
18
26
  const mappedAtCreation = Boolean(this.props.onMapped || props.data);
19
27
 
20
28
  // WebGPU buffers must be aligned to 4 bytes
21
- const size = Math.ceil(this.byteLength / 4) * 4;
29
+ const size = this.paddedByteLength;
22
30
 
23
31
  this.device.pushErrorScope('out-of-memory');
24
32
  this.device.pushErrorScope('validation');
@@ -59,12 +67,27 @@ export class WebGPUBuffer extends Buffer {
59
67
  this.device.reportError(new Error(`${this} creation failed ${error.message}`), this)();
60
68
  this.device.debug();
61
69
  });
70
+
71
+ if (!this.props.handle) {
72
+ this.trackAllocatedMemory(size);
73
+ } else {
74
+ this.trackReferencedMemory(size, 'Buffer');
75
+ }
62
76
  }
63
77
 
64
78
  override destroy(): void {
65
- this.handle?.destroy();
66
- // @ts-expect-error readonly
67
- this.handle = null;
79
+ if (!this.destroyed && this.handle) {
80
+ this.removeStats();
81
+ if (!this.props.handle) {
82
+ this.trackDeallocatedMemory();
83
+ this.handle.destroy();
84
+ } else {
85
+ this.trackDeallocatedReferencedMemory('Buffer');
86
+ }
87
+ this.destroyed = true;
88
+ // @ts-expect-error readonly
89
+ this.handle = null;
90
+ }
68
91
  }
69
92
 
70
93
  write(data: ArrayBufferLike | ArrayBufferView | SharedArrayBuffer, byteOffset = 0) {
@@ -92,10 +115,11 @@ export class WebGPUBuffer extends Buffer {
92
115
  byteOffset: number = 0,
93
116
  byteLength: number = this.byteLength - byteOffset
94
117
  ): Promise<void> {
118
+ const alignedByteLength = Math.ceil(byteLength / 4) * 4;
95
119
  // Unless the application created and supplied a mappable buffer, a staging buffer is needed
96
120
  const isMappable = (this.usage & Buffer.MAP_WRITE) !== 0;
97
121
  const mappableBuffer: WebGPUBuffer | null = !isMappable
98
- ? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.byteLength)
122
+ ? this._getMappableBuffer(Buffer.MAP_WRITE | Buffer.COPY_SRC, 0, this.paddedByteLength)
99
123
  : null;
100
124
 
101
125
  const writeBuffer = mappableBuffer || this;
@@ -105,13 +129,15 @@ export class WebGPUBuffer extends Buffer {
105
129
  this.device.pushErrorScope('validation');
106
130
  try {
107
131
  await this.device.handle.queue.onSubmittedWorkDone();
108
- await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, byteLength);
109
- const arrayBuffer = writeBuffer.handle.getMappedRange(byteOffset, byteLength);
132
+ await writeBuffer.handle.mapAsync(GPUMapMode.WRITE, byteOffset, alignedByteLength);
133
+ const mappedRange = writeBuffer.handle.getMappedRange(byteOffset, alignedByteLength);
134
+ const arrayBuffer = mappedRange.slice(0, byteLength);
110
135
  // eslint-disable-next-line @typescript-eslint/await-thenable
111
136
  await callback(arrayBuffer, 'mapped');
137
+ new Uint8Array(mappedRange).set(new Uint8Array(arrayBuffer), 0);
112
138
  writeBuffer.handle.unmap();
113
139
  if (mappableBuffer) {
114
- this._copyBuffer(mappableBuffer, byteOffset, byteLength);
140
+ this._copyBuffer(mappableBuffer, byteOffset, alignedByteLength);
115
141
  }
116
142
  } finally {
117
143
  this.device.popErrorScope((error: GPUError) => {
@@ -138,17 +164,33 @@ export class WebGPUBuffer extends Buffer {
138
164
  byteOffset = 0,
139
165
  byteLength = this.byteLength - byteOffset
140
166
  ): Promise<T> {
167
+ const requestedEnd = byteOffset + byteLength;
168
+ if (requestedEnd > this.byteLength) {
169
+ throw new Error('Mapping range exceeds buffer size');
170
+ }
171
+
172
+ let mappedByteOffset = byteOffset;
173
+ let mappedByteLength = byteLength;
174
+ let sliceByteOffset = 0;
175
+ let lifetime: 'mapped' | 'copied' = 'mapped';
176
+
177
+ // WebGPU mapAsync requires 8-byte offsets and 4-byte lengths.
141
178
  if (byteOffset % 8 !== 0 || byteLength % 4 !== 0) {
142
- throw new Error('byteOffset must be multiple of 8 and byteLength multiple of 4');
179
+ mappedByteOffset = Math.floor(byteOffset / 8) * 8;
180
+ const alignedEnd = Math.ceil(requestedEnd / 4) * 4;
181
+ mappedByteLength = alignedEnd - mappedByteOffset;
182
+ sliceByteOffset = byteOffset - mappedByteOffset;
183
+ lifetime = 'copied';
143
184
  }
144
- if (byteOffset + byteLength > this.handle.size) {
185
+
186
+ if (mappedByteOffset + mappedByteLength > this.paddedByteLength) {
145
187
  throw new Error('Mapping range exceeds buffer size');
146
188
  }
147
189
 
148
190
  // Unless the application created and supplied a mappable buffer, a staging buffer is needed
149
191
  const isMappable = (this.usage & Buffer.MAP_READ) !== 0;
150
192
  const mappableBuffer: WebGPUBuffer | null = !isMappable
151
- ? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.byteLength)
193
+ ? this._getMappableBuffer(Buffer.MAP_READ | Buffer.COPY_DST, 0, this.paddedByteLength)
152
194
  : null;
153
195
 
154
196
  const readBuffer = mappableBuffer || this;
@@ -158,12 +200,16 @@ export class WebGPUBuffer extends Buffer {
158
200
  try {
159
201
  await this.device.handle.queue.onSubmittedWorkDone();
160
202
  if (mappableBuffer) {
161
- mappableBuffer._copyBuffer(this);
203
+ mappableBuffer._copyBuffer(this, mappedByteOffset, mappedByteLength);
162
204
  }
163
- await readBuffer.handle.mapAsync(GPUMapMode.READ, byteOffset, byteLength);
164
- const arrayBuffer = readBuffer.handle.getMappedRange(byteOffset, byteLength);
205
+ await readBuffer.handle.mapAsync(GPUMapMode.READ, mappedByteOffset, mappedByteLength);
206
+ const arrayBuffer = readBuffer.handle.getMappedRange(mappedByteOffset, mappedByteLength);
207
+ const mappedRange =
208
+ lifetime === 'mapped'
209
+ ? arrayBuffer
210
+ : arrayBuffer.slice(sliceByteOffset, sliceByteOffset + byteLength);
165
211
  // eslint-disable-next-line @typescript-eslint/await-thenable
166
- const result = await callback(arrayBuffer, 'mapped');
212
+ const result = await callback(mappedRange, lifetime);
167
213
  readBuffer.handle.unmap();
168
214
  return result;
169
215
  } finally {
@@ -13,7 +13,7 @@ export class WebGPUCommandBuffer extends CommandBuffer {
13
13
  readonly handle: GPUCommandBuffer;
14
14
 
15
15
  constructor(commandEncoder: WebGPUCommandEncoder, props: CommandBufferProps) {
16
- super(commandEncoder.device, {});
16
+ super(commandEncoder.device, props);
17
17
  this.device = commandEncoder.device;
18
18
  this.handle =
19
19
  this.props.handle ||