@luma.gl/webgpu 9.3.0-alpha.6 → 9.3.0-alpha.9

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 (76) hide show
  1. package/dist/adapter/helpers/get-bind-group.d.ts +3 -6
  2. package/dist/adapter/helpers/get-bind-group.d.ts.map +1 -1
  3. package/dist/adapter/helpers/get-bind-group.js +11 -14
  4. package/dist/adapter/helpers/get-bind-group.js.map +1 -1
  5. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts +3 -1
  6. package/dist/adapter/helpers/get-vertex-buffer-layout.d.ts.map +1 -1
  7. package/dist/adapter/helpers/get-vertex-buffer-layout.js +17 -12
  8. package/dist/adapter/helpers/get-vertex-buffer-layout.js.map +1 -1
  9. package/dist/adapter/helpers/webgpu-parameters.d.ts.map +1 -1
  10. package/dist/adapter/helpers/webgpu-parameters.js +1 -0
  11. package/dist/adapter/helpers/webgpu-parameters.js.map +1 -1
  12. package/dist/adapter/resources/webgpu-command-encoder.d.ts +3 -13
  13. package/dist/adapter/resources/webgpu-command-encoder.d.ts.map +1 -1
  14. package/dist/adapter/resources/webgpu-command-encoder.js +68 -29
  15. package/dist/adapter/resources/webgpu-command-encoder.js.map +1 -1
  16. package/dist/adapter/resources/webgpu-compute-pass.d.ts +3 -3
  17. package/dist/adapter/resources/webgpu-compute-pass.d.ts.map +1 -1
  18. package/dist/adapter/resources/webgpu-compute-pass.js +17 -7
  19. package/dist/adapter/resources/webgpu-compute-pass.js.map +1 -1
  20. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts +7 -9
  21. package/dist/adapter/resources/webgpu-compute-pipeline.d.ts.map +1 -1
  22. package/dist/adapter/resources/webgpu-compute-pipeline.js +26 -29
  23. package/dist/adapter/resources/webgpu-compute-pipeline.js.map +1 -1
  24. package/dist/adapter/resources/webgpu-fence.d.ts.map +1 -1
  25. package/dist/adapter/resources/webgpu-fence.js +9 -1
  26. package/dist/adapter/resources/webgpu-fence.js.map +1 -1
  27. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts +1 -1
  28. package/dist/adapter/resources/webgpu-pipeline-layout.d.ts.map +1 -1
  29. package/dist/adapter/resources/webgpu-pipeline-layout.js +10 -16
  30. package/dist/adapter/resources/webgpu-pipeline-layout.js.map +1 -1
  31. package/dist/adapter/resources/webgpu-render-pass.d.ts +4 -4
  32. package/dist/adapter/resources/webgpu-render-pass.d.ts.map +1 -1
  33. package/dist/adapter/resources/webgpu-render-pass.js +10 -10
  34. package/dist/adapter/resources/webgpu-render-pass.js.map +1 -1
  35. package/dist/adapter/resources/webgpu-render-pipeline.d.ts +10 -9
  36. package/dist/adapter/resources/webgpu-render-pipeline.d.ts.map +1 -1
  37. package/dist/adapter/resources/webgpu-render-pipeline.js +48 -38
  38. package/dist/adapter/resources/webgpu-render-pipeline.js.map +1 -1
  39. package/dist/adapter/resources/webgpu-shader.d.ts.map +1 -1
  40. package/dist/adapter/resources/webgpu-shader.js +17 -1
  41. package/dist/adapter/resources/webgpu-shader.js.map +1 -1
  42. package/dist/adapter/resources/webgpu-texture.d.ts +8 -1
  43. package/dist/adapter/resources/webgpu-texture.d.ts.map +1 -1
  44. package/dist/adapter/resources/webgpu-texture.js +35 -43
  45. package/dist/adapter/resources/webgpu-texture.js.map +1 -1
  46. package/dist/adapter/webgpu-device.d.ts +6 -2
  47. package/dist/adapter/webgpu-device.d.ts.map +1 -1
  48. package/dist/adapter/webgpu-device.js +60 -18
  49. package/dist/adapter/webgpu-device.js.map +1 -1
  50. package/dist/dist.dev.js +508 -311
  51. package/dist/dist.min.js +13 -13
  52. package/dist/index.cjs +439 -319
  53. package/dist/index.cjs.map +4 -4
  54. package/dist/wgsl/get-shader-layout-wgsl.d.ts.map +1 -1
  55. package/dist/wgsl/get-shader-layout-wgsl.js +8 -0
  56. package/dist/wgsl/get-shader-layout-wgsl.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/adapter/helpers/get-bind-group.ts +18 -27
  59. package/src/adapter/helpers/get-vertex-buffer-layout.ts +31 -12
  60. package/src/adapter/helpers/webgpu-parameters.ts +2 -0
  61. package/src/adapter/resources/webgpu-command-encoder.ts +99 -46
  62. package/src/adapter/resources/webgpu-compute-pass.ts +35 -8
  63. package/src/adapter/resources/webgpu-compute-pipeline.ts +43 -30
  64. package/src/adapter/resources/webgpu-fence.ts +11 -3
  65. package/src/adapter/resources/webgpu-pipeline-layout.ts +16 -14
  66. package/src/adapter/resources/webgpu-render-pass.ts +18 -14
  67. package/src/adapter/resources/webgpu-render-pipeline.ts +68 -46
  68. package/src/adapter/resources/webgpu-shader.ts +16 -1
  69. package/src/adapter/resources/webgpu-texture.ts +61 -44
  70. package/src/adapter/webgpu-device.ts +101 -25
  71. package/src/wgsl/get-shader-layout-wgsl.ts +9 -0
  72. package/dist/adapter/helpers/accessor-to-format.d.ts +0 -1
  73. package/dist/adapter/helpers/accessor-to-format.d.ts.map +0 -1
  74. package/dist/adapter/helpers/accessor-to-format.js +0 -105
  75. package/dist/adapter/helpers/accessor-to-format.js.map +0 -1
  76. package/src/adapter/helpers/accessor-to-format.ts +0 -104
@@ -15,9 +15,17 @@ export class WebGPUFence extends Fence {
15
15
  constructor(device: WebGPUDevice, props: FenceProps = {}) {
16
16
  super(device, {});
17
17
  this.device = device;
18
- this.signaled = device.handle.queue.onSubmittedWorkDone().then(() => {
19
- this._signaled = true;
20
- });
18
+ this.signaled = device.handle.queue
19
+ .onSubmittedWorkDone()
20
+ .then(() => {
21
+ this._signaled = true;
22
+ })
23
+ .catch(error => {
24
+ if (this.device.shouldIgnoreDroppedInstanceError(error)) {
25
+ return;
26
+ }
27
+ throw error;
28
+ });
21
29
  }
22
30
 
23
31
  isSignaled(): boolean {
@@ -20,19 +20,16 @@ export class WebGPUPipelineLayout extends PipelineLayout {
20
20
 
21
21
  this.device = device;
22
22
 
23
- const bindGroupEntries = this.mapShaderLayoutToBindGroupEntries();
23
+ const bindGroupEntriesByGroup = this.mapShaderLayoutToBindGroupEntriesByGroup();
24
24
 
25
25
  this.handle = this.device.handle.createPipelineLayout({
26
26
  label: props?.id ?? 'unnamed-pipeline-layout',
27
- bindGroupLayouts: [
28
- // TODO (kaapp): We can cache these to re-use them across
29
- // layers, particularly if using a separate group for injected
30
- // bindings (e.g. project/lighting)
27
+ bindGroupLayouts: bindGroupEntriesByGroup.map((entries, group) =>
31
28
  this.device.handle.createBindGroupLayout({
32
- label: 'bind-group-layout',
33
- entries: bindGroupEntries
29
+ label: `bind-group-layout-${group}`,
30
+ entries
34
31
  })
35
- ]
32
+ )
36
33
  });
37
34
  }
38
35
 
@@ -42,10 +39,15 @@ export class WebGPUPipelineLayout extends PipelineLayout {
42
39
  this.handle = null;
43
40
  }
44
41
 
45
- protected mapShaderLayoutToBindGroupEntries(): GPUBindGroupLayoutEntry[] {
46
- // Set up the pipeline layout
47
- // TODO (kaapp): This only supports the first group, but so does the rest of the code
48
- const bindGroupEntries: GPUBindGroupLayoutEntry[] = [];
42
+ protected mapShaderLayoutToBindGroupEntriesByGroup(): GPUBindGroupLayoutEntry[][] {
43
+ const maxGroup = this.props.shaderLayout.bindings.reduce(
44
+ (highestGroup, binding) => Math.max(highestGroup, binding.group),
45
+ -1
46
+ );
47
+ const bindGroupEntriesByGroup: GPUBindGroupLayoutEntry[][] = Array.from(
48
+ {length: maxGroup + 1},
49
+ () => []
50
+ );
49
51
 
50
52
  for (const binding of this.props.shaderLayout.bindings) {
51
53
  const bindingTypeInfo: Omit<GPUBindGroupLayoutEntry, 'binding' | 'visibility'> = {};
@@ -112,14 +114,14 @@ export class WebGPUPipelineLayout extends PipelineLayout {
112
114
  const VISIBILITY_ALL =
113
115
  GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE;
114
116
 
115
- bindGroupEntries.push({
117
+ bindGroupEntriesByGroup[binding.group].push({
116
118
  binding: binding.location,
117
119
  visibility: binding.visibility || VISIBILITY_ALL,
118
120
  ...bindingTypeInfo
119
121
  });
120
122
  }
121
123
 
122
- return bindGroupEntries;
124
+ return bindGroupEntriesByGroup;
123
125
  }
124
126
  }
125
127
 
@@ -3,8 +3,8 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {TypedArray, NumberArray4} from '@math.gl/types';
6
- import type {RenderPassProps, RenderPassParameters, Binding} from '@luma.gl/core';
7
- import {Buffer, RenderPass, RenderPipeline, log} from '@luma.gl/core';
6
+ import type {RenderPassProps, RenderPassParameters, Bindings, BindingsByGroup} from '@luma.gl/core';
7
+ import {Buffer, RenderPass, RenderPipeline, _getDefaultBindGroupFactory, log} from '@luma.gl/core';
8
8
  import {WebGPUDevice} from '../webgpu-device';
9
9
  import {WebGPUBuffer} from './webgpu-buffer';
10
10
  // import {WebGPUCommandEncoder} from './webgpu-command-encoder';
@@ -22,9 +22,13 @@ export class WebGPURenderPass extends RenderPass {
22
22
  pipeline: WebGPURenderPipeline | null = null;
23
23
 
24
24
  /** Latest bindings applied to this pass */
25
- bindings: Record<string, Binding> = {};
25
+ bindings: Bindings | BindingsByGroup = {};
26
26
 
27
- constructor(device: WebGPUDevice, props: RenderPassProps = {}) {
27
+ constructor(
28
+ device: WebGPUDevice,
29
+ props: RenderPassProps = {},
30
+ commandEncoder: GPUCommandEncoder = device.commandEncoder.handle
31
+ ) {
28
32
  super(device, props);
29
33
  this.device = device;
30
34
  const {props: renderPassProps} = this;
@@ -72,14 +76,9 @@ export class WebGPURenderPass extends RenderPass {
72
76
  (getTimestamp() - descriptorAssemblyStartTime);
73
77
  }
74
78
 
75
- if (!device.commandEncoder) {
76
- throw new Error('commandEncoder not available');
77
- }
78
-
79
79
  this.device.pushErrorScope('validation');
80
80
  const beginRenderPassStartTime = profiler ? getTimestamp() : 0;
81
- this.handle =
82
- this.props.handle || device.commandEncoder.handle.beginRenderPass(renderPassDescriptor);
81
+ this.handle = this.props.handle || commandEncoder.beginRenderPass(renderPassDescriptor);
83
82
  if (profiler) {
84
83
  profiler.renderPassBeginCount = (profiler.renderPassBeginCount || 0) + 1;
85
84
  profiler.renderPassBeginTimeMs =
@@ -125,11 +124,16 @@ export class WebGPURenderPass extends RenderPass {
125
124
  }
126
125
 
127
126
  /** Sets an array of bindings (uniform buffers, samplers, textures, ...) */
128
- setBindings(bindings: Record<string, Binding>): void {
127
+ setBindings(bindings: Bindings | BindingsByGroup): void {
129
128
  this.bindings = bindings;
130
- const bindGroup = this.pipeline?._getBindGroup(bindings);
131
- if (bindGroup) {
132
- this.handle.setBindGroup(0, bindGroup);
129
+ const bindGroups =
130
+ (this.pipeline &&
131
+ _getDefaultBindGroupFactory(this.device).getBindGroups(this.pipeline, bindings)) ||
132
+ {};
133
+ for (const [group, bindGroup] of Object.entries(bindGroups)) {
134
+ if (bindGroup) {
135
+ this.handle.setBindGroup(Number(group), bindGroup as GPUBindGroup);
136
+ }
133
137
  }
134
138
  }
135
139
 
@@ -1,13 +1,17 @@
1
1
  // luma.gl MIT license
2
2
 
3
- import type {Binding, RenderPass, VertexArray} from '@luma.gl/core';
4
- import {RenderPipeline, RenderPipelineProps, log} from '@luma.gl/core';
3
+ import type {Bindings, BindingsByGroup, RenderPass, VertexArray} from '@luma.gl/core';
4
+ import {
5
+ RenderPipeline,
6
+ RenderPipelineProps,
7
+ _getDefaultBindGroupFactory,
8
+ log,
9
+ normalizeBindingsByGroup
10
+ } from '@luma.gl/core';
5
11
  import {applyParametersToRenderPipelineDescriptor} from '../helpers/webgpu-parameters';
6
12
  import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
7
- import {getBindGroup} from '../helpers/get-bind-group';
8
13
  import {getVertexBufferLayout} from '../helpers/get-vertex-buffer-layout';
9
14
  // import {convertAttributesVertexBufferToLayout} from '../helpers/get-vertex-buffer-layout';
10
- // import {mapAccessorToWebGPUFormat} from './helpers/accessor-to-format';
11
15
  // import type {BufferAccessors} from './webgpu-pipeline';
12
16
 
13
17
  import type {WebGPUDevice} from '../webgpu-device';
@@ -15,23 +19,20 @@ import type {WebGPUDevice} from '../webgpu-device';
15
19
  import type {WebGPUShader} from './webgpu-shader';
16
20
  import type {WebGPURenderPass} from './webgpu-render-pass';
17
21
 
18
- const EMPTY_BINDINGS: Record<string, Binding> = {};
19
-
20
22
  // RENDER PIPELINE
21
23
 
22
24
  /** Creates a new render pipeline when parameters change */
23
25
  export class WebGPURenderPipeline extends RenderPipeline {
24
26
  readonly device: WebGPUDevice;
25
27
  readonly handle: GPURenderPipeline;
28
+ readonly descriptor: GPURenderPipelineDescriptor | null;
26
29
 
27
30
  readonly vs: WebGPUShader;
28
31
  readonly fs: WebGPUShader | null = null;
29
32
 
30
33
  /** Compatibility path for direct pipeline.setBindings() usage */
31
- private _bindings: Record<string, Binding>;
32
- /** For internal use to create BindGroups */
33
- private _bindGroupLayout: GPUBindGroupLayout | null = null;
34
- private _bindGroup: GPUBindGroup | null = null;
34
+ private _bindingsByGroup: BindingsByGroup;
35
+ private _bindGroupCacheKeysByGroup: Partial<Record<number, object>> = {};
35
36
 
36
37
  override get [Symbol.toStringTag]() {
37
38
  return 'WebGPURenderPipeline';
@@ -40,9 +41,14 @@ export class WebGPURenderPipeline extends RenderPipeline {
40
41
  constructor(device: WebGPUDevice, props: RenderPipelineProps) {
41
42
  super(device, props);
42
43
  this.device = device;
44
+ this.shaderLayout ||= this.device.getShaderLayout((props.vs as WebGPUShader).source) || {
45
+ attributes: [],
46
+ bindings: []
47
+ };
43
48
  this.handle = this.props.handle as GPURenderPipeline;
49
+ let descriptor: GPURenderPipelineDescriptor | null = null;
44
50
  if (!this.handle) {
45
- const descriptor = this._getRenderPipelineDescriptor();
51
+ descriptor = this._getRenderPipelineDescriptor();
46
52
  log.groupCollapsed(1, `new WebGPURenderPipeline(${this.id})`)();
47
53
  log.probe(1, JSON.stringify(descriptor, null, 2))();
48
54
  log.groupEnd(1)();
@@ -54,12 +60,15 @@ export class WebGPURenderPipeline extends RenderPipeline {
54
60
  this.device.debug();
55
61
  });
56
62
  }
63
+ this.descriptor = descriptor;
57
64
  this.handle.label = this.props.id;
58
65
 
59
66
  // Note: Often the same shader in WebGPU
60
67
  this.vs = props.vs as WebGPUShader;
61
68
  this.fs = props.fs as WebGPUShader;
62
- this._bindings = props.bindings || EMPTY_BINDINGS;
69
+ this._bindingsByGroup =
70
+ props.bindGroups || normalizeBindingsByGroup(this.shaderLayout, props.bindings);
71
+ this._bindGroupCacheKeysByGroup = createBindGroupCacheKeys(this._bindingsByGroup);
63
72
  }
64
73
 
65
74
  override destroy(): void {
@@ -72,22 +81,24 @@ export class WebGPURenderPipeline extends RenderPipeline {
72
81
  * Compatibility shim for code paths that still set bindings on the pipeline.
73
82
  * The shared-model path passes bindings per draw and does not rely on this state.
74
83
  */
75
- setBindings(bindings: Record<string, Binding>): void {
76
- let bindingsChanged = false;
77
- for (const [name, binding] of Object.entries(bindings)) {
78
- if (this._bindings[name] !== binding) {
79
- if (!bindingsChanged) {
80
- if (this._bindings === this.props.bindings || this._bindings === EMPTY_BINDINGS) {
81
- this._bindings = {...this._bindings};
84
+ setBindings(bindings: Bindings | BindingsByGroup): void {
85
+ const nextBindingsByGroup = normalizeBindingsByGroup(this.shaderLayout, bindings);
86
+ for (const [groupKey, groupBindings] of Object.entries(nextBindingsByGroup)) {
87
+ const group = Number(groupKey);
88
+ for (const [name, binding] of Object.entries(groupBindings || {})) {
89
+ const currentGroupBindings = this._bindingsByGroup[group] || {};
90
+ if (currentGroupBindings[name] !== binding) {
91
+ if (
92
+ !this._bindingsByGroup[group] ||
93
+ this._bindingsByGroup[group] === currentGroupBindings
94
+ ) {
95
+ this._bindingsByGroup[group] = {...currentGroupBindings};
82
96
  }
83
- bindingsChanged = true;
97
+ this._bindingsByGroup[group][name] = binding;
98
+ this._bindGroupCacheKeysByGroup[group] = {};
84
99
  }
85
- this._bindings[name] = binding;
86
100
  }
87
101
  }
88
- if (bindingsChanged) {
89
- this._bindGroup = null;
90
- }
91
102
  }
92
103
 
93
104
  /** @todo - should this be moved to renderpass? */
@@ -101,7 +112,9 @@ export class WebGPURenderPipeline extends RenderPipeline {
101
112
  firstIndex?: number;
102
113
  firstInstance?: number;
103
114
  baseVertex?: number;
104
- bindings?: Record<string, Binding>;
115
+ bindings?: Bindings;
116
+ bindGroups?: BindingsByGroup;
117
+ _bindGroupCacheKeys?: Partial<Record<number, object>>;
105
118
  uniforms?: Record<string, unknown>;
106
119
  }): boolean {
107
120
  const webgpuRenderPass = options.renderPass as WebGPURenderPass;
@@ -117,9 +130,16 @@ export class WebGPURenderPipeline extends RenderPipeline {
117
130
  });
118
131
 
119
132
  // Set bindings (uniform buffers, textures etc)
120
- const bindGroup = this._getBindGroup(options.bindings);
121
- if (bindGroup) {
122
- webgpuRenderPass.handle.setBindGroup(0, bindGroup);
133
+ const hasExplicitBindings = Boolean(options.bindGroups || options.bindings);
134
+ const bindGroups = _getDefaultBindGroupFactory(this.device).getBindGroups(
135
+ this,
136
+ hasExplicitBindings ? options.bindGroups || options.bindings : this._bindingsByGroup,
137
+ hasExplicitBindings ? options._bindGroupCacheKeys : this._bindGroupCacheKeysByGroup
138
+ );
139
+ for (const [group, bindGroup] of Object.entries(bindGroups)) {
140
+ if (bindGroup) {
141
+ webgpuRenderPass.handle.setBindGroup(Number(group), bindGroup as GPUBindGroup);
142
+ }
123
143
  }
124
144
 
125
145
  // Set attributes
@@ -150,24 +170,12 @@ export class WebGPURenderPipeline extends RenderPipeline {
150
170
  return true;
151
171
  }
152
172
 
153
- /** Return a bind group created by setBindings */
154
- _getBindGroup(bindings?: Record<string, Binding>) {
155
- if (this.shaderLayout.bindings.length === 0) {
156
- return null;
157
- }
158
-
159
- // Get hold of the bind group layout. We don't want to do this unless we know there is at least one bind group
160
- this._bindGroupLayout = this._bindGroupLayout || this.handle.getBindGroupLayout(0);
161
-
162
- if (bindings) {
163
- return getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, bindings);
164
- }
165
-
166
- this._bindGroup =
167
- this._bindGroup ||
168
- getBindGroup(this.device, this._bindGroupLayout, this.shaderLayout, this._bindings);
173
+ _getBindingsByGroupWebGPU(): BindingsByGroup {
174
+ return this._bindingsByGroup;
175
+ }
169
176
 
170
- return this._bindGroup;
177
+ _getBindGroupCacheKeysWebGPU(): Partial<Record<number, object>> {
178
+ return this._bindGroupCacheKeysByGroup;
171
179
  }
172
180
 
173
181
  /**
@@ -178,7 +186,9 @@ export class WebGPURenderPipeline extends RenderPipeline {
178
186
  const vertex: GPUVertexState = {
179
187
  module: (this.props.vs as WebGPUShader).handle,
180
188
  entryPoint: this.props.vertexEntryPoint || 'main',
181
- buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout)
189
+ buffers: getVertexBufferLayout(this.shaderLayout, this.props.bufferLayout, {
190
+ pipelineId: this.id
191
+ })
182
192
  };
183
193
 
184
194
  // Populate color targets
@@ -228,6 +238,18 @@ export class WebGPURenderPipeline extends RenderPipeline {
228
238
  return descriptor;
229
239
  }
230
240
  }
241
+
242
+ function createBindGroupCacheKeys(
243
+ bindingsByGroup: BindingsByGroup
244
+ ): Partial<Record<number, object>> {
245
+ const bindGroupCacheKeys: Partial<Record<number, object>> = {};
246
+ for (const [groupKey, groupBindings] of Object.entries(bindingsByGroup)) {
247
+ if (groupBindings && Object.keys(groupBindings).length > 0) {
248
+ bindGroupCacheKeys[Number(groupKey)] = {};
249
+ }
250
+ }
251
+ return bindGroupCacheKeys;
252
+ }
231
253
  /**
232
254
  _setAttributeBuffers(webgpuRenderPass: WebGPURenderPass) {
233
255
  if (this._indexBuffer) {
@@ -64,7 +64,22 @@ export class WebGPUShader extends Shader {
64
64
 
65
65
  /** Returns compilation info for this shader */
66
66
  async getCompilationInfo(): Promise<readonly CompilerMessage[]> {
67
- const compilationInfo = await this.handle.getCompilationInfo();
67
+ // `_checkCompilationError()` runs asynchronously after construction, so the shader can be
68
+ // destroyed before we await compilation info. Snapshot the handle and treat a destroyed shader
69
+ // as having no compiler messages instead of dereferencing `null`.
70
+ const handle = this.handle;
71
+ if (!handle) {
72
+ return [];
73
+ }
74
+ let compilationInfo;
75
+ try {
76
+ compilationInfo = await handle.getCompilationInfo();
77
+ } catch (error) {
78
+ if (this.device.shouldIgnoreDroppedInstanceError(error, 'getCompilationInfo')) {
79
+ return [];
80
+ }
81
+ throw error;
82
+ }
68
83
  return compilationInfo.messages;
69
84
  }
70
85
  }
@@ -16,6 +16,7 @@ import {getWebGPUTextureFormat} from '../helpers/convert-texture-format';
16
16
  import type {WebGPUDevice} from '../webgpu-device';
17
17
  import {WebGPUSampler} from './webgpu-sampler';
18
18
  import {WebGPUTextureView} from './webgpu-texture-view';
19
+ import {WebGPUBuffer} from './webgpu-buffer';
19
20
 
20
21
  /** WebGPU implementation of the luma.gl core Texture resource */
21
22
  export class WebGPUTexture extends Texture {
@@ -165,77 +166,93 @@ export class WebGPUTexture extends Texture {
165
166
  };
166
167
  }
167
168
 
168
- override readBuffer(options: TextureReadOptions = {}, buffer?: Buffer): Buffer {
169
+ override readBuffer(
170
+ options: TextureReadOptions & {byteOffset?: number} = {},
171
+ buffer?: Buffer
172
+ ): Buffer {
173
+ if (!buffer) {
174
+ throw new Error(`${this} readBuffer requires a destination buffer`);
175
+ }
169
176
  const {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect} =
170
177
  this._getSupportedColorReadOptions(options);
178
+ const byteOffset = options.byteOffset ?? 0;
171
179
 
172
- const layout = this.computeMemoryLayout({x, y, z, width, height, depthOrArrayLayers, mipLevel});
173
-
174
- const {bytesPerRow, rowsPerImage, byteLength} = layout;
180
+ const layout = this.computeMemoryLayout({width, height, depthOrArrayLayers, mipLevel});
175
181
 
176
- // Create a GPUBuffer to hold the copied pixel data.
177
- const readBuffer =
178
- buffer ||
179
- this.device.createBuffer({
180
- byteLength,
181
- usage: Buffer.COPY_DST | Buffer.MAP_READ
182
- });
182
+ const {byteLength} = layout;
183
183
 
184
- if (readBuffer.byteLength < byteLength) {
184
+ if (buffer.byteLength < byteOffset + byteLength) {
185
185
  throw new Error(
186
- `${this} readBuffer target is too small (${readBuffer.byteLength} < ${byteLength})`
186
+ `${this} readBuffer target is too small (${buffer.byteLength} < ${byteOffset + byteLength})`
187
187
  );
188
188
  }
189
189
 
190
- const gpuReadBuffer = readBuffer.handle as GPUBuffer;
191
-
192
- // Record commands to copy from the texture to the buffer.
193
190
  const gpuDevice = this.device.handle;
194
-
195
191
  this.device.pushErrorScope('validation');
196
192
  const commandEncoder = gpuDevice.createCommandEncoder();
193
+ this.copyToBuffer(
194
+ commandEncoder,
195
+ {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect, byteOffset},
196
+ buffer
197
+ );
198
+
199
+ const commandBuffer = commandEncoder.finish();
200
+ this.device.handle.queue.submit([commandBuffer]);
201
+ this.device.popErrorScope((error: GPUError) => {
202
+ this.device.reportError(new Error(`${this} readBuffer: ${error.message}`), this)();
203
+ this.device.debug();
204
+ });
205
+
206
+ return buffer;
207
+ }
208
+
209
+ override async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
210
+ throw new Error(
211
+ `${this} readDataAsync is deprecated; use readBuffer() with an explicit destination buffer or DynamicTexture.readAsync()`
212
+ );
213
+ }
214
+
215
+ copyToBuffer(
216
+ commandEncoder: GPUCommandEncoder,
217
+ options: TextureReadOptions & {
218
+ byteOffset?: number;
219
+ bytesPerRow?: number;
220
+ rowsPerImage?: number;
221
+ } = {},
222
+ buffer: Buffer
223
+ ): void {
224
+ const {
225
+ byteOffset = 0,
226
+ bytesPerRow: requestedBytesPerRow,
227
+ rowsPerImage: requestedRowsPerImage,
228
+ ...textureReadOptions
229
+ } = options;
230
+ const {x, y, z, width, height, depthOrArrayLayers, mipLevel, aspect} =
231
+ this._getSupportedColorReadOptions(textureReadOptions);
232
+ const layout = this.computeMemoryLayout({width, height, depthOrArrayLayers, mipLevel});
233
+ const effectiveBytesPerRow = requestedBytesPerRow ?? layout.bytesPerRow;
234
+ const effectiveRowsPerImage = requestedRowsPerImage ?? layout.rowsPerImage;
235
+ const webgpuBuffer = buffer as WebGPUBuffer;
236
+
197
237
  commandEncoder.copyTextureToBuffer(
198
- // source
199
238
  {
200
239
  texture: this.handle,
201
240
  origin: {x, y, z},
202
- // origin: [options.x, options.y, 0], // options.depth],
203
241
  mipLevel,
204
242
  aspect
205
- // colorSpace: options.colorSpace,
206
- // premultipliedAlpha: options.premultipliedAlpha
207
243
  },
208
- // destination
209
244
  {
210
- buffer: gpuReadBuffer,
211
- offset: 0,
212
- bytesPerRow,
213
- rowsPerImage
245
+ buffer: webgpuBuffer.handle,
246
+ offset: byteOffset,
247
+ bytesPerRow: effectiveBytesPerRow,
248
+ rowsPerImage: effectiveRowsPerImage
214
249
  },
215
- // copy size
216
250
  {
217
251
  width,
218
252
  height,
219
253
  depthOrArrayLayers
220
254
  }
221
255
  );
222
-
223
- // Submit the command.
224
- const commandBuffer = commandEncoder.finish();
225
- this.device.handle.queue.submit([commandBuffer]);
226
- this.device.popErrorScope((error: GPUError) => {
227
- this.device.reportError(new Error(`${this} readBuffer: ${error.message}`), this)();
228
- this.device.debug();
229
- });
230
-
231
- return readBuffer;
232
- }
233
-
234
- override async readDataAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
235
- const buffer = this.readBuffer(options);
236
- const data = await buffer.readAsync();
237
- buffer.destroy();
238
- return data.buffer as ArrayBuffer;
239
256
  }
240
257
 
241
258
  override writeBuffer(buffer: Buffer, options_: TextureWriteOptions = {}) {