@luma.gl/webgpu 9.2.5 → 9.3.0-alpha.10

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 +4 -6
  10. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  11. package/dist/adapter/helpers/get-bind-group.js +39 -32
  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 +56 -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 +34 -34
  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 +173 -16
  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 +8070 -547
  94. package/dist/dist.min.js +169 -6
  95. package/dist/index.cjs +1929 -410
  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 +52 -46
  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 +83 -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 +40 -42
  128. package/src/adapter/webgpu-canvas-context.ts +107 -40
  129. package/src/adapter/webgpu-device.ts +231 -21
  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,13 @@
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 {Binding, Bindings, ComputeShaderLayout, ShaderLayout} from '@luma.gl/core';
6
+ import {Buffer, Sampler, Texture, TextureView, getShaderLayoutBinding, log} from '@luma.gl/core';
7
+ import type {WebGPUDevice} from '../webgpu-device';
7
8
  import type {WebGPUBuffer} from '../resources/webgpu-buffer';
8
9
  import type {WebGPUSampler} from '../resources/webgpu-sampler';
9
10
  import type {WebGPUTexture} from '../resources/webgpu-texture';
11
+ import type {WebGPUTextureView} from '../resources/webgpu-texture-view';
10
12
 
11
13
  /**
12
14
  * Create a WebGPU "bind group layout" from an array of luma.gl bindings
@@ -15,7 +17,7 @@ import type {WebGPUTexture} from '../resources/webgpu-texture';
15
17
  export function makeBindGroupLayout(
16
18
  device: GPUDevice,
17
19
  layout: GPUBindGroupLayout,
18
- bindings: Binding[]
20
+ bindings: Bindings
19
21
  ): GPUBindGroupLayout {
20
22
  throw new Error('not implemented');
21
23
  // return device.createBindGroupLayout({
@@ -28,69 +30,66 @@ export function makeBindGroupLayout(
28
30
  * Create a WebGPU "bind group" from an array of luma.gl bindings
29
31
  */
30
32
  export function getBindGroup(
31
- device: GPUDevice,
33
+ device: WebGPUDevice,
32
34
  bindGroupLayout: GPUBindGroupLayout,
33
- shaderLayout: ComputeShaderLayout,
34
- bindings: Record<string, Binding>
35
- ): GPUBindGroup {
36
- const entries = getBindGroupEntries(bindings, shaderLayout);
35
+ shaderLayout: ShaderLayout | ComputeShaderLayout,
36
+ bindings: Bindings,
37
+ group: number
38
+ ): GPUBindGroup | null {
39
+ const entries = getBindGroupEntries(bindings, shaderLayout, group);
40
+ if (entries.length === 0) {
41
+ return null;
42
+ }
37
43
  device.pushErrorScope('validation');
38
- const bindGroup = device.createBindGroup({
44
+ const bindGroup = device.handle.createBindGroup({
39
45
  layout: bindGroupLayout,
40
46
  entries
41
47
  });
42
- device.popErrorScope().then((error: GPUError | null) => {
43
- if (error) {
44
- log.error(`bindGroup creation: ${error.message}`, bindGroup)();
45
- }
48
+ device.popErrorScope((error: GPUError) => {
49
+ log.error(`bindGroup creation: ${error.message}`, bindGroup)();
46
50
  });
47
51
  return bindGroup;
48
52
  }
49
53
 
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()
59
- );
60
- if (!bindingLayout && !options?.ignoreWarnings) {
61
- log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
62
- }
63
- return bindingLayout || null;
64
- }
65
-
66
54
  /**
67
55
  * @param bindings
68
56
  * @returns
69
57
  */
70
58
  function getBindGroupEntries(
71
- bindings: Record<string, Binding>,
72
- shaderLayout: ComputeShaderLayout
59
+ bindings: Bindings,
60
+ shaderLayout: ShaderLayout | ComputeShaderLayout,
61
+ group: number
73
62
  ): GPUBindGroupEntry[] {
74
63
  const entries: GPUBindGroupEntry[] = [];
75
64
 
76
65
  for (const [bindingName, value] of Object.entries(bindings)) {
77
- let bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
78
- if (bindingLayout) {
79
- const entry = getBindGroupEntry(value, bindingLayout.location);
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;
70
+
71
+ // Mirror the WebGL path: when both `foo` and `fooUniforms` exist in the bindings map,
72
+ // prefer the exact shader binding name and ignore the alias entry.
73
+ if (!isShadowedAlias && bindingLayout?.group === group) {
74
+ const entry = bindingLayout
75
+ ? getBindGroupEntry(value, bindingLayout.location, undefined, bindingName)
76
+ : null;
80
77
  if (entry) {
81
78
  entries.push(entry);
82
79
  }
83
- }
84
80
 
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);
81
+ // TODO - hack to automatically bind samplers to supplied texture default samplers
82
+ if (value instanceof Texture) {
83
+ const samplerBindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
84
+ ignoreWarnings: true
85
+ });
86
+ const samplerEntry = samplerBindingLayout
87
+ ? samplerBindingLayout.group === group
88
+ ? getBindGroupEntry(value, samplerBindingLayout.location, {sampler: true}, bindingName)
89
+ : null
90
+ : null;
91
+ if (samplerEntry) {
92
+ entries.push(samplerEntry);
94
93
  }
95
94
  }
96
95
  }
@@ -102,7 +101,8 @@ function getBindGroupEntries(
102
101
  function getBindGroupEntry(
103
102
  binding: Binding,
104
103
  index: number,
105
- options?: {sampler?: boolean}
104
+ options?: {sampler?: boolean},
105
+ bindingName: string = 'unknown'
106
106
  ): GPUBindGroupEntry | null {
107
107
  if (binding instanceof Buffer) {
108
108
  return {
@@ -118,6 +118,12 @@ function getBindGroupEntry(
118
118
  resource: (binding as WebGPUSampler).handle
119
119
  };
120
120
  }
121
+ if (binding instanceof TextureView) {
122
+ return {
123
+ binding: index,
124
+ resource: (binding as WebGPUTextureView).handle
125
+ };
126
+ }
121
127
  if (binding instanceof Texture) {
122
128
  if (options?.sampler) {
123
129
  return {
@@ -130,6 +136,6 @@ function getBindGroupEntry(
130
136
  resource: (binding as WebGPUTexture).view.handle
131
137
  };
132
138
  }
133
- log.warn(`invalid binding ${name}`, binding);
139
+ log.warn(`invalid binding ${bindingName}`, binding);
134
140
  return null;
135
141
  }
@@ -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 ||