@luma.gl/engine 9.2.0-alpha.1 → 9.2.0-alpha.2

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 (59) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +1 -1
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +2 -14
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.d.ts +4 -1
  6. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  7. package/dist/animation-loop/make-animation-loop.js +39 -7
  8. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  9. package/dist/async-texture/async-texture.js.map +1 -1
  10. package/dist/compute/computation.js +2 -2
  11. package/dist/compute/computation.js.map +1 -1
  12. package/dist/dist.dev.js +166 -86
  13. package/dist/dist.min.js +19 -19
  14. package/dist/factories/pipeline-factory.js +3 -3
  15. package/dist/factories/pipeline-factory.js.map +1 -1
  16. package/dist/factories/shader-factory.js +2 -2
  17. package/dist/factories/shader-factory.js.map +1 -1
  18. package/dist/geometries/cube-geometry.d.ts +3 -3
  19. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  20. package/dist/geometry/geometry.d.ts.map +1 -1
  21. package/dist/geometry/geometry.js +3 -2
  22. package/dist/geometry/geometry.js.map +1 -1
  23. package/dist/index.cjs +177 -101
  24. package/dist/index.cjs.map +4 -4
  25. package/dist/model/model.d.ts.map +1 -1
  26. package/dist/model/model.js +25 -13
  27. package/dist/model/model.js.map +1 -1
  28. package/dist/modules/picking/color-picking.d.ts +1 -1
  29. package/dist/modules/picking/index-picking.d.ts +1 -1
  30. package/dist/modules/picking/picking-manager.d.ts +1 -1
  31. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  32. package/dist/modules/picking/picking-manager.js +1 -1
  33. package/dist/modules/picking/picking-manager.js.map +1 -1
  34. package/dist/modules/picking/picking-uniforms.d.ts +1 -1
  35. package/dist/shader-inputs.d.ts +5 -1
  36. package/dist/shader-inputs.d.ts.map +1 -1
  37. package/dist/shader-inputs.js +13 -6
  38. package/dist/shader-inputs.js.map +1 -1
  39. package/dist/utils/buffer-layout-helper.d.ts +12 -0
  40. package/dist/utils/buffer-layout-helper.d.ts.map +1 -0
  41. package/dist/utils/buffer-layout-helper.js +41 -0
  42. package/dist/utils/buffer-layout-helper.js.map +1 -0
  43. package/dist/utils/buffer-layout-order.d.ts +3 -0
  44. package/dist/utils/buffer-layout-order.d.ts.map +1 -0
  45. package/dist/utils/buffer-layout-order.js +16 -0
  46. package/dist/utils/buffer-layout-order.js.map +1 -0
  47. package/package.json +4 -4
  48. package/src/animation-loop/animation-loop.ts +2 -14
  49. package/src/animation-loop/make-animation-loop.ts +41 -9
  50. package/src/async-texture/async-texture.ts +3 -3
  51. package/src/compute/computation.ts +2 -2
  52. package/src/factories/pipeline-factory.ts +3 -3
  53. package/src/factories/shader-factory.ts +2 -2
  54. package/src/geometry/geometry.ts +3 -2
  55. package/src/model/model.ts +33 -15
  56. package/src/modules/picking/picking-manager.ts +1 -1
  57. package/src/shader-inputs.ts +21 -6
  58. package/src/utils/buffer-layout-helper.ts +51 -0
  59. package/src/utils/buffer-layout-order.ts +26 -0
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {luma, Adapter} from '@luma.gl/core';
5
+ import {luma, Adapter, Device} from '@luma.gl/core';
6
6
  import {AnimationLoopTemplate} from './animation-loop-template';
7
7
  import {AnimationLoop, AnimationLoopProps} from './animation-loop';
8
8
  import type {AnimationProps} from './animation-props';
@@ -15,7 +15,10 @@ export type MakeAnimationLoopProps = Omit<
15
15
  adapters?: Adapter[];
16
16
  };
17
17
 
18
- /** Instantiates and runs the render loop */
18
+ /**
19
+ * Instantiates an animation loop and initializes it with the template.
20
+ * @note The application needs to call `start()` on the returned animation loop to start the rendering loop.
21
+ */
19
22
  export function makeAnimationLoop(
20
23
  AnimationLoopTemplateCtor: typeof AnimationLoopTemplate,
21
24
  props?: MakeAnimationLoopProps
@@ -33,10 +36,16 @@ export function makeAnimationLoop(
33
36
  device,
34
37
 
35
38
  async onInitialize(animationProps: AnimationProps): Promise<unknown> {
36
- // @ts-expect-error abstract to prevent instantiation
37
- renderLoop = new AnimationLoopTemplateCtor(animationProps);
38
- // Any async loading can be handled here
39
- return await renderLoop?.onInitialize(animationProps);
39
+ clearError(animationProps.animationLoop.device!);
40
+ try {
41
+ // @ts-expect-error abstract to prevent instantiation
42
+ renderLoop = new AnimationLoopTemplateCtor(animationProps);
43
+ // Any async loading can be handled here
44
+ return await renderLoop?.onInitialize(animationProps);
45
+ } catch (error) {
46
+ setError(animationProps.animationLoop.device!, error as Error);
47
+ return null;
48
+ }
40
49
  },
41
50
 
42
51
  onRender: (animationProps: AnimationProps) => renderLoop?.onRender(animationProps),
@@ -51,8 +60,31 @@ export function makeAnimationLoop(
51
60
  return this.AnimationLoopTemplateCtor.info;
52
61
  };
53
62
 
54
- // Start the loop automatically
55
- // animationLoop.start();
56
-
57
63
  return animationLoop;
58
64
  }
65
+
66
+ function setError(device: Device, error: Error): void {
67
+ const canvas = device?.getDefaultCanvasContext().canvas;
68
+ if (canvas instanceof HTMLCanvasElement) {
69
+ canvas.style.overflow = 'visible';
70
+ let errorDiv = document.getElementById('animation-loop-error');
71
+ errorDiv?.remove();
72
+ errorDiv = document.createElement('h1');
73
+ errorDiv.id = 'animation-loop-error';
74
+ errorDiv.innerHTML = error.message;
75
+ errorDiv.style.position = 'absolute';
76
+ errorDiv.style.top = '10px'; // left: 50%; transform: translate(-50%, -50%);';
77
+ errorDiv.style.left = '10px';
78
+ errorDiv.style.color = 'black';
79
+ errorDiv.style.backgroundColor = 'red';
80
+ canvas.parentElement?.appendChild(errorDiv);
81
+ // canvas.style.position = 'absolute';
82
+ }
83
+ }
84
+
85
+ function clearError(device: Device): void {
86
+ const errorDiv = document.getElementById('animation-loop-error');
87
+ if (errorDiv) {
88
+ errorDiv.remove();
89
+ }
90
+ }
@@ -212,7 +212,7 @@ export class AsyncTexture {
212
212
  this._setTexture1DData(this.texture, data as Texture1DData);
213
213
  break;
214
214
  case '2d':
215
- this._setTexture2DData(data as Texture2DData);
215
+ this._setTexture2DData(data);
216
216
  break;
217
217
  case '3d':
218
218
  this._setTexture3DData(this.texture, data as Texture3DData);
@@ -308,8 +308,8 @@ export class AsyncTexture {
308
308
  return this.device.getExternalImageSize(data);
309
309
  }
310
310
  if (data && typeof data === 'object' && data.constructor === Object) {
311
- const textureDataArray = Object.values(data) as Texture2DData[];
312
- const untypedData = textureDataArray[0] as any;
311
+ const textureDataArray = Object.values(data);
312
+ const untypedData = textureDataArray[0];
313
313
  return {width: untypedData.width, height: untypedData.height};
314
314
  }
315
315
  throw new Error('texture size deduction failed');
@@ -10,7 +10,7 @@ import {
10
10
  ComputePass,
11
11
  UniformStore,
12
12
  log,
13
- getTypedArrayFromDataType
13
+ getTypedArrayConstructor
14
14
  } from '@luma.gl/core';
15
15
  import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
16
16
  import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
@@ -340,7 +340,7 @@ export class Computation {
340
340
 
341
341
  // TODO - fix typing of luma data types
342
342
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
343
- const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
343
+ const TypedArrayConstructor = getTypedArrayConstructor(dataType);
344
344
  const typedArray =
345
345
  attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
346
346
  return typedArray.toString();
@@ -19,9 +19,9 @@ export class PipelineFactory {
19
19
 
20
20
  /** Get the singleton default pipeline factory for the specified device */
21
21
  static getDefaultPipelineFactory(device: Device): PipelineFactory {
22
- device._lumaData.defaultPipelineFactory =
23
- device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
24
- return device._lumaData.defaultPipelineFactory as PipelineFactory;
22
+ device._lumaData['defaultPipelineFactory'] =
23
+ device._lumaData['defaultPipelineFactory'] || new PipelineFactory(device);
24
+ return device._lumaData['defaultPipelineFactory'] as PipelineFactory;
25
25
  }
26
26
 
27
27
  readonly device: Device;
@@ -10,8 +10,8 @@ export class ShaderFactory {
10
10
 
11
11
  /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
12
12
  static getDefaultShaderFactory(device: Device): ShaderFactory {
13
- device._lumaData.defaultShaderFactory ||= new ShaderFactory(device);
14
- return device._lumaData.defaultShaderFactory as ShaderFactory;
13
+ device._lumaData['defaultShaderFactory'] ||= new ShaderFactory(device);
14
+ return device._lumaData['defaultShaderFactory'] as ShaderFactory;
15
15
  }
16
16
 
17
17
  public readonly device: Device;
@@ -86,9 +86,9 @@ export class Geometry {
86
86
  }
87
87
  }
88
88
 
89
- if (this.indices && this.indices.isIndexed !== undefined) {
89
+ if (this.indices && this.indices['isIndexed'] !== undefined) {
90
90
  this.indices = Object.assign({}, this.indices);
91
- delete this.indices.isIndexed;
91
+ delete this.indices['isIndexed'];
92
92
  }
93
93
 
94
94
  this.vertexCount = vertexCount || this._calculateVertexCount(this.attributes, this.indices);
@@ -103,6 +103,7 @@ export class Geometry {
103
103
  * TODO Geometry types are a mess
104
104
  */
105
105
  getAttributes(): GeometryAttributes {
106
+ // @ts-ignore
106
107
  return this.indices ? {indices: this.indices, ...this.attributes} : this.attributes;
107
108
  }
108
109
 
@@ -26,9 +26,8 @@ import {
26
26
  RenderPass,
27
27
  UniformStore,
28
28
  log,
29
- getTypedArrayFromDataType,
30
- getAttributeInfosFromLayouts,
31
- _BufferLayoutHelper
29
+ getTypedArrayConstructor,
30
+ getAttributeInfosFromLayouts
32
31
  } from '@luma.gl/core';
33
32
 
34
33
  import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
@@ -41,6 +40,8 @@ import {ShaderFactory} from '../factories/shader-factory';
41
40
  import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
42
41
  import {debugFramebuffer} from '../debug/debug-framebuffer';
43
42
  import {deepEqual} from '../utils/deep-equal';
43
+ import {BufferLayoutHelper} from '../utils/buffer-layout-helper';
44
+ import {sortedBufferLayoutByShaderSourceLocations} from '../utils/buffer-layout-order';
44
45
  import {uid} from '../utils/uid';
45
46
  import {ShaderInputs} from '../shader-inputs';
46
47
  import {AsyncTexture} from '../async-texture/async-texture';
@@ -230,15 +231,19 @@ export class Model {
230
231
  const moduleMap = Object.fromEntries(
231
232
  this.props.modules?.map(module => [module.name, module]) || []
232
233
  );
233
- // @ts-expect-error Fix typings
234
- this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
234
+
235
+ const shaderInputs =
236
+ props.shaderInputs ||
237
+ new ShaderInputs(moduleMap, {disableWarnings: this.props.disableWarnings});
238
+ // @ts-ignore
239
+ this.setShaderInputs(shaderInputs);
235
240
 
236
241
  // Setup shader assembler
237
242
  const platformInfo = getPlatformInfo(device);
238
243
 
239
244
  // Extract modules from shader inputs if not supplied
240
245
  const modules =
241
- // @ts-expect-error shaderInputs is assigned in setShaderInputs above.
246
+ // @ts-ignore shaderInputs is assigned in setShaderInputs above.
242
247
  (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
243
248
 
244
249
  const isWebGPU = this.device.type === 'webgpu';
@@ -248,7 +253,6 @@ export class Model {
248
253
  // TODO - this is wrong, compile a single shader
249
254
  if (isWebGPU && this.props.source) {
250
255
  // WGSL
251
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
252
256
  const {source, getUniforms} = this.props.shaderAssembler.assembleWGSLShader({
253
257
  platformInfo,
254
258
  ...this.props,
@@ -257,6 +261,8 @@ export class Model {
257
261
  this.source = source;
258
262
  // @ts-expect-error
259
263
  this._getModuleUniforms = getUniforms;
264
+ // Extract shader layout after modules have been added to WGSL source, to include any bindings added by modules
265
+ this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.source);
260
266
  } else {
261
267
  // GLSL
262
268
  const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleGLSLShaderPair({
@@ -453,7 +459,7 @@ export class Model {
453
459
  const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
454
460
  if (gpuGeometry) {
455
461
  this.setTopology(gpuGeometry.topology || 'triangle-list');
456
- const bufferLayoutHelper = new _BufferLayoutHelper(this.bufferLayout);
462
+ const bufferLayoutHelper = new BufferLayoutHelper(this.bufferLayout);
457
463
  this.bufferLayout = bufferLayoutHelper.mergeBufferLayouts(
458
464
  gpuGeometry.bufferLayout,
459
465
  this.bufferLayout
@@ -481,7 +487,7 @@ export class Model {
481
487
  * @note Triggers a pipeline rebuild / pipeline cache fetch
482
488
  */
483
489
  setBufferLayout(bufferLayout: BufferLayout[]): void {
484
- const bufferLayoutHelper = new _BufferLayoutHelper(this.bufferLayout);
490
+ const bufferLayoutHelper = new BufferLayoutHelper(this.bufferLayout);
485
491
  this.bufferLayout = this._gpuGeometry
486
492
  ? bufferLayoutHelper.mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
487
493
  : bufferLayout;
@@ -593,13 +599,19 @@ export class Model {
593
599
  */
594
600
  setAttributes(buffers: Record<string, Buffer>, options?: {disableWarnings?: boolean}): void {
595
601
  const disableWarnings = options?.disableWarnings ?? this.props.disableWarnings;
596
- if (buffers.indices) {
602
+ if (buffers['indices']) {
597
603
  log.warn(
598
604
  `Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
599
605
  )();
600
606
  }
601
607
 
602
- const bufferLayoutHelper = new _BufferLayoutHelper(this.bufferLayout);
608
+ // ensure bufferLayout order matches source layout so we bind
609
+ // the correct buffers to the correct indices in webgpu.
610
+ this.bufferLayout = sortedBufferLayoutByShaderSourceLocations(
611
+ this.pipeline.shaderLayout,
612
+ this.bufferLayout
613
+ );
614
+ const bufferLayoutHelper = new BufferLayoutHelper(this.bufferLayout);
603
615
 
604
616
  // Check if all buffers have a layout
605
617
  for (const [bufferName, buffer] of Object.entries(buffers)) {
@@ -611,13 +623,19 @@ export class Model {
611
623
  continue; // eslint-disable-line no-continue
612
624
  }
613
625
 
614
- // For an interleaved attribute we may need to set multiple attributes
626
+ // In WebGL, for an interleaved attribute we may need to set multiple attributes
627
+ // but in WebGPU, we set it according to the buffer's position in the vertexArray
615
628
  const attributeNames = bufferLayoutHelper.getAttributeNamesForBuffer(bufferLayout);
616
629
  let set = false;
617
630
  for (const attributeName of attributeNames) {
618
631
  const attributeInfo = this._attributeInfos[attributeName];
619
632
  if (attributeInfo) {
620
- this.vertexArray.setBuffer(attributeInfo.location, buffer);
633
+ const location =
634
+ this.device.type === 'webgpu'
635
+ ? bufferLayoutHelper.getBufferIndex(attributeInfo.bufferName)
636
+ : attributeInfo.location;
637
+
638
+ this.vertexArray.setBuffer(location, buffer);
621
639
  set = true;
622
640
  }
623
641
  }
@@ -867,7 +885,7 @@ export class Model {
867
885
  indexBuffer.indexType === 'uint32'
868
886
  ? new Uint32Array(indexBuffer.debugData)
869
887
  : new Uint16Array(indexBuffer.debugData);
870
- table.indices = {
888
+ table['indices'] = {
871
889
  name: 'indices',
872
890
  type: indexBuffer.indexType,
873
891
  values: values.toString()
@@ -878,7 +896,7 @@ export class Model {
878
896
 
879
897
  // TODO - fix typing of luma data types
880
898
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
881
- const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
899
+ const TypedArrayConstructor = getTypedArrayConstructor(dataType);
882
900
  const typedArray =
883
901
  attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
884
902
  return typedArray.toString();
@@ -80,7 +80,7 @@ export class PickingManager {
80
80
  return pickingPass;
81
81
  }
82
82
 
83
- getPickInfo(mousePosition: [number, number]): PickInfo | null {
83
+ async updatePickInfo(mousePosition: [number, number]): Promise<PickInfo | null> {
84
84
  const framebuffer = this.getFramebuffer();
85
85
 
86
86
  // use the center pixel location in device pixel range
@@ -8,6 +8,10 @@ import {log} from '@luma.gl/core';
8
8
  import {getShaderModuleDependencies, ShaderModule} from '@luma.gl/shadertools';
9
9
  import {splitUniformsAndBindings} from './model/split-uniforms-and-bindings';
10
10
 
11
+ export type ShaderInputsOptions = {
12
+ disableWarnings?: boolean;
13
+ };
14
+
11
15
  /**
12
16
  * ShaderInputs holds uniform and binding values for one or more shader modules,
13
17
  * - It can generate binary data for any uniform buffer
@@ -20,11 +24,15 @@ export class ShaderInputs<
20
24
  Record<string, Record<string, unknown>>
21
25
  >
22
26
  > {
27
+ options: Required<ShaderInputsOptions> = {
28
+ disableWarnings: false
29
+ };
30
+
23
31
  /**
24
32
  * The map of modules
25
33
  * @todo should should this include the resolved dependencies?
26
34
  */
27
- // @ts-expect-error Fix typings
35
+ // @ts-ignore Fix typings
28
36
  modules: Readonly<{[P in keyof ShaderPropsT]: ShaderModule<ShaderPropsT[P]>}>;
29
37
 
30
38
  /** Stores the uniform values for each module */
@@ -38,8 +46,13 @@ export class ShaderInputs<
38
46
  * Create a new UniformStore instance
39
47
  * @param modules
40
48
  */
41
- // @ts-expect-error Fix typings
42
- constructor(modules: {[P in keyof ShaderPropsT]?: ShaderModule<ShaderPropsT[P], any>}) {
49
+ constructor(
50
+ // @ts-ignore Fix typings
51
+ modules: {[P in keyof ShaderPropsT]?: ShaderModule<ShaderPropsT[P], any>},
52
+ options?: ShaderInputsOptions
53
+ ) {
54
+ Object.assign(this.options, options);
55
+
43
56
  // Extract modules with dependencies
44
57
  const resolvedModules = getShaderModuleDependencies(
45
58
  Object.values(modules).filter(module => module.dependencies)
@@ -52,7 +65,7 @@ export class ShaderInputs<
52
65
  log.log(1, 'Creating ShaderInputs with modules', Object.keys(modules))();
53
66
 
54
67
  // Store the module definitions and create storage for uniform values and binding values, per module
55
- // @ts-expect-error Fix typings
68
+ // @ts-ignore Fix typings
56
69
  this.modules = modules as {[P in keyof ShaderPropsT]: ShaderModule<ShaderPropsT[P]>};
57
70
  this.moduleUniforms = {} as Record<keyof ShaderPropsT, Record<string, UniformValue>>;
58
71
  this.moduleBindings = {} as Record<keyof ShaderPropsT, Record<string, Binding>>;
@@ -60,7 +73,7 @@ export class ShaderInputs<
60
73
  // Initialize the modules
61
74
  for (const [name, module] of Object.entries(modules)) {
62
75
  this._addModule(module);
63
- if (module.name && name !== module.name) {
76
+ if (module.name && name !== module.name && !this.options.disableWarnings) {
64
77
  log.warn(`Module name: ${name} vs ${module.name}`)();
65
78
  }
66
79
  }
@@ -79,7 +92,9 @@ export class ShaderInputs<
79
92
  const module = this.modules[moduleName];
80
93
  if (!module) {
81
94
  // Ignore props for unregistered modules
82
- log.warn(`Module ${name} not found`)();
95
+ if (!this.options.disableWarnings) {
96
+ log.warn(`Module ${name} not found`)();
97
+ }
83
98
  continue; // eslint-disable-line no-continue
84
99
  }
85
100
 
@@ -0,0 +1,51 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {log, type BufferLayout} from '@luma.gl/core';
6
+
7
+ /** BufferLayoutHelper is a helper class that should not be used directly by applications */
8
+ export class BufferLayoutHelper {
9
+ bufferLayouts: BufferLayout[];
10
+
11
+ constructor(bufferLayouts: BufferLayout[]) {
12
+ this.bufferLayouts = bufferLayouts;
13
+ }
14
+
15
+ getBufferLayout(name: string): BufferLayout | null {
16
+ return this.bufferLayouts.find(layout => layout.name === name) || null;
17
+ }
18
+
19
+ /** Get attribute names from a BufferLayout */
20
+ getAttributeNamesForBuffer(bufferLayout: BufferLayout): string[] {
21
+ return bufferLayout.attributes
22
+ ? bufferLayout.attributes?.map(layout => layout.attribute)
23
+ : [bufferLayout.name];
24
+ }
25
+
26
+ mergeBufferLayouts(
27
+ bufferLayouts1: BufferLayout[],
28
+ bufferLayouts2: BufferLayout[]
29
+ ): BufferLayout[] {
30
+ const mergedLayouts = [...bufferLayouts1];
31
+ for (const attribute of bufferLayouts2) {
32
+ const index = mergedLayouts.findIndex(attribute2 => attribute2.name === attribute.name);
33
+ if (index < 0) {
34
+ mergedLayouts.push(attribute);
35
+ } else {
36
+ mergedLayouts[index] = attribute;
37
+ }
38
+ }
39
+ return mergedLayouts;
40
+ }
41
+
42
+ getBufferIndex(bufferName: string): number {
43
+ const bufferIndex = this.bufferLayouts.findIndex(layout => layout.name === bufferName);
44
+
45
+ if (bufferIndex === -1) {
46
+ log.warn(`BufferLayout: Missing buffer for "${bufferName}".`)();
47
+ }
48
+
49
+ return bufferIndex;
50
+ }
51
+ }
@@ -0,0 +1,26 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {type BufferLayout, type ShaderLayout} from '@luma.gl/core';
6
+
7
+ export function sortedBufferLayoutByShaderSourceLocations(
8
+ shaderLayout: ShaderLayout,
9
+ bufferLayout: BufferLayout[]
10
+ ): BufferLayout[] {
11
+ const shaderLayoutMap = Object.fromEntries(
12
+ shaderLayout.attributes.map(attr => [attr.name, attr.location])
13
+ );
14
+
15
+ const sortedLayout = bufferLayout.slice();
16
+ sortedLayout.sort((a, b) => {
17
+ const attributeNamesA = a.attributes ? a.attributes.map(attr => attr.attribute) : [a.name];
18
+ const attributeNamesB = b.attributes ? b.attributes.map(attr => attr.attribute) : [b.name];
19
+ const minLocationA = Math.min(...attributeNamesA.map(name => shaderLayoutMap[name]));
20
+ const minLocationB = Math.min(...attributeNamesB.map(name => shaderLayoutMap[name]));
21
+
22
+ return minLocationA - minLocationB;
23
+ });
24
+
25
+ return sortedLayout;
26
+ }