@luma.gl/engine 9.0.0-beta.6 → 9.0.0-beta.7

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.
@@ -1,12 +1,13 @@
1
1
  // luma.gl
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
- import { Buffer, RenderPipeline, UniformStore } from '@luma.gl/core';
5
- import { log, uid, deepEqual, splitUniformsAndBindings, isNumberArray } from '@luma.gl/core';
4
+ import { Buffer, Texture, TextureView, Sampler } from '@luma.gl/core';
5
+ import { RenderPipeline, UniformStore } from '@luma.gl/core';
6
+ import { log, uid, deepEqual, isObjectEmpty, splitUniformsAndBindings } from '@luma.gl/core';
6
7
  import { getTypedArrayFromDataType, getAttributeInfosFromLayouts } from '@luma.gl/core';
7
8
  import { ShaderAssembler, getShaderLayoutFromWGSL } from '@luma.gl/shadertools';
8
- import { ShaderInputs } from "../shader-inputs.js";
9
9
  import { makeGPUGeometry } from "../geometry/gpu-geometry.js";
10
+ import { ShaderInputs } from "../shader-inputs.js";
10
11
  import { PipelineFactory } from "../lib/pipeline-factory.js";
11
12
  import { ShaderFactory } from "../lib/shader-factory.js";
12
13
  import { getDebugTableForShaderLayout } from "../debug/debug-shader-layout.js";
@@ -48,6 +49,7 @@ export class Model {
48
49
  };
49
50
  device;
50
51
  id;
52
+ source;
51
53
  vs;
52
54
  fs;
53
55
  pipelineFactory;
@@ -88,12 +90,15 @@ export class Model {
88
90
  /** ShaderInputs instance */
89
91
  shaderInputs;
90
92
  _uniformStore;
91
- _pipelineNeedsUpdate = 'newly created';
92
93
  _attributeInfos = {};
93
94
  _gpuGeometry = null;
94
95
  _getModuleUniforms;
95
96
  props;
97
+ _pipelineNeedsUpdate = 'newly created';
98
+ _needsRedraw = 'initializing';
96
99
  _destroyed = false;
100
+ /** "Time" of last draw. Monotonically increasing timestamp */
101
+ _lastDrawTimestamp = -1;
97
102
  constructor(device, props) {
98
103
  this.props = { ...Model.defaultProps, ...props };
99
104
  props = this.props;
@@ -103,32 +108,36 @@ export class Model {
103
108
  // Setup shader module inputs
104
109
  const moduleMap = Object.fromEntries(this.props.modules?.map(module => [module.name, module]) || []);
105
110
  this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
106
- const isWebGPU = this.device.info.type === 'webgpu';
107
- // TODO - hack to support unified WGSL shader
108
- // TODO - this is wrong, compile a single shader
109
- if (this.props.source) {
110
- if (isWebGPU) {
111
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
112
- }
113
- this.props.fs = this.props.source;
114
- this.props.vs = this.props.source;
115
- }
116
- // Support WGSL shader layout introspection
117
- if (isWebGPU && typeof this.props.vs !== 'string') {
118
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.vs.wgsl);
119
- }
120
111
  // Setup shader assembler
121
112
  const platformInfo = getPlatformInfo(device);
122
113
  // Extract modules from shader inputs if not supplied
123
114
  const modules = (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
124
- const { vs, fs, getUniforms } = this.props.shaderAssembler.assembleShaders({
125
- platformInfo,
126
- ...this.props,
127
- modules
128
- });
129
- this.vs = vs;
130
- this.fs = fs;
131
- this._getModuleUniforms = getUniforms;
115
+ const isWebGPU = this.device.type === 'webgpu';
116
+ // WebGPU
117
+ // TODO - hack to support unified WGSL shader
118
+ // TODO - this is wrong, compile a single shader
119
+ if (isWebGPU && this.props.source) {
120
+ // WGSL
121
+ this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
122
+ const { source, getUniforms } = this.props.shaderAssembler.assembleShader({
123
+ platformInfo,
124
+ ...this.props,
125
+ modules
126
+ });
127
+ this.source = source;
128
+ this._getModuleUniforms = getUniforms;
129
+ }
130
+ else {
131
+ // GLSL
132
+ const { vs, fs, getUniforms } = this.props.shaderAssembler.assembleShaderPair({
133
+ platformInfo,
134
+ ...this.props,
135
+ modules
136
+ });
137
+ this.vs = vs;
138
+ this.fs = fs;
139
+ this._getModuleUniforms = getUniforms;
140
+ }
132
141
  this.vertexCount = this.props.vertexCount;
133
142
  this.instanceCount = this.props.instanceCount;
134
143
  this.topology = this.props.topology;
@@ -136,7 +145,7 @@ export class Model {
136
145
  this.parameters = this.props.parameters;
137
146
  // Geometry, if provided, sets topology and vertex cound
138
147
  if (props.geometry) {
139
- this._gpuGeometry = this.setGeometry(props.geometry);
148
+ this.setGeometry(props.geometry);
140
149
  }
141
150
  this.pipelineFactory =
142
151
  props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
@@ -194,31 +203,55 @@ export class Model {
194
203
  return;
195
204
  this.pipelineFactory.release(this.pipeline);
196
205
  this.shaderFactory.release(this.pipeline.vs);
197
- this.shaderFactory.release(this.pipeline.fs);
206
+ if (this.pipeline.fs) {
207
+ this.shaderFactory.release(this.pipeline.fs);
208
+ }
198
209
  this._uniformStore.destroy();
210
+ // TODO - mark resource as managed and destroyIfManaged() ?
211
+ this._gpuGeometry?.destroy();
199
212
  this._destroyed = true;
200
213
  }
201
214
  // Draw call
215
+ /** Query redraw status. Clears the status. */
216
+ needsRedraw() {
217
+ // Catch any writes to already bound resources
218
+ if (this._getBindingsUpdateTimestamp() > this._lastDrawTimestamp) {
219
+ this.setNeedsRedraw('contents of bound textures or buffers updated');
220
+ }
221
+ const needsRedraw = this._needsRedraw;
222
+ this._needsRedraw = false;
223
+ return needsRedraw;
224
+ }
225
+ /** Mark the model as needing a redraw */
226
+ setNeedsRedraw(reason) {
227
+ this._needsRedraw ||= reason;
228
+ }
202
229
  predraw() {
203
230
  // Update uniform buffers if needed
204
231
  this.updateShaderInputs();
232
+ // Check if the pipeline is invalidated
233
+ this.pipeline = this._updatePipeline();
205
234
  }
206
235
  draw(renderPass) {
207
236
  this.predraw();
237
+ let drawSuccess;
208
238
  try {
209
239
  this._logDrawCallStart();
210
- // Check if the pipeline is invalidated
211
- // TODO - this is likely the worst place to do this from performance perspective. Perhaps add a predraw()?
240
+ // Update the pipeline if invalidated
241
+ // TODO - inside RenderPass is likely the worst place to do this from performance perspective.
242
+ // Application can call Model.predraw() to avoid this.
212
243
  this.pipeline = this._updatePipeline();
213
244
  // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
214
245
  // Any caching needs to be done inside the pipeline functions
215
246
  this.pipeline.setBindings(this.bindings);
216
- this.pipeline.setUniformsWebGL(this.uniforms);
247
+ if (!isObjectEmpty(this.uniforms)) {
248
+ this.pipeline.setUniformsWebGL(this.uniforms);
249
+ }
217
250
  const { indexBuffer } = this.vertexArray;
218
251
  const indexCount = indexBuffer
219
252
  ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2)
220
253
  : undefined;
221
- this.pipeline.draw({
254
+ drawSuccess = this.pipeline.draw({
222
255
  renderPass,
223
256
  vertexArray: this.vertexArray,
224
257
  vertexCount: this.vertexCount,
@@ -231,6 +264,15 @@ export class Model {
231
264
  this._logDrawCallEnd();
232
265
  }
233
266
  this._logFramebuffer(renderPass);
267
+ // Update needsRedraw flag
268
+ if (drawSuccess) {
269
+ this._lastDrawTimestamp = this.device.timestamp;
270
+ this._needsRedraw = false;
271
+ }
272
+ else {
273
+ this._needsRedraw = 'waiting for resource initialization';
274
+ }
275
+ return drawSuccess;
234
276
  }
235
277
  // Update fixed fields (can trigger pipeline rebuild)
236
278
  /**
@@ -239,33 +281,16 @@ export class Model {
239
281
  * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
240
282
  */
241
283
  setGeometry(geometry) {
284
+ this._gpuGeometry?.destroy();
242
285
  const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
243
- this.setTopology(gpuGeometry.topology || 'triangle-list');
244
- this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
245
- if (this.vertexArray) {
246
- this._setGeometryAttributes(gpuGeometry);
247
- }
248
- return gpuGeometry;
249
- }
250
- /**
251
- * Updates the optional geometry attributes
252
- * Geometry, sets several attributes, indexBuffer, and also vertex count
253
- * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
254
- */
255
- _setGeometryAttributes(gpuGeometry) {
256
- // Filter geometry attribute so that we don't issue warnings for unused attributes
257
- const attributes = { ...gpuGeometry.attributes };
258
- for (const [attributeName] of Object.entries(attributes)) {
259
- if (!this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
260
- attributeName !== 'positions') {
261
- delete attributes[attributeName];
286
+ if (gpuGeometry) {
287
+ this.setTopology(gpuGeometry.topology || 'triangle-list');
288
+ this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
289
+ if (this.vertexArray) {
290
+ this._setGeometryAttributes(gpuGeometry);
262
291
  }
263
292
  }
264
- // TODO - delete previous geometry?
265
- this.vertexCount = gpuGeometry.vertexCount;
266
- this.setIndexBuffer(gpuGeometry.indices);
267
- this.setAttributes(gpuGeometry.attributes, { ignoreUnknownAttributes: true });
268
- this.setAttributes(attributes, { ignoreUnknownAttributes: this.props.ignoreUnknownAttributes });
293
+ this._gpuGeometry = gpuGeometry;
269
294
  }
270
295
  /**
271
296
  * Updates the primitive topology ('triangle-list', 'triangle-strip' etc).
@@ -285,7 +310,6 @@ export class Model {
285
310
  this.bufferLayout = this._gpuGeometry
286
311
  ? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
287
312
  : bufferLayout;
288
- this._setPipelineNeedsUpdate('bufferLayout');
289
313
  // Recreate the pipeline
290
314
  this.pipeline = this._updatePipeline();
291
315
  // vertex array needs to be updated if we update buffer layout,
@@ -297,6 +321,7 @@ export class Model {
297
321
  if (this._gpuGeometry) {
298
322
  this._setGeometryAttributes(this._gpuGeometry);
299
323
  }
324
+ this._setPipelineNeedsUpdate('bufferLayout');
300
325
  }
301
326
  /**
302
327
  * Set GPU parameters.
@@ -316,6 +341,7 @@ export class Model {
316
341
  */
317
342
  setVertexCount(vertexCount) {
318
343
  this.vertexCount = vertexCount;
344
+ this.setNeedsRedraw('vertexCount');
319
345
  }
320
346
  /**
321
347
  * Updates the instance count (used in draw calls)
@@ -323,6 +349,7 @@ export class Model {
323
349
  */
324
350
  setInstanceCount(instanceCount) {
325
351
  this.instanceCount = instanceCount;
352
+ this.setNeedsRedraw('instanceCount');
326
353
  }
327
354
  setShaderInputs(shaderInputs) {
328
355
  this.shaderInputs = shaderInputs;
@@ -332,51 +359,26 @@ export class Model {
332
359
  const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
333
360
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
334
361
  }
335
- }
336
- /**
337
- * Updates shader module settings (which results in uniforms being set)
338
- */
339
- setShaderModuleProps(props) {
340
- const uniforms = this._getModuleUniforms(props);
341
- // Extract textures & framebuffers set by the modules
342
- // TODO better way to extract bindings
343
- const keys = Object.keys(uniforms).filter(k => {
344
- const uniform = uniforms[k];
345
- return !isNumberArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean';
346
- });
347
- const bindings = {};
348
- for (const k of keys) {
349
- bindings[k] = uniforms[k];
350
- delete uniforms[k];
351
- }
362
+ this.setNeedsRedraw('shaderInputs');
352
363
  }
353
364
  updateShaderInputs() {
354
365
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
355
- }
356
- /**
357
- * @deprecated Updates shader module settings (which results in uniforms being set)
358
- */
359
- updateModuleSettings(props) {
360
- log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
361
- const { bindings, uniforms } = splitUniformsAndBindings(this._getModuleUniforms(props));
362
- Object.assign(this.bindings, bindings);
363
- Object.assign(this.uniforms, uniforms);
366
+ // TODO - this is already tracked through buffer/texture update times?
367
+ this.setNeedsRedraw('shaderInputs');
364
368
  }
365
369
  /**
366
370
  * Sets bindings (textures, samplers, uniform buffers)
367
371
  */
368
372
  setBindings(bindings) {
369
373
  Object.assign(this.bindings, bindings);
374
+ this.setNeedsRedraw('bindings');
370
375
  }
371
376
  /**
372
- * Sets individual uniforms
373
- * @deprecated WebGL only, use uniform buffers for portability
374
- * @param uniforms
375
- * @returns self for chaining
377
+ * Updates optional transform feedback. WebGL only.
376
378
  */
377
- setUniforms(uniforms) {
378
- this.pipeline.setUniformsWebGL(uniforms);
379
- Object.assign(this.uniforms, uniforms);
379
+ setTransformFeedback(transformFeedback) {
380
+ this.transformFeedback = transformFeedback;
381
+ this.setNeedsRedraw('transformFeedback');
380
382
  }
381
383
  /**
382
384
  * Sets the index buffer
@@ -384,12 +386,7 @@ export class Model {
384
386
  */
385
387
  setIndexBuffer(indexBuffer) {
386
388
  this.vertexArray.setIndexBuffer(indexBuffer);
387
- }
388
- /**
389
- * Updates optional transform feedback. WebGL only.
390
- */
391
- setTransformFeedback(transformFeedback) {
392
- this.transformFeedback = transformFeedback;
389
+ this.setNeedsRedraw('indexBuffer');
393
390
  }
394
391
  /**
395
392
  * Sets attributes (buffers)
@@ -419,6 +416,7 @@ export class Model {
419
416
  log.warn(`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`)();
420
417
  }
421
418
  }
419
+ this.setNeedsRedraw('attributes');
422
420
  }
423
421
  /**
424
422
  * Sets constant attributes
@@ -438,10 +436,75 @@ export class Model {
438
436
  log.warn(`Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`)();
439
437
  }
440
438
  }
439
+ this.setNeedsRedraw('constants');
440
+ }
441
+ // DEPRECATED METHODS
442
+ /**
443
+ * Sets individual uniforms
444
+ * @deprecated WebGL only, use uniform buffers for portability
445
+ * @param uniforms
446
+ */
447
+ setUniforms(uniforms) {
448
+ if (!isObjectEmpty(uniforms)) {
449
+ this.pipeline.setUniformsWebGL(uniforms);
450
+ Object.assign(this.uniforms, uniforms);
451
+ }
452
+ this.setNeedsRedraw('uniforms');
453
+ }
454
+ /**
455
+ * @deprecated Updates shader module settings (which results in uniforms being set)
456
+ */
457
+ updateModuleSettings(props) {
458
+ log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
459
+ const { bindings, uniforms } = splitUniformsAndBindings(this._getModuleUniforms(props));
460
+ Object.assign(this.bindings, bindings);
461
+ Object.assign(this.uniforms, uniforms);
462
+ this.setNeedsRedraw('moduleSettings');
463
+ }
464
+ // Internal methods
465
+ /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
466
+ _getBindingsUpdateTimestamp() {
467
+ let timestamp = 0;
468
+ for (const binding of Object.values(this.bindings)) {
469
+ if (binding instanceof TextureView) {
470
+ timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
471
+ }
472
+ else if (binding instanceof Buffer || binding instanceof Texture) {
473
+ timestamp = Math.max(timestamp, binding.updateTimestamp);
474
+ }
475
+ else if (!(binding instanceof Sampler)) {
476
+ timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
477
+ }
478
+ }
479
+ return timestamp;
480
+ }
481
+ /**
482
+ * Updates the optional geometry attributes
483
+ * Geometry, sets several attributes, indexBuffer, and also vertex count
484
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
485
+ */
486
+ _setGeometryAttributes(gpuGeometry) {
487
+ // Filter geometry attribute so that we don't issue warnings for unused attributes
488
+ const attributes = { ...gpuGeometry.attributes };
489
+ for (const [attributeName] of Object.entries(attributes)) {
490
+ if (!this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
491
+ attributeName !== 'positions') {
492
+ delete attributes[attributeName];
493
+ }
494
+ }
495
+ // TODO - delete previous geometry?
496
+ this.vertexCount = gpuGeometry.vertexCount;
497
+ this.setIndexBuffer(gpuGeometry.indices);
498
+ this.setAttributes(gpuGeometry.attributes, { ignoreUnknownAttributes: true });
499
+ this.setAttributes(attributes, { ignoreUnknownAttributes: this.props.ignoreUnknownAttributes });
500
+ this.setNeedsRedraw('geometry attributes');
441
501
  }
502
+ /** Mark pipeline as needing update */
442
503
  _setPipelineNeedsUpdate(reason) {
443
- this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
504
+ this._pipelineNeedsUpdate ||= reason;
505
+ this.setNeedsRedraw(reason);
444
506
  }
507
+ /** Update pipeline if needed */
445
508
  _updatePipeline() {
446
509
  if (this._pipelineNeedsUpdate) {
447
510
  let prevShaderVs = null;
@@ -455,17 +518,21 @@ export class Model {
455
518
  const vs = this.shaderFactory.createShader({
456
519
  id: `${this.id}-vertex`,
457
520
  stage: 'vertex',
458
- source: this.vs,
521
+ source: this.source || this.vs,
459
522
  debug: this.props.debugShaders
460
523
  });
461
- const fs = this.fs
462
- ? this.shaderFactory.createShader({
524
+ let fs = null;
525
+ if (this.source) {
526
+ fs = vs;
527
+ }
528
+ else if (this.fs) {
529
+ fs = this.shaderFactory.createShader({
463
530
  id: `${this.id}-fragment`,
464
531
  stage: 'fragment',
465
- source: this.fs,
532
+ source: this.source || this.fs,
466
533
  debug: this.props.debugShaders
467
- })
468
- : null;
534
+ });
535
+ }
469
536
  this.pipeline = this.pipelineFactory.createRenderPipeline({
470
537
  ...this.props,
471
538
  bufferLayout: this.bufferLayout,
@@ -576,7 +643,7 @@ function mergeBufferLayouts(layouts1, layouts2) {
576
643
  /** Create a shadertools platform info from the Device */
577
644
  export function getPlatformInfo(device) {
578
645
  return {
579
- type: device.info.type,
646
+ type: device.type,
580
647
  shaderLanguage: device.info.shadingLanguage,
581
648
  shaderLanguageVersion: device.info.shadingLanguageVersion,
582
649
  gpu: device.info.gpu,
@@ -13,6 +13,6 @@ export declare class ModelNode extends ScenegraphNode {
13
13
  constructor(props: ModelNodeProps);
14
14
  getBounds(): [number[], number[]] | null;
15
15
  destroy(): void;
16
- draw(renderPass?: RenderPass): void;
16
+ draw(renderPass?: RenderPass): boolean;
17
17
  }
18
18
  //# sourceMappingURL=model-node.d.ts.map