@luma.gl/engine 9.2.6 → 9.3.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) 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.d.ts.map +1 -1
  6. package/dist/animation-loop/make-animation-loop.js +8 -1
  7. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  9. package/dist/animation-loop/request-animation-frame.js +23 -6
  10. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  11. package/dist/compute/computation.d.ts +3 -7
  12. package/dist/compute/computation.d.ts.map +1 -1
  13. package/dist/compute/computation.js +16 -13
  14. package/dist/compute/computation.js.map +1 -1
  15. package/dist/compute/swap.d.ts +2 -0
  16. package/dist/compute/swap.d.ts.map +1 -1
  17. package/dist/compute/swap.js +10 -5
  18. package/dist/compute/swap.js.map +1 -1
  19. package/dist/debug/debug-framebuffer.d.ts +9 -4
  20. package/dist/debug/debug-framebuffer.d.ts.map +1 -1
  21. package/dist/debug/debug-framebuffer.js +91 -45
  22. package/dist/debug/debug-framebuffer.js.map +1 -1
  23. package/dist/dist.dev.js +2767 -1344
  24. package/dist/dist.min.js +326 -211
  25. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  26. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  27. package/dist/dynamic-texture/dynamic-texture.js +558 -0
  28. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  29. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  30. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  31. package/dist/dynamic-texture/texture-data.js +208 -0
  32. package/dist/dynamic-texture/texture-data.js.map +1 -0
  33. package/dist/geometries/cone-geometry.d.ts +3 -1
  34. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  35. package/dist/geometries/cone-geometry.js.map +1 -1
  36. package/dist/geometries/cube-geometry.js +7 -7
  37. package/dist/geometries/cube-geometry.js.map +1 -1
  38. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  39. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  40. package/dist/geometries/cylinder-geometry.js.map +1 -1
  41. package/dist/geometries/ico-sphere-geometry.js +3 -1
  42. package/dist/geometries/ico-sphere-geometry.js.map +1 -1
  43. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  44. package/dist/geometry/gpu-geometry.js +11 -3
  45. package/dist/geometry/gpu-geometry.js.map +1 -1
  46. package/dist/index.cjs +2620 -1267
  47. package/dist/index.cjs.map +4 -4
  48. package/dist/index.d.ts +20 -6
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +12 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/material/material-factory.d.ts +73 -0
  53. package/dist/material/material-factory.d.ts.map +1 -0
  54. package/dist/material/material-factory.js +111 -0
  55. package/dist/material/material-factory.js.map +1 -0
  56. package/dist/material/material.d.ts +84 -0
  57. package/dist/material/material.d.ts.map +1 -0
  58. package/dist/material/material.js +176 -0
  59. package/dist/material/material.js.map +1 -0
  60. package/dist/model/model.d.ts +47 -16
  61. package/dist/model/model.d.ts.map +1 -1
  62. package/dist/model/model.js +148 -71
  63. package/dist/model/model.js.map +1 -1
  64. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  65. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  66. package/dist/model/split-uniforms-and-bindings.js +2 -2
  67. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  68. package/dist/models/billboard-texture-model.d.ts +8 -5
  69. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  70. package/dist/models/billboard-texture-model.js +79 -25
  71. package/dist/models/billboard-texture-model.js.map +1 -1
  72. package/dist/models/billboard-texture-module.d.ts +1 -1
  73. package/dist/models/billboard-texture-module.js +1 -1
  74. package/dist/models/clip-space.js +7 -7
  75. package/dist/models/directional-light-model.d.ts +7 -0
  76. package/dist/models/directional-light-model.d.ts.map +1 -0
  77. package/dist/models/directional-light-model.js +23 -0
  78. package/dist/models/directional-light-model.js.map +1 -0
  79. package/dist/models/light-model-utils.d.ts +69 -0
  80. package/dist/models/light-model-utils.d.ts.map +1 -0
  81. package/dist/models/light-model-utils.js +395 -0
  82. package/dist/models/light-model-utils.js.map +1 -0
  83. package/dist/models/point-light-model.d.ts +7 -0
  84. package/dist/models/point-light-model.d.ts.map +1 -0
  85. package/dist/models/point-light-model.js +22 -0
  86. package/dist/models/point-light-model.js.map +1 -0
  87. package/dist/models/spot-light-model.d.ts +7 -0
  88. package/dist/models/spot-light-model.d.ts.map +1 -0
  89. package/dist/models/spot-light-model.js +23 -0
  90. package/dist/models/spot-light-model.js.map +1 -0
  91. package/dist/modules/picking/color-picking.d.ts +5 -9
  92. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  93. package/dist/modules/picking/color-picking.js +122 -115
  94. package/dist/modules/picking/color-picking.js.map +1 -1
  95. package/dist/modules/picking/index-picking.d.ts +4 -4
  96. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  97. package/dist/modules/picking/index-picking.js +36 -16
  98. package/dist/modules/picking/index-picking.js.map +1 -1
  99. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  100. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  101. package/dist/modules/picking/legacy-color-picking.js +7 -0
  102. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  103. package/dist/modules/picking/picking-manager.d.ts +29 -3
  104. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  105. package/dist/modules/picking/picking-manager.js +188 -41
  106. package/dist/modules/picking/picking-manager.js.map +1 -1
  107. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  108. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  109. package/dist/modules/picking/picking-uniforms.js +27 -14
  110. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  111. package/dist/modules/picking/picking.d.ts +25 -0
  112. package/dist/modules/picking/picking.d.ts.map +1 -0
  113. package/dist/modules/picking/picking.js +18 -0
  114. package/dist/modules/picking/picking.js.map +1 -0
  115. package/dist/passes/get-fragment-shader.js +12 -27
  116. package/dist/passes/get-fragment-shader.js.map +1 -1
  117. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  118. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  119. package/dist/passes/shader-pass-renderer.js +16 -42
  120. package/dist/passes/shader-pass-renderer.js.map +1 -1
  121. package/dist/scenegraph/group-node.d.ts +5 -0
  122. package/dist/scenegraph/group-node.d.ts.map +1 -1
  123. package/dist/scenegraph/group-node.js +12 -0
  124. package/dist/scenegraph/group-node.js.map +1 -1
  125. package/dist/scenegraph/model-node.d.ts +2 -2
  126. package/dist/scenegraph/model-node.d.ts.map +1 -1
  127. package/dist/scenegraph/model-node.js.map +1 -1
  128. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  129. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  130. package/dist/scenegraph/scenegraph-node.js +23 -15
  131. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  132. package/dist/shader-inputs.d.ts +9 -7
  133. package/dist/shader-inputs.d.ts.map +1 -1
  134. package/dist/shader-inputs.js +90 -13
  135. package/dist/shader-inputs.js.map +1 -1
  136. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  137. package/dist/utils/buffer-layout-order.js +12 -2
  138. package/dist/utils/buffer-layout-order.js.map +1 -1
  139. package/dist/utils/shader-module-utils.d.ts +7 -0
  140. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  141. package/dist/utils/shader-module-utils.js +46 -0
  142. package/dist/utils/shader-module-utils.js.map +1 -0
  143. package/package.json +6 -6
  144. package/src/animation-loop/animation-loop.ts +89 -50
  145. package/src/animation-loop/make-animation-loop.ts +14 -5
  146. package/src/animation-loop/request-animation-frame.ts +32 -6
  147. package/src/compute/computation.ts +32 -17
  148. package/src/compute/swap.ts +13 -7
  149. package/src/debug/debug-framebuffer.ts +139 -61
  150. package/src/dynamic-texture/dynamic-texture.ts +730 -0
  151. package/src/dynamic-texture/texture-data.ts +336 -0
  152. package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +1 -1
  153. package/src/geometries/cone-geometry.ts +6 -1
  154. package/src/geometries/cube-geometry.ts +7 -7
  155. package/src/geometries/cylinder-geometry.ts +5 -1
  156. package/src/geometries/ico-sphere-geometry.ts +3 -1
  157. package/src/geometry/gpu-geometry.ts +11 -3
  158. package/src/index.ts +38 -8
  159. package/src/material/material-factory.ts +157 -0
  160. package/src/material/material.ts +254 -0
  161. package/src/model/model.ts +196 -93
  162. package/src/model/split-uniforms-and-bindings.ts +8 -6
  163. package/src/models/billboard-texture-model.ts +90 -29
  164. package/src/models/billboard-texture-module.ts +1 -1
  165. package/src/models/clip-space.ts +7 -7
  166. package/src/models/directional-light-model.ts +32 -0
  167. package/src/models/light-model-utils.ts +587 -0
  168. package/src/models/point-light-model.ts +31 -0
  169. package/src/models/spot-light-model.ts +32 -0
  170. package/src/modules/picking/color-picking.ts +123 -122
  171. package/src/modules/picking/index-picking.ts +36 -16
  172. package/src/modules/picking/legacy-color-picking.ts +8 -0
  173. package/src/modules/picking/picking-manager.ts +252 -50
  174. package/src/modules/picking/picking-uniforms.ts +39 -24
  175. package/src/modules/picking/picking.ts +22 -0
  176. package/src/passes/get-fragment-shader.ts +12 -27
  177. package/src/passes/shader-pass-renderer.ts +25 -48
  178. package/src/scenegraph/group-node.ts +16 -0
  179. package/src/scenegraph/model-node.ts +2 -2
  180. package/src/scenegraph/scenegraph-node.ts +27 -16
  181. package/src/shader-inputs.ts +167 -26
  182. package/src/utils/buffer-layout-order.ts +18 -2
  183. package/src/utils/shader-module-utils.ts +65 -0
  184. package/dist/async-texture/async-texture.d.ts +0 -166
  185. package/dist/async-texture/async-texture.d.ts.map +0 -1
  186. package/dist/async-texture/async-texture.js +0 -386
  187. package/dist/async-texture/async-texture.js.map +0 -1
  188. package/dist/factories/pipeline-factory.d.ts +0 -37
  189. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  190. package/dist/factories/pipeline-factory.js +0 -181
  191. package/dist/factories/pipeline-factory.js.map +0 -1
  192. package/dist/factories/shader-factory.d.ts +0 -22
  193. package/dist/factories/shader-factory.d.ts.map +0 -1
  194. package/dist/factories/shader-factory.js +0 -88
  195. package/dist/factories/shader-factory.js.map +0 -1
  196. package/src/async-texture/async-texture.ts +0 -551
  197. package/src/factories/pipeline-factory.ts +0 -224
  198. package/src/factories/shader-factory.ts +0 -103
@@ -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,30 +23,37 @@ 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;
56
+ const PIPELINE_INITIALIZATION_FAILED = 'render pipeline initialization failed';
51
57
 
52
58
  export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
53
59
  source?: string;
@@ -62,8 +68,12 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
62
68
 
63
69
  /** Shader inputs, used to generated uniform buffers and bindings */
64
70
  shaderInputs?: ShaderInputs;
71
+ /** Material-owned group-3 bindings */
72
+ material?: Material;
65
73
  /** Bindings */
66
- bindings?: Record<string, Binding | AsyncTexture>;
74
+ bindings?: Record<string, Binding | DynamicTexture>;
75
+ /** WebGL-only uniforms */
76
+ uniforms?: Record<string, unknown>;
67
77
  /** Parameters that are built into the pipeline */
68
78
  parameters?: RenderPipelineParameters;
69
79
 
@@ -103,12 +113,19 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
103
113
  };
104
114
 
105
115
  /**
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
116
+ * High level draw API for luma.gl.
117
+ *
118
+ * A `Model` encapsulates shaders, geometry attributes, bindings and render
119
+ * pipeline state into a single object. It automatically reuses and rebuilds
120
+ * pipelines as render parameters change and exposes convenient hooks for
121
+ * updating uniforms and attributes.
122
+ *
123
+ * Features:
124
+ * - Reuses and lazily recompiles {@link RenderPipeline | pipelines} as needed.
125
+ * - Integrates with `@luma.gl/shadertools` to assemble GLSL or WGSL from shader modules.
126
+ * - Manages geometry attributes and buffer bindings.
127
+ * - Accepts textures, samplers and uniform buffers as bindings, including `DynamicTexture`.
128
+ * - Provides detailed debug logging and optional shader source inspection.
112
129
  */
113
130
  export class Model {
114
131
  static defaultProps: Required<ModelProps> = {
@@ -125,6 +142,8 @@ export class Model {
125
142
  indexBuffer: null,
126
143
  attributes: {},
127
144
  constantAttributes: {},
145
+ bindings: {},
146
+ uniforms: {},
128
147
  varyings: [],
129
148
 
130
149
  isInstanced: undefined!,
@@ -132,6 +151,7 @@ export class Model {
132
151
  vertexCount: 0,
133
152
 
134
153
  shaderInputs: undefined!,
154
+ material: undefined!,
135
155
  pipelineFactory: undefined!,
136
156
  shaderFactory: undefined!,
137
157
  transformFeedback: undefined!,
@@ -141,16 +161,24 @@ export class Model {
141
161
  disableWarnings: undefined!
142
162
  };
143
163
 
164
+ /** Device that created this model */
144
165
  readonly device: Device;
166
+ /** Application provided identifier */
145
167
  readonly id: string;
168
+ /** WGSL shader source when using unified shader */
146
169
  // @ts-expect-error assigned in function called from constructor
147
170
  readonly source: string;
171
+ /** GLSL vertex shader source */
148
172
  // @ts-expect-error assigned in function called from constructor
149
173
  readonly vs: string;
174
+ /** GLSL fragment shader source */
150
175
  // @ts-expect-error assigned in function called from constructor
151
176
  readonly fs: string;
177
+ /** Factory used to create render pipelines */
152
178
  readonly pipelineFactory: PipelineFactory;
179
+ /** Factory used to create shaders */
153
180
  readonly shaderFactory: ShaderFactory;
181
+ /** User-supplied per-model data */
154
182
  userData: {[key: string]: any} = {};
155
183
 
156
184
  // Fixed properties (change can trigger pipeline rebuild)
@@ -179,7 +207,7 @@ export class Model {
179
207
  /** Constant-valued attributes */
180
208
  constantAttributes: Record<string, TypedArray> = {};
181
209
  /** Bindings (textures, samplers, uniform buffers) */
182
- bindings: Record<string, Binding | AsyncTexture> = {};
210
+ bindings: Record<string, Binding | DynamicTexture> = {};
183
211
 
184
212
  /**
185
213
  * VertexArray
@@ -197,6 +225,7 @@ export class Model {
197
225
  /** ShaderInputs instance */
198
226
  // @ts-expect-error Assigned in function called by constructor
199
227
  shaderInputs: ShaderInputs;
228
+ material: Material | null = null;
200
229
  // @ts-expect-error Assigned in function called by constructor
201
230
  _uniformStore: UniformStore;
202
231
 
@@ -210,6 +239,7 @@ export class Model {
210
239
 
211
240
  /** "Time" of last draw. Monotonically increasing timestamp */
212
241
  _lastDrawTimestamp: number = -1;
242
+ private _bindingTable: ShaderBindingDebugRow[] = [];
213
243
 
214
244
  get [Symbol.toStringTag](): string {
215
245
  return 'Model';
@@ -227,6 +257,8 @@ export class Model {
227
257
 
228
258
  Object.assign(this.userData, props.userData);
229
259
 
260
+ this.material = props.material || null;
261
+
230
262
  // Setup shader module inputs
231
263
  const moduleMap = Object.fromEntries(
232
264
  this.props.modules?.map(module => [module.name, module]) || []
@@ -246,6 +278,9 @@ export class Model {
246
278
  // @ts-ignore shaderInputs is assigned in setShaderInputs above.
247
279
  (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
248
280
 
281
+ this.props.shaderLayout =
282
+ mergeShaderModuleBindingsIntoLayout(this.props.shaderLayout, modules) || null;
283
+
249
284
  const isWebGPU = this.device.type === 'webgpu';
250
285
 
251
286
  // WebGPU
@@ -253,7 +288,7 @@ export class Model {
253
288
  // TODO - this is wrong, compile a single shader
254
289
  if (isWebGPU && this.props.source) {
255
290
  // WGSL
256
- const {source, getUniforms} = this.props.shaderAssembler.assembleWGSLShader({
291
+ const {source, getUniforms, bindingTable} = this.props.shaderAssembler.assembleWGSLShader({
257
292
  platformInfo,
258
293
  ...this.props,
259
294
  modules
@@ -261,8 +296,16 @@ export class Model {
261
296
  this.source = source;
262
297
  // @ts-expect-error
263
298
  this._getModuleUniforms = getUniforms;
299
+ this._bindingTable = bindingTable;
264
300
  // 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);
301
+ const inferredShaderLayout = (
302
+ device as Device & {getShaderLayout?: (source: string) => any}
303
+ ).getShaderLayout?.(this.source);
304
+ this.props.shaderLayout =
305
+ mergeShaderModuleBindingsIntoLayout(
306
+ this.props.shaderLayout || inferredShaderLayout || null,
307
+ modules
308
+ ) || null;
266
309
  } else {
267
310
  // GLSL
268
311
  const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleGLSLShaderPair({
@@ -275,6 +318,7 @@ export class Model {
275
318
  this.fs = fs;
276
319
  // @ts-expect-error
277
320
  this._getModuleUniforms = getUniforms;
321
+ this._bindingTable = [];
278
322
  }
279
323
 
280
324
  this.vertexCount = this.props.vertexCount;
@@ -333,9 +377,6 @@ export class Model {
333
377
  if (props.transformFeedback) {
334
378
  this.transformFeedback = props.transformFeedback;
335
379
  }
336
-
337
- // Catch any access to non-standard props
338
- Object.seal(this);
339
380
  }
340
381
 
341
382
  destroy(): void {
@@ -344,7 +385,7 @@ export class Model {
344
385
  this.pipelineFactory.release(this.pipeline);
345
386
  // Release the shaders
346
387
  this.shaderFactory.release(this.pipeline.vs);
347
- if (this.pipeline.fs) {
388
+ if (this.pipeline.fs && this.pipeline.fs !== this.pipeline.vs) {
348
389
  this.shaderFactory.release(this.pipeline.fs);
349
390
  }
350
391
  this._uniformStore.destroy();
@@ -372,6 +413,12 @@ export class Model {
372
413
  this._needsRedraw ||= reason;
373
414
  }
374
415
 
416
+ /** Returns WGSL binding debug rows for the assembled shader. Returns an empty array for GLSL models. */
417
+ getBindingDebugTable(): readonly ShaderBindingDebugRow[] {
418
+ return this._bindingTable;
419
+ }
420
+
421
+ /** Update uniforms and pipeline state prior to drawing. */
375
422
  predraw(): void {
376
423
  // Update uniform buffers if needed
377
424
  this.updateShaderInputs();
@@ -379,6 +426,11 @@ export class Model {
379
426
  this.pipeline = this._updatePipeline();
380
427
  }
381
428
 
429
+ /**
430
+ * Issue one draw call.
431
+ * @param renderPass - render pass to draw into
432
+ * @returns `true` if the draw call was executed, `false` if resources were not ready.
433
+ */
382
434
  draw(renderPass: RenderPass): boolean {
383
435
  const loadingBinding = this._areBindingsLoading();
384
436
  if (loadingBinding) {
@@ -394,6 +446,7 @@ export class Model {
394
446
  }
395
447
 
396
448
  let drawSuccess: boolean;
449
+ let pipelineErrored = this.pipeline.isErrored;
397
450
  try {
398
451
  renderPass.pushDebugGroup(`${this}.draw(${renderPass})`);
399
452
  this._logDrawCallStart();
@@ -402,35 +455,45 @@ export class Model {
402
455
  // TODO - inside RenderPass is likely the worst place to do this from performance perspective.
403
456
  // Application can call Model.predraw() to avoid this.
404
457
  this.pipeline = this._updatePipeline();
458
+ pipelineErrored = this.pipeline.isErrored;
405
459
 
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
- const syncBindings = this._getBindings();
411
- this.pipeline.setBindings(syncBindings, {
412
- disableWarnings: this.props.disableWarnings
413
- });
414
-
415
- const {indexBuffer} = this.vertexArray;
416
- const indexCount = indexBuffer
417
- ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2)
418
- : undefined;
419
-
420
- drawSuccess = this.pipeline.draw({
421
- renderPass,
422
- vertexArray: this.vertexArray,
423
- isInstanced: this.isInstanced,
424
- vertexCount: this.vertexCount,
425
- instanceCount: this.instanceCount,
426
- indexCount,
427
- transformFeedback: this.transformFeedback || undefined,
428
- // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
429
- // so we must provide our unique parameters to each draw
430
- // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
431
- parameters: this.parameters,
432
- topology: this.topology
433
- });
460
+ if (pipelineErrored) {
461
+ log.info(
462
+ LOG_DRAW_PRIORITY,
463
+ `>>> DRAWING ABORTED ${this.id}: ${PIPELINE_INITIALIZATION_FAILED}`
464
+ )();
465
+ drawSuccess = false;
466
+ } else {
467
+ const syncBindings = this._getBindings();
468
+ const syncBindGroups = this._getBindGroups();
469
+
470
+ const {indexBuffer} = this.vertexArray;
471
+ const indexCount = indexBuffer
472
+ ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2)
473
+ : undefined;
474
+
475
+ drawSuccess = this.pipeline.draw({
476
+ renderPass,
477
+ vertexArray: this.vertexArray,
478
+ isInstanced: this.isInstanced,
479
+ vertexCount: this.vertexCount,
480
+ instanceCount: this.instanceCount,
481
+ indexCount,
482
+ transformFeedback: this.transformFeedback || undefined,
483
+ // Pipelines may be shared across models when caching is enabled, so bindings
484
+ // and WebGL uniforms must be supplied on every draw instead of being stored
485
+ // on the pipeline instance.
486
+ bindings: syncBindings,
487
+ bindGroups: syncBindGroups,
488
+ _bindGroupCacheKeys: this._getBindGroupCacheKeys(),
489
+ uniforms: this.props.uniforms,
490
+ // WebGL shares underlying cached pipelines even for models that have different parameters and topology,
491
+ // so we must provide our unique parameters to each draw
492
+ // (In WebGPU most parameters are encoded in the pipeline and cannot be changed per draw call)
493
+ parameters: this.parameters,
494
+ topology: this.topology
495
+ });
496
+ }
434
497
  } finally {
435
498
  renderPass.popDebugGroup();
436
499
  this._logDrawCallEnd();
@@ -441,6 +504,8 @@ export class Model {
441
504
  if (drawSuccess) {
442
505
  this._lastDrawTimestamp = this.device.timestamp;
443
506
  this._needsRedraw = false;
507
+ } else if (pipelineErrored) {
508
+ this._needsRedraw = PIPELINE_INITIALIZATION_FAILED;
444
509
  } else {
445
510
  this._needsRedraw = 'waiting for resource initialization';
446
511
  }
@@ -549,21 +614,26 @@ export class Model {
549
614
  /** Set the shader inputs */
550
615
  setShaderInputs(shaderInputs: ShaderInputs): void {
551
616
  this.shaderInputs = shaderInputs;
552
- this._uniformStore = new UniformStore(this.shaderInputs.modules);
617
+ this._uniformStore = new UniformStore(this.device, this.shaderInputs.modules);
553
618
  // Create uniform buffer bindings for all modules that actually have uniforms
554
619
  for (const [moduleName, module] of Object.entries(this.shaderInputs.modules)) {
555
- if (shaderModuleHasUniforms(module)) {
556
- const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
620
+ if (shaderModuleHasUniforms(module) && !this.material?.ownsModule(moduleName)) {
621
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(moduleName);
557
622
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
558
623
  }
559
624
  }
560
625
  this.setNeedsRedraw('shaderInputs');
561
626
  }
562
627
 
628
+ setMaterial(material: Material | null): void {
629
+ this.material = material;
630
+ this.setNeedsRedraw('material');
631
+ }
632
+
563
633
  /** Update uniform buffers from the model's shader inputs */
564
634
  updateShaderInputs(): void {
565
635
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
566
- this.setBindings(this.shaderInputs.getBindingValues());
636
+ this.setBindings(this._getNonMaterialBindings(this.shaderInputs.getBindingValues()));
567
637
  // TODO - this is already tracked through buffer/texture update times?
568
638
  this.setNeedsRedraw('shaderInputs');
569
639
  }
@@ -571,7 +641,7 @@ export class Model {
571
641
  /**
572
642
  * Sets bindings (textures, samplers, uniform buffers)
573
643
  */
574
- setBindings(bindings: Record<string, Binding | AsyncTexture>): void {
644
+ setBindings(bindings: Record<string, Binding | DynamicTexture>): void {
575
645
  Object.assign(this.bindings, bindings);
576
646
  this.setNeedsRedraw('bindings');
577
647
  }
@@ -678,7 +748,12 @@ export class Model {
678
748
  /** Check that bindings are loaded. Returns id of first binding that is still loading. */
679
749
  _areBindingsLoading(): string | false {
680
750
  for (const binding of Object.values(this.bindings)) {
681
- if (binding instanceof AsyncTexture && !binding.isReady) {
751
+ if (binding instanceof DynamicTexture && !binding.isReady) {
752
+ return binding.id;
753
+ }
754
+ }
755
+ for (const binding of Object.values(this.material?.bindings || {})) {
756
+ if (binding instanceof DynamicTexture && !binding.isReady) {
682
757
  return binding.id;
683
758
  }
684
759
  }
@@ -690,7 +765,7 @@ export class Model {
690
765
  const validBindings: Record<string, Binding> = {};
691
766
 
692
767
  for (const [name, binding] of Object.entries(this.bindings)) {
693
- if (binding instanceof AsyncTexture) {
768
+ if (binding instanceof DynamicTexture) {
694
769
  // Check that async textures are loaded
695
770
  if (binding.isReady) {
696
771
  validBindings[name] = binding.texture;
@@ -703,6 +778,32 @@ export class Model {
703
778
  return validBindings;
704
779
  }
705
780
 
781
+ _getBindGroups(): BindingsByGroup {
782
+ const shaderLayout = this.pipeline?.shaderLayout || this.props.shaderLayout || {bindings: []};
783
+ const bindGroups = shaderLayout.bindings.length
784
+ ? normalizeBindingsByGroup(shaderLayout, this._getBindings())
785
+ : {0: this._getBindings()};
786
+
787
+ if (!this.material) {
788
+ return bindGroups;
789
+ }
790
+
791
+ for (const [groupKey, groupBindings] of Object.entries(this.material.getBindingsByGroup())) {
792
+ const group = Number(groupKey);
793
+ bindGroups[group] = {
794
+ ...(bindGroups[group] || {}),
795
+ ...groupBindings
796
+ };
797
+ }
798
+
799
+ return bindGroups;
800
+ }
801
+
802
+ _getBindGroupCacheKeys(): Partial<Record<number, object>> {
803
+ const bindGroupCacheKey = this.material?.getBindGroupCacheKey(3);
804
+ return bindGroupCacheKey ? {3: bindGroupCacheKey} : {};
805
+ }
806
+
706
807
  /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
707
808
  _getBindingsUpdateTimestamp(): number {
708
809
  let timestamp = 0;
@@ -711,7 +812,7 @@ export class Model {
711
812
  timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
712
813
  } else if (binding instanceof Buffer || binding instanceof Texture) {
713
814
  timestamp = Math.max(timestamp, binding.updateTimestamp);
714
- } else if (binding instanceof AsyncTexture) {
815
+ } else if (binding instanceof DynamicTexture) {
715
816
  timestamp = binding.texture
716
817
  ? Math.max(timestamp, binding.texture.updateTimestamp)
717
818
  : // The texture will become available in the future
@@ -720,7 +821,7 @@ export class Model {
720
821
  timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
721
822
  }
722
823
  }
723
- return timestamp;
824
+ return Math.max(timestamp, this.material?.getBindingsUpdateTimestamp() || 0);
724
825
  }
725
826
 
726
827
  /**
@@ -792,12 +893,11 @@ export class Model {
792
893
 
793
894
  this.pipeline = this.pipelineFactory.createRenderPipeline({
794
895
  ...this.props,
896
+ bindings: undefined,
795
897
  bufferLayout: this.bufferLayout,
796
898
  topology: this.topology,
797
899
  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(),
900
+ bindGroups: this._getBindGroups(),
801
901
  vs,
802
902
  fs
803
903
  });
@@ -808,7 +908,9 @@ export class Model {
808
908
  );
809
909
 
810
910
  if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
811
- if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
911
+ if (prevShaderFs && prevShaderFs !== prevShaderVs) {
912
+ this.shaderFactory.release(prevShaderFs);
913
+ }
812
914
  }
813
915
  return this.pipeline;
814
916
  }
@@ -859,12 +961,11 @@ export class Model {
859
961
  // } || (this._drawCount++ > 3 && this._drawCount % 60)) {
860
962
  return;
861
963
  }
862
- // TODO - display framebuffer output in debug window
863
964
  const framebuffer = renderPass.props.framebuffer;
864
- if (framebuffer) {
865
- debugFramebuffer(framebuffer, {id: framebuffer.id, minimap: true});
866
- // log.image({logLevel: LOG_DRAW_PRIORITY, message: `${framebuffer.id} %c sup?`, image})();
867
- }
965
+ debugFramebuffer(renderPass, framebuffer, {
966
+ id: framebuffer?.id || `${this.id}-framebuffer`,
967
+ minimap: true
968
+ });
868
969
  }
869
970
 
870
971
  _getAttributeDebugTable(): Record<string, Record<string, unknown>> {
@@ -896,15 +997,27 @@ export class Model {
896
997
 
897
998
  // TODO - fix typing of luma data types
898
999
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
899
- const TypedArrayConstructor = getTypedArrayConstructor(dataType);
1000
+ const TypedArrayConstructor = dataTypeDecoder.getTypedArrayConstructor(dataType);
900
1001
  const typedArray =
901
1002
  attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
902
1003
  return typedArray.toString();
903
1004
  }
904
- }
905
1005
 
906
- function shaderModuleHasUniforms(module: ShaderModule): boolean {
907
- return Boolean(module.uniformTypes && !isObjectEmpty(module.uniformTypes));
1006
+ private _getNonMaterialBindings(
1007
+ bindings: Record<string, Binding | DynamicTexture>
1008
+ ): Record<string, Binding | DynamicTexture> {
1009
+ if (!this.material) {
1010
+ return bindings;
1011
+ }
1012
+
1013
+ const filteredBindings: Record<string, Binding | DynamicTexture> = {};
1014
+ for (const [name, binding] of Object.entries(bindings)) {
1015
+ if (!this.material.ownsBinding(name)) {
1016
+ filteredBindings[name] = binding;
1017
+ }
1018
+ }
1019
+ return filteredBindings;
1020
+ }
908
1021
  }
909
1022
 
910
1023
  // HELPERS
@@ -920,13 +1033,3 @@ export function getPlatformInfo(device: Device): PlatformInfo {
920
1033
  features: device.features as unknown as Set<DeviceFeature>
921
1034
  };
922
1035
  }
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