@luma.gl/engine 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 (186) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +11 -5
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +83 -47
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/compute/computation.d.ts +3 -7
  11. package/dist/compute/computation.d.ts.map +1 -1
  12. package/dist/compute/computation.js +16 -13
  13. package/dist/compute/computation.js.map +1 -1
  14. package/dist/compute/swap.d.ts +2 -0
  15. package/dist/compute/swap.d.ts.map +1 -1
  16. package/dist/compute/swap.js +10 -5
  17. package/dist/compute/swap.js.map +1 -1
  18. package/dist/dist.dev.js +2639 -1290
  19. package/dist/dist.min.js +325 -210
  20. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  21. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  22. package/dist/dynamic-texture/dynamic-texture.js +556 -0
  23. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  24. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  25. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  26. package/dist/dynamic-texture/texture-data.js +208 -0
  27. package/dist/dynamic-texture/texture-data.js.map +1 -0
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  35. package/dist/geometry/gpu-geometry.js +8 -3
  36. package/dist/geometry/gpu-geometry.js.map +1 -1
  37. package/dist/index.cjs +2497 -1212
  38. package/dist/index.cjs.map +4 -4
  39. package/dist/index.d.ts +20 -6
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/material/material-factory.d.ts +73 -0
  44. package/dist/material/material-factory.d.ts.map +1 -0
  45. package/dist/material/material-factory.js +111 -0
  46. package/dist/material/material-factory.js.map +1 -0
  47. package/dist/material/material.d.ts +84 -0
  48. package/dist/material/material.d.ts.map +1 -0
  49. package/dist/material/material.js +176 -0
  50. package/dist/material/material.js.map +1 -0
  51. package/dist/model/model.d.ts +47 -16
  52. package/dist/model/model.d.ts.map +1 -1
  53. package/dist/model/model.js +113 -47
  54. package/dist/model/model.js.map +1 -1
  55. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  56. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  57. package/dist/model/split-uniforms-and-bindings.js +2 -2
  58. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  59. package/dist/models/billboard-texture-model.d.ts +8 -5
  60. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  61. package/dist/models/billboard-texture-model.js +77 -23
  62. package/dist/models/billboard-texture-model.js.map +1 -1
  63. package/dist/models/billboard-texture-module.d.ts +1 -1
  64. package/dist/models/billboard-texture-module.js +1 -1
  65. package/dist/models/clip-space.js +7 -7
  66. package/dist/models/directional-light-model.d.ts +7 -0
  67. package/dist/models/directional-light-model.d.ts.map +1 -0
  68. package/dist/models/directional-light-model.js +23 -0
  69. package/dist/models/directional-light-model.js.map +1 -0
  70. package/dist/models/light-model-utils.d.ts +69 -0
  71. package/dist/models/light-model-utils.d.ts.map +1 -0
  72. package/dist/models/light-model-utils.js +395 -0
  73. package/dist/models/light-model-utils.js.map +1 -0
  74. package/dist/models/point-light-model.d.ts +7 -0
  75. package/dist/models/point-light-model.d.ts.map +1 -0
  76. package/dist/models/point-light-model.js +22 -0
  77. package/dist/models/point-light-model.js.map +1 -0
  78. package/dist/models/spot-light-model.d.ts +7 -0
  79. package/dist/models/spot-light-model.d.ts.map +1 -0
  80. package/dist/models/spot-light-model.js +23 -0
  81. package/dist/models/spot-light-model.js.map +1 -0
  82. package/dist/modules/picking/color-picking.d.ts +5 -9
  83. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  84. package/dist/modules/picking/color-picking.js +122 -115
  85. package/dist/modules/picking/color-picking.js.map +1 -1
  86. package/dist/modules/picking/index-picking.d.ts +4 -4
  87. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  88. package/dist/modules/picking/index-picking.js +36 -16
  89. package/dist/modules/picking/index-picking.js.map +1 -1
  90. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  91. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  92. package/dist/modules/picking/legacy-color-picking.js +7 -0
  93. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  94. package/dist/modules/picking/picking-manager.d.ts +29 -3
  95. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  96. package/dist/modules/picking/picking-manager.js +188 -41
  97. package/dist/modules/picking/picking-manager.js.map +1 -1
  98. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  99. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  100. package/dist/modules/picking/picking-uniforms.js +27 -14
  101. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  102. package/dist/modules/picking/picking.d.ts +25 -0
  103. package/dist/modules/picking/picking.d.ts.map +1 -0
  104. package/dist/modules/picking/picking.js +18 -0
  105. package/dist/modules/picking/picking.js.map +1 -0
  106. package/dist/passes/get-fragment-shader.js +12 -27
  107. package/dist/passes/get-fragment-shader.js.map +1 -1
  108. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  109. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  110. package/dist/passes/shader-pass-renderer.js +16 -42
  111. package/dist/passes/shader-pass-renderer.js.map +1 -1
  112. package/dist/scenegraph/group-node.d.ts +5 -0
  113. package/dist/scenegraph/group-node.d.ts.map +1 -1
  114. package/dist/scenegraph/group-node.js +12 -0
  115. package/dist/scenegraph/group-node.js.map +1 -1
  116. package/dist/scenegraph/model-node.d.ts +2 -2
  117. package/dist/scenegraph/model-node.d.ts.map +1 -1
  118. package/dist/scenegraph/model-node.js.map +1 -1
  119. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  120. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  121. package/dist/scenegraph/scenegraph-node.js +23 -15
  122. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  123. package/dist/shader-inputs.d.ts +9 -7
  124. package/dist/shader-inputs.d.ts.map +1 -1
  125. package/dist/shader-inputs.js +84 -4
  126. package/dist/shader-inputs.js.map +1 -1
  127. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  128. package/dist/utils/buffer-layout-order.js +12 -2
  129. package/dist/utils/buffer-layout-order.js.map +1 -1
  130. package/dist/utils/shader-module-utils.d.ts +7 -0
  131. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  132. package/dist/utils/shader-module-utils.js +46 -0
  133. package/dist/utils/shader-module-utils.js.map +1 -0
  134. package/package.json +6 -6
  135. package/src/animation-loop/animation-loop.ts +89 -50
  136. package/src/animation-loop/make-animation-loop.ts +13 -5
  137. package/src/animation-loop/request-animation-frame.ts +32 -6
  138. package/src/compute/computation.ts +32 -17
  139. package/src/compute/swap.ts +13 -7
  140. package/src/dynamic-texture/dynamic-texture.ts +732 -0
  141. package/src/dynamic-texture/texture-data.ts +336 -0
  142. package/src/geometries/cone-geometry.ts +6 -1
  143. package/src/geometries/cylinder-geometry.ts +5 -1
  144. package/src/geometry/gpu-geometry.ts +8 -3
  145. package/src/index.ts +38 -8
  146. package/src/material/material-factory.ts +157 -0
  147. package/src/material/material.ts +254 -0
  148. package/src/model/model.ts +158 -67
  149. package/src/model/split-uniforms-and-bindings.ts +8 -6
  150. package/src/models/billboard-texture-model.ts +88 -27
  151. package/src/models/billboard-texture-module.ts +1 -1
  152. package/src/models/clip-space.ts +7 -7
  153. package/src/models/directional-light-model.ts +32 -0
  154. package/src/models/light-model-utils.ts +587 -0
  155. package/src/models/point-light-model.ts +31 -0
  156. package/src/models/spot-light-model.ts +32 -0
  157. package/src/modules/picking/color-picking.ts +123 -122
  158. package/src/modules/picking/index-picking.ts +36 -16
  159. package/src/modules/picking/legacy-color-picking.ts +8 -0
  160. package/src/modules/picking/picking-manager.ts +252 -50
  161. package/src/modules/picking/picking-uniforms.ts +39 -24
  162. package/src/modules/picking/picking.ts +22 -0
  163. package/src/passes/get-fragment-shader.ts +12 -27
  164. package/src/passes/shader-pass-renderer.ts +25 -48
  165. package/src/scenegraph/group-node.ts +16 -0
  166. package/src/scenegraph/model-node.ts +2 -2
  167. package/src/scenegraph/scenegraph-node.ts +27 -16
  168. package/src/shader-inputs.ts +165 -15
  169. package/src/utils/buffer-layout-order.ts +18 -2
  170. package/src/utils/shader-module-utils.ts +65 -0
  171. package/dist/async-texture/async-texture.d.ts +0 -166
  172. package/dist/async-texture/async-texture.d.ts.map +0 -1
  173. package/dist/async-texture/async-texture.js +0 -386
  174. package/dist/async-texture/async-texture.js.map +0 -1
  175. package/dist/factories/pipeline-factory.d.ts +0 -37
  176. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  177. package/dist/factories/pipeline-factory.js +0 -181
  178. package/dist/factories/pipeline-factory.js.map +0 -1
  179. package/dist/factories/shader-factory.d.ts +0 -22
  180. package/dist/factories/shader-factory.d.ts.map +0 -1
  181. package/dist/factories/shader-factory.js +0 -88
  182. package/dist/factories/shader-factory.js.map +0 -1
  183. package/src/async-texture/async-texture.ts +0 -551
  184. package/src/factories/pipeline-factory.ts +0 -224
  185. package/src/factories/shader-factory.ts +0 -103
  186. /package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +0 -0
@@ -3,19 +3,18 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  // A lot of imports, but then Model is where it all comes together...
6
- import type {TypedArray} from '@math.gl/types';
7
- import type {
8
- RenderPipelineProps,
9
- RenderPipelineParameters,
10
- BufferLayout,
11
- Shader,
12
- VertexArray,
13
- TransformFeedback,
14
- AttributeInfo,
15
- Binding,
16
- PrimitiveTopology
17
- } from '@luma.gl/core';
6
+ import {type TypedArray} from '@math.gl/types';
18
7
  import {
8
+ type RenderPipelineProps,
9
+ type RenderPipelineParameters,
10
+ type BufferLayout,
11
+ type Shader,
12
+ type VertexArray,
13
+ type TransformFeedback,
14
+ type AttributeInfo,
15
+ type Binding,
16
+ type BindingsByGroup,
17
+ type PrimitiveTopology,
19
18
  Device,
20
19
  DeviceFeature,
21
20
  Buffer,
@@ -24,27 +23,33 @@ import {
24
23
  Sampler,
25
24
  RenderPipeline,
26
25
  RenderPass,
26
+ PipelineFactory,
27
+ ShaderFactory,
27
28
  UniformStore,
28
29
  log,
29
- getTypedArrayConstructor,
30
- getAttributeInfosFromLayouts
30
+ dataTypeDecoder,
31
+ getAttributeInfosFromLayouts,
32
+ normalizeBindingsByGroup
31
33
  } from '@luma.gl/core';
32
34
 
33
- import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
34
- import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
35
+ import type {ShaderBindingDebugRow, ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
36
+ import {ShaderAssembler} from '@luma.gl/shadertools';
35
37
 
36
38
  import type {Geometry} from '../geometry/geometry';
37
39
  import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
38
- import {PipelineFactory} from '../factories/pipeline-factory';
39
- import {ShaderFactory} from '../factories/shader-factory';
40
40
  import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
41
41
  import {debugFramebuffer} from '../debug/debug-framebuffer';
42
42
  import {deepEqual} from '../utils/deep-equal';
43
43
  import {BufferLayoutHelper} from '../utils/buffer-layout-helper';
44
44
  import {sortedBufferLayoutByShaderSourceLocations} from '../utils/buffer-layout-order';
45
+ import {
46
+ mergeShaderModuleBindingsIntoLayout,
47
+ shaderModuleHasUniforms
48
+ } from '../utils/shader-module-utils';
45
49
  import {uid} from '../utils/uid';
46
50
  import {ShaderInputs} from '../shader-inputs';
47
- import {AsyncTexture} from '../async-texture/async-texture';
51
+ import {DynamicTexture} from '../dynamic-texture/dynamic-texture';
52
+ import {Material} from '../material/material';
48
53
 
49
54
  const LOG_DRAW_PRIORITY = 2;
50
55
  const LOG_DRAW_TIMEOUT = 10000;
@@ -62,8 +67,12 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
62
67
 
63
68
  /** Shader inputs, used to generated uniform buffers and bindings */
64
69
  shaderInputs?: ShaderInputs;
70
+ /** Material-owned group-3 bindings */
71
+ material?: Material;
65
72
  /** Bindings */
66
- bindings?: Record<string, Binding | AsyncTexture>;
73
+ bindings?: Record<string, Binding | DynamicTexture>;
74
+ /** WebGL-only uniforms */
75
+ uniforms?: Record<string, unknown>;
67
76
  /** Parameters that are built into the pipeline */
68
77
  parameters?: RenderPipelineParameters;
69
78
 
@@ -103,12 +112,19 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
103
112
  };
104
113
 
105
114
  /**
106
- * v9 Model API
107
- * A model
108
- * - automatically reuses pipelines (programs) when possible
109
- * - automatically rebuilds pipelines if necessary to accommodate changed settings
110
- * shadertools integration
111
- * - accepts modules and performs shader transpilation
115
+ * High level draw API for luma.gl.
116
+ *
117
+ * A `Model` encapsulates shaders, geometry attributes, bindings and render
118
+ * pipeline state into a single object. It automatically reuses and rebuilds
119
+ * pipelines as render parameters change and exposes convenient hooks for
120
+ * updating uniforms and attributes.
121
+ *
122
+ * Features:
123
+ * - Reuses and lazily recompiles {@link RenderPipeline | pipelines} as needed.
124
+ * - Integrates with `@luma.gl/shadertools` to assemble GLSL or WGSL from shader modules.
125
+ * - Manages geometry attributes and buffer bindings.
126
+ * - Accepts textures, samplers and uniform buffers as bindings, including `DynamicTexture`.
127
+ * - Provides detailed debug logging and optional shader source inspection.
112
128
  */
113
129
  export class Model {
114
130
  static defaultProps: Required<ModelProps> = {
@@ -125,6 +141,8 @@ export class Model {
125
141
  indexBuffer: null,
126
142
  attributes: {},
127
143
  constantAttributes: {},
144
+ bindings: {},
145
+ uniforms: {},
128
146
  varyings: [],
129
147
 
130
148
  isInstanced: undefined!,
@@ -132,6 +150,7 @@ export class Model {
132
150
  vertexCount: 0,
133
151
 
134
152
  shaderInputs: undefined!,
153
+ material: undefined!,
135
154
  pipelineFactory: undefined!,
136
155
  shaderFactory: undefined!,
137
156
  transformFeedback: undefined!,
@@ -141,16 +160,24 @@ export class Model {
141
160
  disableWarnings: undefined!
142
161
  };
143
162
 
163
+ /** Device that created this model */
144
164
  readonly device: Device;
165
+ /** Application provided identifier */
145
166
  readonly id: string;
167
+ /** WGSL shader source when using unified shader */
146
168
  // @ts-expect-error assigned in function called from constructor
147
169
  readonly source: string;
170
+ /** GLSL vertex shader source */
148
171
  // @ts-expect-error assigned in function called from constructor
149
172
  readonly vs: string;
173
+ /** GLSL fragment shader source */
150
174
  // @ts-expect-error assigned in function called from constructor
151
175
  readonly fs: string;
176
+ /** Factory used to create render pipelines */
152
177
  readonly pipelineFactory: PipelineFactory;
178
+ /** Factory used to create shaders */
153
179
  readonly shaderFactory: ShaderFactory;
180
+ /** User-supplied per-model data */
154
181
  userData: {[key: string]: any} = {};
155
182
 
156
183
  // Fixed properties (change can trigger pipeline rebuild)
@@ -179,7 +206,7 @@ export class Model {
179
206
  /** Constant-valued attributes */
180
207
  constantAttributes: Record<string, TypedArray> = {};
181
208
  /** Bindings (textures, samplers, uniform buffers) */
182
- bindings: Record<string, Binding | AsyncTexture> = {};
209
+ bindings: Record<string, Binding | DynamicTexture> = {};
183
210
 
184
211
  /**
185
212
  * VertexArray
@@ -197,6 +224,7 @@ export class Model {
197
224
  /** ShaderInputs instance */
198
225
  // @ts-expect-error Assigned in function called by constructor
199
226
  shaderInputs: ShaderInputs;
227
+ material: Material | null = null;
200
228
  // @ts-expect-error Assigned in function called by constructor
201
229
  _uniformStore: UniformStore;
202
230
 
@@ -210,6 +238,7 @@ export class Model {
210
238
 
211
239
  /** "Time" of last draw. Monotonically increasing timestamp */
212
240
  _lastDrawTimestamp: number = -1;
241
+ private _bindingTable: ShaderBindingDebugRow[] = [];
213
242
 
214
243
  get [Symbol.toStringTag](): string {
215
244
  return 'Model';
@@ -227,6 +256,8 @@ export class Model {
227
256
 
228
257
  Object.assign(this.userData, props.userData);
229
258
 
259
+ this.material = props.material || null;
260
+
230
261
  // Setup shader module inputs
231
262
  const moduleMap = Object.fromEntries(
232
263
  this.props.modules?.map(module => [module.name, module]) || []
@@ -246,6 +277,9 @@ export class Model {
246
277
  // @ts-ignore shaderInputs is assigned in setShaderInputs above.
247
278
  (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
248
279
 
280
+ this.props.shaderLayout =
281
+ mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout, modules) || null;
282
+
249
283
  const isWebGPU = this.device.type === 'webgpu';
250
284
 
251
285
  // WebGPU
@@ -253,7 +287,7 @@ export class Model {
253
287
  // TODO - this is wrong, compile a single shader
254
288
  if (isWebGPU && this.props.source) {
255
289
  // WGSL
256
- const {source, getUniforms} = this.props.shaderAssembler.assembleWGSLShader({
290
+ const {source, getUniforms, bindingTable} = this.props.shaderAssembler.assembleWGSLShader({
257
291
  platformInfo,
258
292
  ...this.props,
259
293
  modules
@@ -261,8 +295,16 @@ export class Model {
261
295
  this.source = source;
262
296
  // @ts-expect-error
263
297
  this._getModuleUniforms = getUniforms;
298
+ this._bindingTable = bindingTable;
264
299
  // 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);
300
+ const inferredShaderLayout = (
301
+ device as Device & {getShaderLayout?: (source: string) => any}
302
+ ).getShaderLayout?.(this.source);
303
+ this.props.shaderLayout =
304
+ mergeShaderModuleBindingsIntoLayout(
305
+ this.props.shaderLayout || inferredShaderLayout || null,
306
+ modules
307
+ ) || null;
266
308
  } else {
267
309
  // GLSL
268
310
  const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleGLSLShaderPair({
@@ -275,6 +317,7 @@ export class Model {
275
317
  this.fs = fs;
276
318
  // @ts-expect-error
277
319
  this._getModuleUniforms = getUniforms;
320
+ this._bindingTable = [];
278
321
  }
279
322
 
280
323
  this.vertexCount = this.props.vertexCount;
@@ -333,9 +376,6 @@ export class Model {
333
376
  if (props.transformFeedback) {
334
377
  this.transformFeedback = props.transformFeedback;
335
378
  }
336
-
337
- // Catch any access to non-standard props
338
- Object.seal(this);
339
379
  }
340
380
 
341
381
  destroy(): void {
@@ -344,7 +384,7 @@ export class Model {
344
384
  this.pipelineFactory.release(this.pipeline);
345
385
  // Release the shaders
346
386
  this.shaderFactory.release(this.pipeline.vs);
347
- if (this.pipeline.fs) {
387
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
348
388
  this.shaderFactory.release(this.pipeline.fs);
349
389
  }
350
390
  this._uniformStore.destroy();
@@ -372,6 +412,12 @@ export class Model {
372
412
  this._needsRedraw ||= reason;
373
413
  }
374
414
 
415
+ /** Returns WGSL binding debug rows for the assembled shader. Returns an empty array for GLSL models. */
416
+ getBindingDebugTable(): readonly ShaderBindingDebugRow[] {
417
+ return this._bindingTable;
418
+ }
419
+
420
+ /** Update uniforms and pipeline state prior to drawing. */
375
421
  predraw(): void {
376
422
  // Update uniform buffers if needed
377
423
  this.updateShaderInputs();
@@ -379,6 +425,11 @@ export class Model {
379
425
  this.pipeline = this._updatePipeline();
380
426
  }
381
427
 
428
+ /**
429
+ * Issue one draw call.
430
+ * @param renderPass - render pass to draw into
431
+ * @returns `true` if the draw call was executed, `false` if resources were not ready.
432
+ */
382
433
  draw(renderPass: RenderPass): boolean {
383
434
  const loadingBinding = this._areBindingsLoading();
384
435
  if (loadingBinding) {
@@ -403,14 +454,8 @@ export class Model {
403
454
  // Application can call Model.predraw() to avoid this.
404
455
  this.pipeline = this._updatePipeline();
405
456
 
406
- // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
407
- // Any caching needs to be done inside the pipeline functions
408
- // TODO this is a busy initialized check for all bindings every frame
409
-
410
457
  const syncBindings = this._getBindings();
411
- this.pipeline.setBindings(syncBindings, {
412
- disableWarnings: this.props.disableWarnings
413
- });
458
+ const syncBindGroups = this._getBindGroups();
414
459
 
415
460
  const {indexBuffer} = this.vertexArray;
416
461
  const indexCount = indexBuffer
@@ -425,6 +470,13 @@ export class Model {
425
470
  instanceCount: this.instanceCount,
426
471
  indexCount,
427
472
  transformFeedback: this.transformFeedback || undefined,
473
+ // Pipelines may be shared across models when caching is enabled, so bindings
474
+ // and WebGL uniforms must be supplied on every draw instead of being stored
475
+ // on the pipeline instance.
476
+ bindings: syncBindings,
477
+ bindGroups: syncBindGroups,
478
+ _bindGroupCacheKeys: this._getBindGroupCacheKeys(),
479
+ uniforms: this.props.uniforms,
428
480
  // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
429
481
  // so we must provide our unique parameters to each draw
430
482
  // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
@@ -549,21 +601,26 @@ export class Model {
549
601
  /** Set the shader inputs */
550
602
  setShaderInputs(shaderInputs: ShaderInputs): void {
551
603
  this.shaderInputs = shaderInputs;
552
- this._uniformStore = new UniformStore(this.shaderInputs.modules);
604
+ this._uniformStore = new UniformStore(this.device, this.shaderInputs.modules);
553
605
  // Create uniform buffer bindings for all modules that actually have uniforms
554
606
  for (const [moduleName, module] of Object.entries(this.shaderInputs.modules)) {
555
- if (shaderModuleHasUniforms(module)) {
556
- const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
607
+ if (shaderModuleHasUniforms(module) && !this.material?.ownsModule(moduleName)) {
608
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName);
557
609
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
558
610
  }
559
611
  }
560
612
  this.setNeedsRedraw('shaderInputs');
561
613
  }
562
614
 
615
+ setMaterial(material: Material | null): void {
616
+ this.material = material;
617
+ this.setNeedsRedraw('material');
618
+ }
619
+
563
620
  /** Update uniform buffers from the model's shader inputs */
564
621
  updateShaderInputs(): void {
565
622
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
566
- this.setBindings(this.shaderInputs.getBindingValues());
623
+ this.setBindings(this._getNonMaterialBindings(this.shaderInputs.getBindingValues()));
567
624
  // TODO - this is already tracked through buffer/texture update times?
568
625
  this.setNeedsRedraw('shaderInputs');
569
626
  }
@@ -571,7 +628,7 @@ export class Model {
571
628
  /**
572
629
  * Sets bindings (textures, samplers, uniform buffers)
573
630
  */
574
- setBindings(bindings: Record<string, Binding | AsyncTexture>): void {
631
+ setBindings(bindings: Record<string, Binding | DynamicTexture>): void {
575
632
  Object.assign(this.bindings, bindings);
576
633
  this.setNeedsRedraw('bindings');
577
634
  }
@@ -678,7 +735,12 @@ export class Model {
678
735
  /** Check that bindings are loaded. Returns id of first binding that is still loading. */
679
736
  _areBindingsLoading(): string | false {
680
737
  for (const binding of Object.values(this.bindings)) {
681
- if (binding instanceof AsyncTexture && !binding.isReady) {
738
+ if (binding instanceof DynamicTexture && !binding.isReady) {
739
+ return binding.id;
740
+ }
741
+ }
742
+ for (const binding of Object.values(this.material?.bindings || {})) {
743
+ if (binding instanceof DynamicTexture && !binding.isReady) {
682
744
  return binding.id;
683
745
  }
684
746
  }
@@ -690,7 +752,7 @@ export class Model {
690
752
  const validBindings: Record<string, Binding> = {};
691
753
 
692
754
  for (const [name, binding] of Object.entries(this.bindings)) {
693
- if (binding instanceof AsyncTexture) {
755
+ if (binding instanceof DynamicTexture) {
694
756
  // Check that async textures are loaded
695
757
  if (binding.isReady) {
696
758
  validBindings[name] = binding.texture;
@@ -703,6 +765,32 @@ export class Model {
703
765
  return validBindings;
704
766
  }
705
767
 
768
+ _getBindGroups(): BindingsByGroup {
769
+ const shaderLayout = this.pipeline?.shaderLayout || this.props.shaderLayout || {bindings: []};
770
+ const bindGroups = shaderLayout.bindings.length
771
+ ? normalizeBindingsByGroup(shaderLayout, this._getBindings())
772
+ : {0: this._getBindings()};
773
+
774
+ if (!this.material) {
775
+ return bindGroups;
776
+ }
777
+
778
+ for (const [groupKey, groupBindings] of Object.entries(this.material.getBindingsByGroup())) {
779
+ const group = Number(groupKey);
780
+ bindGroups[group] = {
781
+ ...(bindGroups[group] || {}),
782
+ ...groupBindings
783
+ };
784
+ }
785
+
786
+ return bindGroups;
787
+ }
788
+
789
+ _getBindGroupCacheKeys(): Partial<Record<number, object>> {
790
+ const bindGroupCacheKey = this.material?.getBindGroupCacheKey(3);
791
+ return bindGroupCacheKey ? {3: bindGroupCacheKey} : {};
792
+ }
793
+
706
794
  /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
707
795
  _getBindingsUpdateTimestamp(): number {
708
796
  let timestamp = 0;
@@ -711,7 +799,7 @@ export class Model {
711
799
  timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
712
800
  } else if (binding instanceof Buffer || binding instanceof Texture) {
713
801
  timestamp = Math.max(timestamp, binding.updateTimestamp);
714
- } else if (binding instanceof AsyncTexture) {
802
+ } else if (binding instanceof DynamicTexture) {
715
803
  timestamp = binding.texture
716
804
  ? Math.max(timestamp, binding.texture.updateTimestamp)
717
805
  : // The texture will become available in the future
@@ -720,7 +808,7 @@ export class Model {
720
808
  timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
721
809
  }
722
810
  }
723
- return timestamp;
811
+ return Math.max(timestamp, this.material?.getBindingsUpdateTimestamp() || 0);
724
812
  }
725
813
 
726
814
  /**
@@ -792,12 +880,11 @@ export class Model {
792
880
 
793
881
  this.pipeline = this.pipelineFactory.createRenderPipeline({
794
882
  ...this.props,
883
+ bindings: undefined,
795
884
  bufferLayout: this.bufferLayout,
796
885
  topology: this.topology,
797
886
  parameters: this.parameters,
798
- // TODO - why set bindings here when we reset them every frame?
799
- // Should we expose a BindGroup abstraction?
800
- bindings: this._getBindings(),
887
+ bindGroups: this._getBindGroups(),
801
888
  vs,
802
889
  fs
803
890
  });
@@ -808,7 +895,9 @@ export class Model {
808
895
  );
809
896
 
810
897
  if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
811
- if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
898
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
899
+ this.shaderFactory.release(prevShaderFs);
900
+ }
812
901
  }
813
902
  return this.pipeline;
814
903
  }
@@ -896,15 +985,27 @@ export class Model {
896
985
 
897
986
  // TODO - fix typing of luma data types
898
987
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
899
- const TypedArrayConstructor = getTypedArrayConstructor(dataType);
988
+ const TypedArrayConstructor = dataTypeDecoder.getTypedArrayConstructor(dataType);
900
989
  const typedArray =
901
990
  attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
902
991
  return typedArray.toString();
903
992
  }
904
- }
905
993
 
906
- function shaderModuleHasUniforms(module: ShaderModule): boolean {
907
- return Boolean(module.uniformTypes && !isObjectEmpty(module.uniformTypes));
994
+ private _getNonMaterialBindings(
995
+ bindings: Record<string, Binding | DynamicTexture>
996
+ ): Record<string, Binding | DynamicTexture> {
997
+ if (!this.material) {
998
+ return bindings;
999
+ }
1000
+
1001
+ const filteredBindings: Record<string, Binding | DynamicTexture> = {};
1002
+ for (const [name, binding] of Object.entries(bindings)) {
1003
+ if (!this.material.ownsBinding(name)) {
1004
+ filteredBindings[name] = binding;
1005
+ }
1006
+ }
1007
+ return filteredBindings;
1008
+ }
908
1009
  }
909
1010
 
910
1011
  // HELPERS
@@ -920,13 +1021,3 @@ export function getPlatformInfo(device: Device): PlatformInfo {
920
1021
  features: device.features as unknown as Set<DeviceFeature>
921
1022
  };
922
1023
  }
923
-
924
- /** Returns true if given object is empty, false otherwise. */
925
- function isObjectEmpty(obj: object): boolean {
926
- // @ts-ignore key is unused
927
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
928
- for (const key in obj) {
929
- return false;
930
- }
931
- return true;
932
- }
@@ -2,7 +2,8 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import type {UniformValue, Binding} from '@luma.gl/core';
5
+ import type {Binding, CompositeShaderType, UniformValue} from '@luma.gl/core';
6
+ import type {ShaderModuleUniformValue} from '@luma.gl/shadertools';
6
7
  import {isNumericArray} from '@math.gl/types';
7
8
 
8
9
  export function isUniformValue(value: unknown): value is UniformValue {
@@ -11,19 +12,20 @@ export function isUniformValue(value: unknown): value is UniformValue {
11
12
 
12
13
  type UniformsAndBindings = {
13
14
  bindings: Record<string, Binding>;
14
- uniforms: Record<string, UniformValue>;
15
+ uniforms: Record<string, ShaderModuleUniformValue>;
15
16
  };
16
17
 
17
18
  export function splitUniformsAndBindings(
18
- uniforms: Record<string, Binding | UniformValue>
19
+ uniforms: Record<string, Binding | ShaderModuleUniformValue>,
20
+ uniformTypes: Readonly<Record<string, CompositeShaderType>> = {}
19
21
  ): UniformsAndBindings {
20
22
  const result: UniformsAndBindings = {bindings: {}, uniforms: {}};
21
23
  Object.keys(uniforms).forEach(name => {
22
24
  const uniform = uniforms[name];
23
- if (isUniformValue(uniform)) {
24
- result.uniforms[name] = uniform;
25
+ if (Object.prototype.hasOwnProperty.call(uniformTypes, name) || isUniformValue(uniform)) {
26
+ result.uniforms[name] = uniform as ShaderModuleUniformValue;
25
27
  } else {
26
- result.bindings[name] = uniform;
28
+ result.bindings[name] = uniform as Binding;
27
29
  }
28
30
  });
29
31