@luma.gl/engine 9.0.0-beta.5 → 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.
Files changed (115) hide show
  1. package/dist/animation/timeline.d.ts.map +1 -1
  2. package/dist/animation/timeline.js +3 -3
  3. package/dist/animation-loop/animation-loop-template.d.ts +1 -1
  4. package/dist/animation-loop/animation-loop-template.d.ts.map +1 -1
  5. package/dist/animation-loop/animation-loop-template.js +3 -1
  6. package/dist/animation-loop/animation-loop.d.ts +2 -2
  7. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  8. package/dist/animation-loop/animation-loop.js +14 -6
  9. package/dist/animation-loop/animation-props.d.ts +2 -2
  10. package/dist/animation-loop/animation-props.d.ts.map +1 -1
  11. package/dist/animation-loop/make-animation-loop.d.ts +2 -2
  12. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  13. package/dist/animation-loop/make-animation-loop.js +4 -2
  14. package/dist/computation.d.ts +95 -0
  15. package/dist/computation.d.ts.map +1 -0
  16. package/dist/computation.js +248 -0
  17. package/dist/debug/copy-texture-to-image.d.ts.map +1 -1
  18. package/dist/debug/copy-texture-to-image.js +5 -2
  19. package/dist/debug/debug-framebuffer.d.ts.map +1 -1
  20. package/dist/debug/debug-framebuffer.js +0 -1
  21. package/dist/debug/pixel-data-utils.d.ts.map +1 -1
  22. package/dist/debug/pixel-data-utils.js +2 -1
  23. package/dist/dist.dev.js +713 -329
  24. package/dist/geometries/cone-geometry.d.ts +1 -1
  25. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  26. package/dist/geometries/cone-geometry.js +1 -1
  27. package/dist/geometries/cube-geometry.d.ts +1 -1
  28. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  29. package/dist/geometries/cube-geometry.js +16 -14
  30. package/dist/geometries/cylinder-geometry.d.ts +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  32. package/dist/geometries/cylinder-geometry.js +1 -1
  33. package/dist/geometries/ico-sphere-geometry.d.ts +1 -1
  34. package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -1
  35. package/dist/geometries/ico-sphere-geometry.js +1 -1
  36. package/dist/geometries/plane-geometry.d.ts +1 -1
  37. package/dist/geometries/plane-geometry.d.ts.map +1 -1
  38. package/dist/geometries/plane-geometry.js +2 -2
  39. package/dist/geometries/sphere-geometry.d.ts +1 -1
  40. package/dist/geometries/sphere-geometry.d.ts.map +1 -1
  41. package/dist/geometries/sphere-geometry.js +1 -1
  42. package/dist/geometries/truncated-cone-geometry.d.ts +1 -1
  43. package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -1
  44. package/dist/geometries/truncated-cone-geometry.js +1 -1
  45. package/dist/geometry/geometry-table.d.ts.map +1 -1
  46. package/dist/geometry/geometry-table.js +3 -0
  47. package/dist/geometry/geometry.d.ts.map +1 -1
  48. package/dist/geometry/geometry.js +3 -0
  49. package/dist/geometry/gpu-geometry.d.ts +1 -1
  50. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  51. package/dist/geometry/gpu-geometry.js +4 -5
  52. package/dist/index.cjs +661 -291
  53. package/dist/index.cjs.map +4 -4
  54. package/dist/index.d.ts +43 -40
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +25 -23
  57. package/dist/lib/clip-space.d.ts +1 -1
  58. package/dist/lib/clip-space.d.ts.map +1 -1
  59. package/dist/lib/clip-space.js +8 -10
  60. package/dist/lib/pipeline-factory.d.ts +10 -6
  61. package/dist/lib/pipeline-factory.d.ts.map +1 -1
  62. package/dist/lib/pipeline-factory.js +47 -22
  63. package/dist/lib/shader-factory.d.ts +17 -0
  64. package/dist/lib/shader-factory.d.ts.map +1 -0
  65. package/dist/lib/shader-factory.js +46 -0
  66. package/dist/model/model.d.ts +58 -45
  67. package/dist/model/model.d.ts.map +1 -1
  68. package/dist/model/model.js +213 -120
  69. package/dist/scenegraph/group-node.d.ts +1 -1
  70. package/dist/scenegraph/group-node.d.ts.map +1 -1
  71. package/dist/scenegraph/group-node.js +10 -5
  72. package/dist/scenegraph/model-node.d.ts +3 -3
  73. package/dist/scenegraph/model-node.d.ts.map +1 -1
  74. package/dist/scenegraph/model-node.js +2 -2
  75. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  76. package/dist/shader-inputs.d.ts.map +1 -1
  77. package/dist/shader-inputs.js +3 -0
  78. package/dist/transform/buffer-transform.d.ts +1 -1
  79. package/dist/transform/buffer-transform.d.ts.map +1 -1
  80. package/dist/transform/buffer-transform.js +7 -6
  81. package/dist/transform/texture-transform.d.ts +1 -1
  82. package/dist/transform/texture-transform.d.ts.map +1 -1
  83. package/dist/transform/texture-transform.js +10 -8
  84. package/dist.min.js +2 -2
  85. package/package.json +2 -2
  86. package/src/animation/timeline.ts +20 -20
  87. package/src/animation-loop/animation-loop-template.ts +10 -8
  88. package/src/animation-loop/animation-loop.ts +20 -10
  89. package/src/animation-loop/animation-props.ts +1 -1
  90. package/src/animation-loop/make-animation-loop.ts +17 -8
  91. package/src/computation.ts +346 -0
  92. package/src/debug/copy-texture-to-image.ts +8 -6
  93. package/src/debug/debug-framebuffer.ts +16 -3
  94. package/src/debug/debug-shader-layout.ts +1 -1
  95. package/src/debug/pixel-data-utils.ts +3 -6
  96. package/src/geometries/cube-geometry.ts +17 -13
  97. package/src/geometries/ico-sphere-geometry.ts +1 -1
  98. package/src/geometries/plane-geometry.ts +1 -1
  99. package/src/geometries/sphere-geometry.ts +1 -1
  100. package/src/geometries/truncated-cone-geometry.ts +2 -1
  101. package/src/geometry/geometry-table.ts +9 -6
  102. package/src/geometry/geometry-utils.ts +1 -1
  103. package/src/geometry/geometry.ts +9 -6
  104. package/src/geometry/gpu-geometry.ts +18 -11
  105. package/src/index.ts +3 -0
  106. package/src/lib/clip-space.ts +14 -18
  107. package/src/lib/pipeline-factory.ts +62 -28
  108. package/src/lib/shader-factory.ts +57 -0
  109. package/src/model/model.ts +249 -146
  110. package/src/scenegraph/group-node.ts +14 -10
  111. package/src/scenegraph/model-node.ts +2 -2
  112. package/src/scenegraph/scenegraph-node.ts +2 -2
  113. package/src/shader-inputs.ts +19 -12
  114. package/src/transform/buffer-transform.ts +15 -7
  115. package/src/transform/texture-transform.ts +14 -13
@@ -1,25 +1,24 @@
1
- // luma.gl, MIT license
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
2
3
  // Copyright (c) vis.gl contributors
3
4
 
5
+ // A lot of imports, but then Model is where it all comes together...
4
6
  import type {TypedArray, RenderPipelineProps, RenderPipelineParameters} from '@luma.gl/core';
5
- import type {BufferLayout, VertexArray, TransformFeedback} from '@luma.gl/core';
7
+ import type {BufferLayout, Shader, VertexArray, TransformFeedback} from '@luma.gl/core';
6
8
  import type {AttributeInfo, Binding, UniformValue, PrimitiveTopology} from '@luma.gl/core';
7
- import {
8
- Device,
9
- Buffer,
10
- RenderPipeline,
11
- RenderPass,
12
- UniformStore,
13
- getTypedArrayFromDataType
14
- } from '@luma.gl/core';
15
- import {log, uid, deepEqual, splitUniformsAndBindings, isNumberArray} from '@luma.gl/core';
16
- import {getAttributeInfosFromLayouts} from '@luma.gl/core';
9
+ import {Device, DeviceFeature, Buffer, Texture, TextureView, Sampler} from '@luma.gl/core';
10
+ import {RenderPipeline, RenderPass, UniformStore} from '@luma.gl/core';
11
+ import {log, uid, deepEqual, isObjectEmpty, splitUniformsAndBindings} from '@luma.gl/core';
12
+ import {getTypedArrayFromDataType, getAttributeInfosFromLayouts} from '@luma.gl/core';
13
+
17
14
  import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
18
15
  import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
19
- import {ShaderInputs} from '../shader-inputs';
16
+
20
17
  import type {Geometry} from '../geometry/geometry';
21
18
  import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
19
+ import {ShaderInputs} from '../shader-inputs';
22
20
  import {PipelineFactory} from '../lib/pipeline-factory';
21
+ import {ShaderFactory} from '../lib/shader-factory';
23
22
  import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
24
23
  import {debugFramebuffer} from '../debug/debug-framebuffer';
25
24
 
@@ -28,8 +27,9 @@ const LOG_DRAW_TIMEOUT = 10000;
28
27
 
29
28
  export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
30
29
  source?: string;
31
- vs: {glsl?: string; wgsl?: string} | string | null;
32
- fs: {glsl?: string; wgsl?: string} | string | null;
30
+ vs: string | null;
31
+ fs: string | null;
32
+
33
33
  /** shadertool shader modules (added to shader code) */
34
34
  modules?: ShaderModule[];
35
35
  /** Shadertool module defines (configures shader code)*/
@@ -38,10 +38,6 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
38
38
 
39
39
  /** Shader inputs, used to generated uniform buffers and bindings */
40
40
  shaderInputs?: ShaderInputs;
41
- /** pipeline factory to use to create render pipelines. Defaults to default factory for the device */
42
- pipelineFactory?: PipelineFactory;
43
- /** Shader assembler. Defaults to the ShaderAssembler.getShaderAssembler() */
44
- shaderAssembler?: ShaderAssembler;
45
41
 
46
42
  /** Parameters that are built into the pipeline */
47
43
  parameters?: RenderPipelineParameters;
@@ -59,6 +55,8 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
59
55
  attributes?: Record<string, Buffer>;
60
56
  /** */
61
57
  constantAttributes?: Record<string, TypedArray>;
58
+ /** Some applications intentionally supply unused attributes */
59
+ ignoreUnknownAttributes?: boolean;
62
60
 
63
61
  /** @internal For use with {@link TransformFeedback}, WebGL only. */
64
62
  varyings?: string[];
@@ -67,6 +65,16 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
67
65
 
68
66
  /** Mapped uniforms for shadertool modules */
69
67
  moduleSettings?: Record<string, Record<string, any>>;
68
+
69
+ /** Show shader source in browser? */
70
+ debugShaders?: 'never' | 'errors' | 'warnings' | 'always';
71
+
72
+ /** Factory used to create a {@link RenderPipeline}. Defaults to {@link Device} default factory. */
73
+ pipelineFactory?: PipelineFactory;
74
+ /** Factory used to create a {@link Shader}. Defaults to {@link Device} default factory. */
75
+ shaderFactory?: ShaderFactory;
76
+ /** Shader assembler. Defaults to the ShaderAssembler.getShaderAssembler() */
77
+ shaderAssembler?: ShaderAssembler;
70
78
  };
71
79
 
72
80
  /**
@@ -97,15 +105,21 @@ export class Model {
97
105
 
98
106
  shaderInputs: undefined!,
99
107
  pipelineFactory: undefined!,
108
+ shaderFactory: undefined!,
100
109
  transformFeedback: undefined,
101
- shaderAssembler: ShaderAssembler.getDefaultShaderAssembler()
110
+ shaderAssembler: ShaderAssembler.getDefaultShaderAssembler(),
111
+
112
+ debugShaders: undefined,
113
+ ignoreUnknownAttributes: undefined
102
114
  };
103
115
 
104
116
  readonly device: Device;
105
117
  readonly id: string;
118
+ readonly source: string;
106
119
  readonly vs: string;
107
120
  readonly fs: string;
108
121
  readonly pipelineFactory: PipelineFactory;
122
+ readonly shaderFactory: ShaderFactory;
109
123
  userData: {[key: string]: any} = {};
110
124
 
111
125
  // Fixed properties (change can trigger pipeline rebuild)
@@ -154,12 +168,18 @@ export class Model {
154
168
 
155
169
  _uniformStore: UniformStore;
156
170
 
157
- _pipelineNeedsUpdate: string | false = 'newly created';
158
171
  _attributeInfos: Record<string, AttributeInfo> = {};
159
172
  _gpuGeometry: GPUGeometry | null = null;
160
173
  private _getModuleUniforms: (props?: Record<string, Record<string, any>>) => Record<string, any>;
161
174
  private props: Required<ModelProps>;
162
175
 
176
+ _pipelineNeedsUpdate: string | false = 'newly created';
177
+ private _needsRedraw: string | false = 'initializing';
178
+ private _destroyed = false;
179
+
180
+ /** "Time" of last draw. Monotonically increasing timestamp */
181
+ _lastDrawTimestamp: number = -1;
182
+
163
183
  constructor(device: Device, props: ModelProps) {
164
184
  this.props = {...Model.defaultProps, ...props};
165
185
  props = this.props;
@@ -174,22 +194,6 @@ export class Model {
174
194
  );
175
195
  this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
176
196
 
177
- const isWebGPU = this.device.info.type === 'webgpu';
178
- // TODO - hack to support unified WGSL shader
179
- // TODO - this is wrong, compile a single shader
180
- if (this.props.source) {
181
- if (isWebGPU) {
182
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
183
- }
184
- this.props.fs = this.props.source;
185
- this.props.vs = this.props.source;
186
- }
187
-
188
- // Support WGSL shader layout introspection
189
- if (isWebGPU && typeof this.props.vs !== 'string') {
190
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.vs.wgsl);
191
- }
192
-
193
197
  // Setup shader assembler
194
198
  const platformInfo = getPlatformInfo(device);
195
199
 
@@ -197,15 +201,33 @@ export class Model {
197
201
  const modules =
198
202
  (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
199
203
 
200
- const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleShaders({
201
- platformInfo,
202
- ...this.props,
203
- modules
204
- });
204
+ const isWebGPU = this.device.type === 'webgpu';
205
205
 
206
- this.vs = vs;
207
- this.fs = fs;
208
- this._getModuleUniforms = getUniforms;
206
+ // WebGPU
207
+ // TODO - hack to support unified WGSL shader
208
+ // TODO - this is wrong, compile a single shader
209
+ if (isWebGPU && this.props.source) {
210
+ // WGSL
211
+ this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.props.source);
212
+ const {source, getUniforms} = this.props.shaderAssembler.assembleShader({
213
+ platformInfo,
214
+ ...this.props,
215
+ modules
216
+ });
217
+ this.source = source;
218
+ this._getModuleUniforms = getUniforms;
219
+ } else {
220
+ // GLSL
221
+ const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleShaderPair({
222
+ platformInfo,
223
+ ...this.props,
224
+ modules
225
+ });
226
+
227
+ this.vs = vs;
228
+ this.fs = fs;
229
+ this._getModuleUniforms = getUniforms;
230
+ }
209
231
 
210
232
  this.vertexCount = this.props.vertexCount;
211
233
  this.instanceCount = this.props.instanceCount;
@@ -216,11 +238,12 @@ export class Model {
216
238
 
217
239
  // Geometry, if provided, sets topology and vertex cound
218
240
  if (props.geometry) {
219
- this._gpuGeometry = this.setGeometry(props.geometry);
241
+ this.setGeometry(props.geometry);
220
242
  }
221
243
 
222
244
  this.pipelineFactory =
223
245
  props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
246
+ this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
224
247
 
225
248
  // Create the pipeline
226
249
  // @note order is important
@@ -250,7 +273,9 @@ export class Model {
250
273
  this.setIndexBuffer(props.indexBuffer);
251
274
  }
252
275
  if (props.attributes) {
253
- this.setAttributes(props.attributes);
276
+ this.setAttributes(props.attributes, {
277
+ ignoreUnknownAttributes: props.ignoreUnknownAttributes
278
+ });
254
279
  }
255
280
  if (props.constantAttributes) {
256
281
  this.setConstantAttributes(props.constantAttributes);
@@ -269,44 +294,73 @@ export class Model {
269
294
  this.transformFeedback = props.transformFeedback;
270
295
  }
271
296
 
272
- // TODO - restore?
273
- // this.setUniforms(this._getModuleUniforms()); // Get all default module uniforms
274
-
275
297
  // Catch any access to non-standard props
276
298
  Object.seal(this);
277
299
  }
278
300
 
279
301
  destroy(): void {
302
+ if (this._destroyed) return;
280
303
  this.pipelineFactory.release(this.pipeline);
304
+ this.shaderFactory.release(this.pipeline.vs);
305
+ if (this.pipeline.fs) {
306
+ this.shaderFactory.release(this.pipeline.fs);
307
+ }
281
308
  this._uniformStore.destroy();
309
+ // TODO - mark resource as managed and destroyIfManaged() ?
310
+ this._gpuGeometry?.destroy();
311
+ this._destroyed = true;
282
312
  }
283
313
 
284
314
  // Draw call
285
315
 
316
+ /** Query redraw status. Clears the status. */
317
+ needsRedraw(): false | string {
318
+ // Catch any writes to already bound resources
319
+ if (this._getBindingsUpdateTimestamp() > this._lastDrawTimestamp) {
320
+ this.setNeedsRedraw('contents of bound textures or buffers updated');
321
+ }
322
+ const needsRedraw = this._needsRedraw;
323
+ this._needsRedraw = false;
324
+ return needsRedraw;
325
+ }
326
+
327
+ /** Mark the model as needing a redraw */
328
+ setNeedsRedraw(reason: string): void {
329
+ this._needsRedraw ||= reason;
330
+ }
331
+
286
332
  predraw() {
287
333
  // Update uniform buffers if needed
288
334
  this.updateShaderInputs();
335
+ // Check if the pipeline is invalidated
336
+ this.pipeline = this._updatePipeline();
289
337
  }
290
338
 
291
- draw(renderPass: RenderPass): void {
339
+ draw(renderPass: RenderPass): boolean {
292
340
  this.predraw();
293
341
 
342
+ let drawSuccess: boolean;
294
343
  try {
295
344
  this._logDrawCallStart();
296
345
 
297
- // Check if the pipeline is invalidated
298
- // TODO - this is likely the worst place to do this from performance perspective. Perhaps add a predraw()?
346
+ // Update the pipeline if invalidated
347
+ // TODO - inside RenderPass is likely the worst place to do this from performance perspective.
348
+ // Application can call Model.predraw() to avoid this.
299
349
  this.pipeline = this._updatePipeline();
300
350
 
301
351
  // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
302
352
  // Any caching needs to be done inside the pipeline functions
303
353
  this.pipeline.setBindings(this.bindings);
304
- this.pipeline.setUniforms(this.uniforms);
354
+ if (!isObjectEmpty(this.uniforms)) {
355
+ this.pipeline.setUniformsWebGL(this.uniforms);
356
+ }
305
357
 
306
358
  const {indexBuffer} = this.vertexArray;
307
- const indexCount = indexBuffer ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2) : undefined;
308
-
309
- this.pipeline.draw({
359
+ const indexCount = indexBuffer
360
+ ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2)
361
+ : undefined;
362
+
363
+ drawSuccess = this.pipeline.draw({
310
364
  renderPass,
311
365
  vertexArray: this.vertexArray,
312
366
  vertexCount: this.vertexCount,
@@ -318,6 +372,15 @@ export class Model {
318
372
  this._logDrawCallEnd();
319
373
  }
320
374
  this._logFramebuffer(renderPass);
375
+
376
+ // Update needsRedraw flag
377
+ if (drawSuccess) {
378
+ this._lastDrawTimestamp = this.device.timestamp;
379
+ this._needsRedraw = false;
380
+ } else {
381
+ this._needsRedraw = 'waiting for resource initialization';
382
+ }
383
+ return drawSuccess;
321
384
  }
322
385
 
323
386
  // Update fixed fields (can trigger pipeline rebuild)
@@ -327,38 +390,17 @@ export class Model {
327
390
  * Geometry, set topology and bufferLayout
328
391
  * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
329
392
  */
330
- setGeometry(geometry: GPUGeometry | Geometry): GPUGeometry {
393
+ setGeometry(geometry: GPUGeometry | Geometry | null): void {
394
+ this._gpuGeometry?.destroy();
331
395
  const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
332
- this.setTopology(gpuGeometry.topology || 'triangle-list');
333
- this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
334
- if (this.vertexArray) {
335
- this._setGeometryAttributes(gpuGeometry);
336
- }
337
- return gpuGeometry;
338
- }
339
-
340
- /**
341
- * Updates the optional geometry attributes
342
- * Geometry, sets several attributes, indexBuffer, and also vertex count
343
- * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
344
- */
345
- _setGeometryAttributes(gpuGeometry: GPUGeometry): void {
346
- // Filter geometry attribute so that we don't issue warnings for unused attributes
347
- const attributes = {...gpuGeometry.attributes};
348
- for (const [attributeName] of Object.entries(attributes)) {
349
- if (
350
- !this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
351
- attributeName !== 'positions'
352
- ) {
353
- delete attributes[attributeName];
396
+ if (gpuGeometry) {
397
+ this.setTopology(gpuGeometry.topology || 'triangle-list');
398
+ this.bufferLayout = mergeBufferLayouts(gpuGeometry.bufferLayout, this.bufferLayout);
399
+ if (this.vertexArray) {
400
+ this._setGeometryAttributes(gpuGeometry);
354
401
  }
355
402
  }
356
-
357
- // TODO - delete previous geometry?
358
- this.vertexCount = gpuGeometry.vertexCount;
359
- this.setIndexBuffer(gpuGeometry.indices);
360
- this.setAttributes(gpuGeometry.attributes, 'ignore-unknown');
361
- this.setAttributes(attributes);
403
+ this._gpuGeometry = gpuGeometry;
362
404
  }
363
405
 
364
406
  /**
@@ -374,13 +416,12 @@ export class Model {
374
416
 
375
417
  /**
376
418
  * Updates the buffer layout.
377
- * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
419
+ * @note Triggers a pipeline rebuild / pipeline cache fetch
378
420
  */
379
421
  setBufferLayout(bufferLayout: BufferLayout[]): void {
380
422
  this.bufferLayout = this._gpuGeometry
381
423
  ? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
382
424
  : bufferLayout;
383
- this._setPipelineNeedsUpdate('bufferLayout');
384
425
 
385
426
  // Recreate the pipeline
386
427
  this.pipeline = this._updatePipeline();
@@ -395,6 +436,8 @@ export class Model {
395
436
  if (this._gpuGeometry) {
396
437
  this._setGeometryAttributes(this._gpuGeometry);
397
438
  }
439
+
440
+ this._setPipelineNeedsUpdate('bufferLayout');
398
441
  }
399
442
 
400
443
  /**
@@ -417,6 +460,7 @@ export class Model {
417
460
  */
418
461
  setVertexCount(vertexCount: number): void {
419
462
  this.vertexCount = vertexCount;
463
+ this.setNeedsRedraw('vertexCount');
420
464
  }
421
465
 
422
466
  /**
@@ -425,6 +469,7 @@ export class Model {
425
469
  */
426
470
  setInstanceCount(instanceCount: number): void {
427
471
  this.instanceCount = instanceCount;
472
+ this.setNeedsRedraw('instanceCount');
428
473
  }
429
474
 
430
475
  setShaderInputs(shaderInputs: ShaderInputs): void {
@@ -435,39 +480,13 @@ export class Model {
435
480
  const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
436
481
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
437
482
  }
438
- }
439
-
440
- /**
441
- * Updates shader module settings (which results in uniforms being set)
442
- */
443
- setShaderModuleProps(props: Record<string, any>): void {
444
- const uniforms = this._getModuleUniforms(props);
445
-
446
- // Extract textures & framebuffers set by the modules
447
- // TODO better way to extract bindings
448
- const keys = Object.keys(uniforms).filter(k => {
449
- const uniform = uniforms[k];
450
- return !isNumberArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean';
451
- });
452
- const bindings: Record<string, Binding> = {};
453
- for (const k of keys) {
454
- bindings[k] = uniforms[k];
455
- delete uniforms[k];
456
- }
483
+ this.setNeedsRedraw('shaderInputs');
457
484
  }
458
485
 
459
486
  updateShaderInputs(): void {
460
487
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
461
- }
462
-
463
- /**
464
- * @deprecated Updates shader module settings (which results in uniforms being set)
465
- */
466
- updateModuleSettings(props: Record<string, any>): void {
467
- log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
468
- const {bindings, uniforms} = splitUniformsAndBindings(this._getModuleUniforms(props));
469
- Object.assign(this.bindings, bindings);
470
- Object.assign(this.uniforms, uniforms);
488
+ // TODO - this is already tracked through buffer/texture update times?
489
+ this.setNeedsRedraw('shaderInputs');
471
490
  }
472
491
 
473
492
  /**
@@ -475,17 +494,15 @@ export class Model {
475
494
  */
476
495
  setBindings(bindings: Record<string, Binding>): void {
477
496
  Object.assign(this.bindings, bindings);
497
+ this.setNeedsRedraw('bindings');
478
498
  }
479
499
 
480
500
  /**
481
- * Sets individual uniforms
482
- * @deprecated WebGL only, use uniform buffers for portability
483
- * @param uniforms
484
- * @returns self for chaining
501
+ * Updates optional transform feedback. WebGL only.
485
502
  */
486
- setUniforms(uniforms: Record<string, UniformValue>): void {
487
- this.pipeline.setUniforms(uniforms);
488
- Object.assign(this.uniforms, uniforms);
503
+ setTransformFeedback(transformFeedback: TransformFeedback | null): void {
504
+ this.transformFeedback = transformFeedback;
505
+ this.setNeedsRedraw('transformFeedback');
489
506
  }
490
507
 
491
508
  /**
@@ -494,27 +511,26 @@ export class Model {
494
511
  */
495
512
  setIndexBuffer(indexBuffer: Buffer | null): void {
496
513
  this.vertexArray.setIndexBuffer(indexBuffer);
497
- }
498
-
499
- /**
500
- * Updates optional transform feedback. WebGL only.
501
- */
502
- setTransformFeedback(transformFeedback: TransformFeedback | null): void {
503
- this.transformFeedback = transformFeedback;
514
+ this.setNeedsRedraw('indexBuffer');
504
515
  }
505
516
 
506
517
  /**
507
518
  * Sets attributes (buffers)
508
519
  * @note Overrides any attributes previously set with the same name
509
520
  */
510
- setAttributes(buffers: Record<string, Buffer>, _option?: 'ignore-unknown'): void {
521
+ setAttributes(
522
+ buffers: Record<string, Buffer>,
523
+ options?: {ignoreUnknownAttributes?: boolean}
524
+ ): void {
511
525
  if (buffers.indices) {
512
526
  log.warn(
513
527
  `Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
514
528
  )();
515
529
  }
516
530
  for (const [bufferName, buffer] of Object.entries(buffers)) {
517
- const bufferLayout = this.bufferLayout.find(layout => getAttributeNames(layout).includes(bufferName));
531
+ const bufferLayout = this.bufferLayout.find(layout =>
532
+ getAttributeNames(layout).includes(bufferName)
533
+ );
518
534
  if (!bufferLayout) {
519
535
  log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
520
536
  continue; // eslint-disable-line no-continue
@@ -530,12 +546,13 @@ export class Model {
530
546
  set = true;
531
547
  }
532
548
  }
533
- if (!set && _option !== 'ignore-unknown') {
549
+ if (!set && (options?.ignoreUnknownAttributes || this.props.ignoreUnknownAttributes)) {
534
550
  log.warn(
535
551
  `Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`
536
552
  )();
537
553
  }
538
554
  }
555
+ this.setNeedsRedraw('attributes');
539
556
  }
540
557
 
541
558
  /**
@@ -550,43 +567,125 @@ export class Model {
550
567
  for (const [attributeName, value] of Object.entries(attributes)) {
551
568
  const attributeInfo = this._attributeInfos[attributeName];
552
569
  if (attributeInfo) {
553
- this.vertexArray.setConstant(attributeInfo.location, value);
570
+ this.vertexArray.setConstantWebGL(attributeInfo.location, value);
554
571
  } else {
555
572
  log.warn(
556
573
  `Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`
557
574
  )();
558
575
  }
559
576
  }
577
+ this.setNeedsRedraw('constants');
560
578
  }
561
579
 
580
+ // DEPRECATED METHODS
581
+
582
+ /**
583
+ * Sets individual uniforms
584
+ * @deprecated WebGL only, use uniform buffers for portability
585
+ * @param uniforms
586
+ */
587
+ setUniforms(uniforms: Record<string, UniformValue>): void {
588
+ if (!isObjectEmpty(uniforms)) {
589
+ this.pipeline.setUniformsWebGL(uniforms);
590
+ Object.assign(this.uniforms, uniforms);
591
+ }
592
+ this.setNeedsRedraw('uniforms');
593
+ }
594
+
595
+ /**
596
+ * @deprecated Updates shader module settings (which results in uniforms being set)
597
+ */
598
+ updateModuleSettings(props: Record<string, any>): void {
599
+ log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
600
+ const {bindings, uniforms} = splitUniformsAndBindings(this._getModuleUniforms(props));
601
+ Object.assign(this.bindings, bindings);
602
+ Object.assign(this.uniforms, uniforms);
603
+ this.setNeedsRedraw('moduleSettings');
604
+ }
605
+
606
+ // Internal methods
607
+
608
+ /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
609
+ _getBindingsUpdateTimestamp(): number {
610
+ let timestamp = 0;
611
+ for (const binding of Object.values(this.bindings)) {
612
+ if (binding instanceof TextureView) {
613
+ timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
614
+ } else if (binding instanceof Buffer || binding instanceof Texture) {
615
+ timestamp = Math.max(timestamp, binding.updateTimestamp);
616
+ } else if (!(binding instanceof Sampler)) {
617
+ timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
618
+ }
619
+ }
620
+ return timestamp;
621
+ }
622
+
623
+ /**
624
+ * Updates the optional geometry attributes
625
+ * Geometry, sets several attributes, indexBuffer, and also vertex count
626
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
627
+ */
628
+ _setGeometryAttributes(gpuGeometry: GPUGeometry): void {
629
+ // Filter geometry attribute so that we don't issue warnings for unused attributes
630
+ const attributes = {...gpuGeometry.attributes};
631
+ for (const [attributeName] of Object.entries(attributes)) {
632
+ if (
633
+ !this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
634
+ attributeName !== 'positions'
635
+ ) {
636
+ delete attributes[attributeName];
637
+ }
638
+ }
639
+
640
+ // TODO - delete previous geometry?
641
+ this.vertexCount = gpuGeometry.vertexCount;
642
+ this.setIndexBuffer(gpuGeometry.indices);
643
+ this.setAttributes(gpuGeometry.attributes, {ignoreUnknownAttributes: true});
644
+ this.setAttributes(attributes, {ignoreUnknownAttributes: this.props.ignoreUnknownAttributes});
645
+
646
+ this.setNeedsRedraw('geometry attributes');
647
+ }
648
+
649
+ /** Mark pipeline as needing update */
562
650
  _setPipelineNeedsUpdate(reason: string): void {
563
- this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
651
+ this._pipelineNeedsUpdate ||= reason;
652
+ this.setNeedsRedraw(reason);
564
653
  }
565
654
 
655
+ /** Update pipeline if needed */
566
656
  _updatePipeline(): RenderPipeline {
567
657
  if (this._pipelineNeedsUpdate) {
658
+ let prevShaderVs: Shader | null = null;
659
+ let prevShaderFs: Shader | null = null;
568
660
  if (this.pipeline) {
569
661
  log.log(
570
662
  1,
571
663
  `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`
572
664
  )();
665
+ prevShaderVs = this.pipeline.vs;
666
+ prevShaderFs = this.pipeline.fs;
573
667
  }
574
668
 
575
669
  this._pipelineNeedsUpdate = false;
576
670
 
577
- const vs = this.device.createShader({
671
+ const vs = this.shaderFactory.createShader({
578
672
  id: `${this.id}-vertex`,
579
673
  stage: 'vertex',
580
- source: this.vs
674
+ source: this.source || this.vs,
675
+ debug: this.props.debugShaders
581
676
  });
582
677
 
583
- const fs = this.fs
584
- ? this.device.createShader({
678
+ let fs: Shader | null = null;
679
+ if (this.source) {
680
+ fs = vs;
681
+ } else if (this.fs) {
682
+ fs = this.shaderFactory.createShader({
585
683
  id: `${this.id}-fragment`,
586
684
  stage: 'fragment',
587
- source: this.fs
588
- })
589
- : null;
685
+ source: this.source || this.fs,
686
+ debug: this.props.debugShaders
687
+ });
688
+ }
590
689
 
591
690
  this.pipeline = this.pipelineFactory.createRenderPipeline({
592
691
  ...this.props,
@@ -601,6 +700,9 @@ export class Model {
601
700
  this.pipeline.shaderLayout,
602
701
  this.bufferLayout
603
702
  );
703
+
704
+ if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
705
+ if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
604
706
  }
605
707
  return this.pipeline;
606
708
  }
@@ -651,7 +753,7 @@ export class Model {
651
753
  const debugFramebuffers = log.get('framebuffer');
652
754
  this._drawCount++;
653
755
  // Update first 3 frames and then every 60 frames
654
- if (!debugFramebuffers || ((this._drawCount++ > 3) && (this._drawCount % 60))) {
756
+ if (!debugFramebuffers || (this._drawCount++ > 3 && this._drawCount % 60)) {
655
757
  return;
656
758
  }
657
759
  // TODO - display framebuffer output in debug window
@@ -717,11 +819,12 @@ function mergeBufferLayouts(layouts1: BufferLayout[], layouts2: BufferLayout[]):
717
819
  /** Create a shadertools platform info from the Device */
718
820
  export function getPlatformInfo(device: Device): PlatformInfo {
719
821
  return {
720
- type: device.info.type,
822
+ type: device.type,
721
823
  shaderLanguage: device.info.shadingLanguage,
722
824
  shaderLanguageVersion: device.info.shadingLanguageVersion as 100 | 300,
723
825
  gpu: device.info.gpu,
724
- features: device.features
826
+ // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API
827
+ features: device.features as unknown as Set<DeviceFeature>
725
828
  };
726
829
  }
727
830