@luma.gl/engine 9.0.0-beta.1 → 9.0.0-beta.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 (154) hide show
  1. package/dist/animation/key-frames.js +54 -54
  2. package/dist/animation/timeline.d.ts.map +1 -1
  3. package/dist/animation/timeline.js +95 -100
  4. package/dist/animation-loop/animation-loop-template.d.ts +1 -1
  5. package/dist/animation-loop/animation-loop-template.d.ts.map +1 -1
  6. package/dist/animation-loop/animation-loop-template.js +19 -5
  7. package/dist/animation-loop/animation-loop.d.ts +2 -2
  8. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  9. package/dist/animation-loop/animation-loop.js +433 -356
  10. package/dist/animation-loop/animation-props.d.ts +2 -2
  11. package/dist/animation-loop/animation-props.d.ts.map +1 -1
  12. package/dist/animation-loop/animation-props.js +0 -1
  13. package/dist/animation-loop/make-animation-loop.d.ts +2 -2
  14. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  15. package/dist/animation-loop/make-animation-loop.js +28 -24
  16. package/dist/computation.d.ts +95 -0
  17. package/dist/computation.d.ts.map +1 -0
  18. package/dist/computation.js +248 -0
  19. package/dist/debug/copy-texture-to-image.d.ts.map +1 -1
  20. package/dist/debug/copy-texture-to-image.js +39 -42
  21. package/dist/debug/debug-framebuffer.d.ts.map +1 -1
  22. package/dist/debug/debug-framebuffer.js +43 -40
  23. package/dist/debug/debug-shader-layout.js +24 -25
  24. package/dist/debug/pixel-data-utils.d.ts.map +1 -1
  25. package/dist/debug/pixel-data-utils.js +34 -36
  26. package/dist/dist.dev.js +2538 -3027
  27. package/dist/dist.min.js +102 -0
  28. package/dist/geometries/cone-geometry.d.ts +1 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js +11 -17
  31. package/dist/geometries/cube-geometry.d.ts +1 -1
  32. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cube-geometry.js +190 -61
  34. package/dist/geometries/cylinder-geometry.d.ts +1 -1
  35. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  36. package/dist/geometries/cylinder-geometry.js +9 -14
  37. package/dist/geometries/ico-sphere-geometry.d.ts +1 -1
  38. package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -1
  39. package/dist/geometries/ico-sphere-geometry.js +141 -160
  40. package/dist/geometries/plane-geometry.d.ts +1 -1
  41. package/dist/geometries/plane-geometry.d.ts.map +1 -1
  42. package/dist/geometries/plane-geometry.js +92 -110
  43. package/dist/geometries/sphere-geometry.d.ts +1 -1
  44. package/dist/geometries/sphere-geometry.d.ts.map +1 -1
  45. package/dist/geometries/sphere-geometry.js +76 -95
  46. package/dist/geometries/truncated-cone-geometry.d.ts +1 -1
  47. package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -1
  48. package/dist/geometries/truncated-cone-geometry.js +99 -117
  49. package/dist/geometry/geometry-table.d.ts.map +1 -1
  50. package/dist/geometry/geometry-table.js +3 -1
  51. package/dist/geometry/geometry-utils.js +35 -32
  52. package/dist/geometry/geometry.d.ts.map +1 -1
  53. package/dist/geometry/geometry.js +80 -71
  54. package/dist/geometry/gpu-geometry.d.ts +1 -1
  55. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  56. package/dist/geometry/gpu-geometry.js +79 -99
  57. package/dist/geometry/gpu-table.js +41 -1
  58. package/dist/index.cjs +725 -409
  59. package/dist/index.cjs.map +7 -0
  60. package/dist/index.d.ts +43 -40
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +6 -1
  63. package/dist/lib/clip-space.d.ts +2 -2
  64. package/dist/lib/clip-space.d.ts.map +1 -1
  65. package/dist/lib/clip-space.js +28 -34
  66. package/dist/lib/pipeline-factory.d.ts +13 -14
  67. package/dist/lib/pipeline-factory.d.ts.map +1 -1
  68. package/dist/lib/pipeline-factory.js +86 -85
  69. package/dist/lib/shader-factory.d.ts +17 -0
  70. package/dist/lib/shader-factory.d.ts.map +1 -0
  71. package/dist/lib/shader-factory.js +46 -0
  72. package/dist/model/model.d.ts +59 -46
  73. package/dist/model/model.d.ts.map +1 -1
  74. package/dist/model/model.js +635 -411
  75. package/dist/scenegraph/group-node.d.ts +1 -1
  76. package/dist/scenegraph/group-node.d.ts.map +1 -1
  77. package/dist/scenegraph/group-node.js +73 -83
  78. package/dist/scenegraph/model-node.d.ts +3 -3
  79. package/dist/scenegraph/model-node.d.ts.map +1 -1
  80. package/dist/scenegraph/model-node.js +31 -24
  81. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  82. package/dist/scenegraph/scenegraph-node.js +136 -124
  83. package/dist/shader-inputs.d.ts.map +1 -1
  84. package/dist/shader-inputs.js +99 -58
  85. package/dist/transform/buffer-transform.d.ts +1 -1
  86. package/dist/transform/buffer-transform.d.ts.map +1 -1
  87. package/dist/transform/buffer-transform.js +65 -57
  88. package/dist/transform/texture-transform.d.ts +1 -1
  89. package/dist/transform/texture-transform.d.ts.map +1 -1
  90. package/dist/transform/texture-transform.js +109 -114
  91. package/dist.min.js +3 -271
  92. package/package.json +10 -9
  93. package/src/animation/timeline.ts +20 -20
  94. package/src/animation-loop/animation-loop-template.ts +10 -8
  95. package/src/animation-loop/animation-loop.ts +20 -10
  96. package/src/animation-loop/animation-props.ts +1 -1
  97. package/src/animation-loop/make-animation-loop.ts +17 -8
  98. package/src/computation.ts +346 -0
  99. package/src/debug/copy-texture-to-image.ts +9 -11
  100. package/src/debug/debug-framebuffer.ts +16 -3
  101. package/src/debug/debug-shader-layout.ts +1 -1
  102. package/src/debug/pixel-data-utils.ts +3 -6
  103. package/src/geometries/cube-geometry.ts +17 -13
  104. package/src/geometries/ico-sphere-geometry.ts +1 -1
  105. package/src/geometries/plane-geometry.ts +1 -1
  106. package/src/geometries/sphere-geometry.ts +1 -1
  107. package/src/geometries/truncated-cone-geometry.ts +2 -1
  108. package/src/geometry/geometry-table.ts +9 -6
  109. package/src/geometry/geometry-utils.ts +16 -0
  110. package/src/geometry/geometry.ts +9 -6
  111. package/src/geometry/gpu-geometry.ts +18 -11
  112. package/src/index.ts +4 -1
  113. package/src/lib/clip-space.ts +16 -19
  114. package/src/lib/pipeline-factory.ts +71 -64
  115. package/src/lib/shader-factory.ts +57 -0
  116. package/src/model/model.ts +255 -146
  117. package/src/scenegraph/group-node.ts +14 -10
  118. package/src/scenegraph/model-node.ts +2 -2
  119. package/src/scenegraph/scenegraph-node.ts +2 -2
  120. package/src/shader-inputs.ts +19 -12
  121. package/src/transform/buffer-transform.ts +16 -8
  122. package/src/transform/texture-transform.ts +14 -15
  123. package/dist/animation/key-frames.js.map +0 -1
  124. package/dist/animation/timeline.js.map +0 -1
  125. package/dist/animation-loop/animation-loop-template.js.map +0 -1
  126. package/dist/animation-loop/animation-loop.js.map +0 -1
  127. package/dist/animation-loop/animation-props.js.map +0 -1
  128. package/dist/animation-loop/make-animation-loop.js.map +0 -1
  129. package/dist/debug/copy-texture-to-image.js.map +0 -1
  130. package/dist/debug/debug-framebuffer.js.map +0 -1
  131. package/dist/debug/debug-shader-layout.js.map +0 -1
  132. package/dist/debug/pixel-data-utils.js.map +0 -1
  133. package/dist/geometries/cone-geometry.js.map +0 -1
  134. package/dist/geometries/cube-geometry.js.map +0 -1
  135. package/dist/geometries/cylinder-geometry.js.map +0 -1
  136. package/dist/geometries/ico-sphere-geometry.js.map +0 -1
  137. package/dist/geometries/plane-geometry.js.map +0 -1
  138. package/dist/geometries/sphere-geometry.js.map +0 -1
  139. package/dist/geometries/truncated-cone-geometry.js.map +0 -1
  140. package/dist/geometry/geometry-table.js.map +0 -1
  141. package/dist/geometry/geometry-utils.js.map +0 -1
  142. package/dist/geometry/geometry.js.map +0 -1
  143. package/dist/geometry/gpu-geometry.js.map +0 -1
  144. package/dist/geometry/gpu-table.js.map +0 -1
  145. package/dist/index.js.map +0 -1
  146. package/dist/lib/clip-space.js.map +0 -1
  147. package/dist/lib/pipeline-factory.js.map +0 -1
  148. package/dist/model/model.js.map +0 -1
  149. package/dist/scenegraph/group-node.js.map +0 -1
  150. package/dist/scenegraph/model-node.js.map +0 -1
  151. package/dist/scenegraph/scenegraph-node.js.map +0 -1
  152. package/dist/shader-inputs.js.map +0 -1
  153. package/dist/transform/buffer-transform.js.map +0 -1
  154. package/dist/transform/texture-transform.js.map +0 -1
@@ -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,14 +55,26 @@ 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
- /** @internal For use with {@link TransformFeedback}, WebGL 2 only. */
61
+ /** @internal For use with {@link TransformFeedback}, WebGL only. */
64
62
  varyings?: string[];
65
63
 
66
64
  transformFeedback?: TransformFeedback;
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,35 +194,40 @@ 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);
199
+
200
+ // Extract modules from shader inputs if not supplied
195
201
  const modules =
196
202
  (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
197
- const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleShaders({
198
- platformInfo,
199
- ...this.props,
200
- modules
201
- });
202
203
 
203
- this.vs = vs;
204
- this.fs = fs;
205
- this._getModuleUniforms = getUniforms;
204
+ const isWebGPU = this.device.type === 'webgpu';
205
+
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
+ }
206
231
 
207
232
  this.vertexCount = this.props.vertexCount;
208
233
  this.instanceCount = this.props.instanceCount;
@@ -213,11 +238,12 @@ export class Model {
213
238
 
214
239
  // Geometry, if provided, sets topology and vertex cound
215
240
  if (props.geometry) {
216
- this._gpuGeometry = this.setGeometry(props.geometry);
241
+ this.setGeometry(props.geometry);
217
242
  }
218
243
 
219
244
  this.pipelineFactory =
220
245
  props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
246
+ this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
221
247
 
222
248
  // Create the pipeline
223
249
  // @note order is important
@@ -247,7 +273,9 @@ export class Model {
247
273
  this.setIndexBuffer(props.indexBuffer);
248
274
  }
249
275
  if (props.attributes) {
250
- this.setAttributes(props.attributes);
276
+ this.setAttributes(props.attributes, {
277
+ ignoreUnknownAttributes: props.ignoreUnknownAttributes
278
+ });
251
279
  }
252
280
  if (props.constantAttributes) {
253
281
  this.setConstantAttributes(props.constantAttributes);
@@ -266,51 +294,93 @@ export class Model {
266
294
  this.transformFeedback = props.transformFeedback;
267
295
  }
268
296
 
269
- // WebGL1?
270
- // this.setUniforms(this._getModuleUniforms()); // Get all default module uniforms
271
-
272
297
  // Catch any access to non-standard props
273
298
  Object.seal(this);
274
299
  }
275
300
 
276
301
  destroy(): void {
302
+ if (this._destroyed) return;
277
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
+ }
278
308
  this._uniformStore.destroy();
309
+ // TODO - mark resource as managed and destroyIfManaged() ?
310
+ this._gpuGeometry?.destroy();
311
+ this._destroyed = true;
279
312
  }
280
313
 
281
314
  // Draw call
282
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
+
283
332
  predraw() {
284
333
  // Update uniform buffers if needed
285
334
  this.updateShaderInputs();
335
+ // Check if the pipeline is invalidated
336
+ this.pipeline = this._updatePipeline();
286
337
  }
287
338
 
288
- draw(renderPass: RenderPass): void {
339
+ draw(renderPass: RenderPass): boolean {
289
340
  this.predraw();
290
341
 
342
+ let drawSuccess: boolean;
291
343
  try {
292
344
  this._logDrawCallStart();
293
345
 
294
- // Check if the pipeline is invalidated
295
- // 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.
296
349
  this.pipeline = this._updatePipeline();
297
350
 
298
351
  // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
299
352
  // Any caching needs to be done inside the pipeline functions
300
353
  this.pipeline.setBindings(this.bindings);
301
- this.pipeline.setUniforms(this.uniforms);
354
+ if (!isObjectEmpty(this.uniforms)) {
355
+ this.pipeline.setUniformsWebGL(this.uniforms);
356
+ }
357
+
358
+ const {indexBuffer} = this.vertexArray;
359
+ const indexCount = indexBuffer
360
+ ? indexBuffer.byteLength / (indexBuffer.indexType === 'uint32' ? 4 : 2)
361
+ : undefined;
302
362
 
303
- this.pipeline.draw({
363
+ drawSuccess = this.pipeline.draw({
304
364
  renderPass,
305
365
  vertexArray: this.vertexArray,
306
366
  vertexCount: this.vertexCount,
307
367
  instanceCount: this.instanceCount,
368
+ indexCount,
308
369
  transformFeedback: this.transformFeedback
309
370
  });
310
371
  } finally {
311
372
  this._logDrawCallEnd();
312
373
  }
313
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;
314
384
  }
315
385
 
316
386
  // Update fixed fields (can trigger pipeline rebuild)
@@ -320,38 +390,17 @@ export class Model {
320
390
  * Geometry, set topology and bufferLayout
321
391
  * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
322
392
  */
323
- setGeometry(geometry: GPUGeometry | Geometry): GPUGeometry {
393
+ setGeometry(geometry: GPUGeometry | Geometry | null): void {
394
+ this._gpuGeometry?.destroy();
324
395
  const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
325
- this.setTopology(gpuGeometry.topology || 'triangle-list');
326
- this.bufferLayout = mergeBufferLayouts(this.bufferLayout, gpuGeometry.bufferLayout);
327
- if (this.vertexArray) {
328
- this._setGeometryAttributes(gpuGeometry);
329
- }
330
- return gpuGeometry;
331
- }
332
-
333
- /**
334
- * Updates the optional geometry attributes
335
- * Geometry, sets several attributes, indexBuffer, and also vertex count
336
- * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
337
- */
338
- _setGeometryAttributes(gpuGeometry: GPUGeometry): void {
339
- // Filter geometry attribute so that we don't issue warnings for unused attributes
340
- const attributes = {...gpuGeometry.attributes};
341
- for (const [attributeName] of Object.entries(attributes)) {
342
- if (
343
- !this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
344
- attributeName !== 'positions'
345
- ) {
346
- 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);
347
401
  }
348
402
  }
349
-
350
- // TODO - delete previous geometry?
351
- this.vertexCount = gpuGeometry.vertexCount;
352
- this.setIndexBuffer(gpuGeometry.indices);
353
- this.setAttributes(gpuGeometry.attributes, 'ignore-unknown');
354
- this.setAttributes(attributes);
403
+ this._gpuGeometry = gpuGeometry;
355
404
  }
356
405
 
357
406
  /**
@@ -367,7 +416,7 @@ export class Model {
367
416
 
368
417
  /**
369
418
  * Updates the buffer layout.
370
- * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
419
+ * @note Triggers a pipeline rebuild / pipeline cache fetch
371
420
  */
372
421
  setBufferLayout(bufferLayout: BufferLayout[]): void {
373
422
  this.bufferLayout = this._gpuGeometry
@@ -410,6 +459,7 @@ export class Model {
410
459
  */
411
460
  setVertexCount(vertexCount: number): void {
412
461
  this.vertexCount = vertexCount;
462
+ this.setNeedsRedraw('vertexCount');
413
463
  }
414
464
 
415
465
  /**
@@ -418,6 +468,7 @@ export class Model {
418
468
  */
419
469
  setInstanceCount(instanceCount: number): void {
420
470
  this.instanceCount = instanceCount;
471
+ this.setNeedsRedraw('instanceCount');
421
472
  }
422
473
 
423
474
  setShaderInputs(shaderInputs: ShaderInputs): void {
@@ -428,39 +479,13 @@ export class Model {
428
479
  const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
429
480
  this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
430
481
  }
431
- }
432
-
433
- /**
434
- * Updates shader module settings (which results in uniforms being set)
435
- */
436
- setShaderModuleProps(props: Record<string, any>): void {
437
- const uniforms = this._getModuleUniforms(props);
438
-
439
- // Extract textures & framebuffers set by the modules
440
- // TODO better way to extract bindings
441
- const keys = Object.keys(uniforms).filter(k => {
442
- const uniform = uniforms[k];
443
- return !isNumberArray(uniform) && typeof uniform !== 'number' && typeof uniform !== 'boolean';
444
- });
445
- const bindings: Record<string, Binding> = {};
446
- for (const k of keys) {
447
- bindings[k] = uniforms[k];
448
- delete uniforms[k];
449
- }
482
+ this.setNeedsRedraw('shaderInputs');
450
483
  }
451
484
 
452
485
  updateShaderInputs(): void {
453
486
  this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
454
- }
455
-
456
- /**
457
- * @deprecated Updates shader module settings (which results in uniforms being set)
458
- */
459
- updateModuleSettings(props: Record<string, any>): void {
460
- log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
461
- const {bindings, uniforms} = splitUniformsAndBindings(this._getModuleUniforms(props));
462
- Object.assign(this.bindings, bindings);
463
- Object.assign(this.uniforms, uniforms);
487
+ // TODO - this is already tracked through buffer/texture update times?
488
+ this.setNeedsRedraw('shaderInputs');
464
489
  }
465
490
 
466
491
  /**
@@ -468,17 +493,15 @@ export class Model {
468
493
  */
469
494
  setBindings(bindings: Record<string, Binding>): void {
470
495
  Object.assign(this.bindings, bindings);
496
+ this.setNeedsRedraw('bindings');
471
497
  }
472
498
 
473
499
  /**
474
- * Sets individual uniforms
475
- * @deprecated WebGL only, use uniform buffers for portability
476
- * @param uniforms
477
- * @returns self for chaining
500
+ * Updates optional transform feedback. WebGL only.
478
501
  */
479
- setUniforms(uniforms: Record<string, UniformValue>): void {
480
- this.pipeline.setUniforms(uniforms);
481
- Object.assign(this.uniforms, uniforms);
502
+ setTransformFeedback(transformFeedback: TransformFeedback | null): void {
503
+ this.transformFeedback = transformFeedback;
504
+ this.setNeedsRedraw('transformFeedback');
482
505
  }
483
506
 
484
507
  /**
@@ -487,27 +510,26 @@ export class Model {
487
510
  */
488
511
  setIndexBuffer(indexBuffer: Buffer | null): void {
489
512
  this.vertexArray.setIndexBuffer(indexBuffer);
490
- }
491
-
492
- /**
493
- * Updates optional transform feedback. WebGL 2 only.
494
- */
495
- setTransformFeedback(transformFeedback: TransformFeedback | null): void {
496
- this.transformFeedback = transformFeedback;
513
+ this.setNeedsRedraw('indexBuffer');
497
514
  }
498
515
 
499
516
  /**
500
517
  * Sets attributes (buffers)
501
518
  * @note Overrides any attributes previously set with the same name
502
519
  */
503
- setAttributes(buffers: Record<string, Buffer>, _option?: 'ignore-unknown'): void {
520
+ setAttributes(
521
+ buffers: Record<string, Buffer>,
522
+ options?: {ignoreUnknownAttributes?: boolean}
523
+ ): void {
504
524
  if (buffers.indices) {
505
525
  log.warn(
506
526
  `Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
507
527
  )();
508
528
  }
509
529
  for (const [bufferName, buffer] of Object.entries(buffers)) {
510
- const bufferLayout = this.bufferLayout.find(layout => getAttributeNames(layout).includes(bufferName));
530
+ const bufferLayout = this.bufferLayout.find(layout =>
531
+ getAttributeNames(layout).includes(bufferName)
532
+ );
511
533
  if (!bufferLayout) {
512
534
  log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
513
535
  continue; // eslint-disable-line no-continue
@@ -523,12 +545,13 @@ export class Model {
523
545
  set = true;
524
546
  }
525
547
  }
526
- if (!set && _option !== 'ignore-unknown') {
548
+ if (!set && (options?.ignoreUnknownAttributes || this.props.ignoreUnknownAttributes)) {
527
549
  log.warn(
528
550
  `Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`
529
551
  )();
530
552
  }
531
553
  }
554
+ this.setNeedsRedraw('attributes');
532
555
  }
533
556
 
534
557
  /**
@@ -543,45 +566,127 @@ export class Model {
543
566
  for (const [attributeName, value] of Object.entries(attributes)) {
544
567
  const attributeInfo = this._attributeInfos[attributeName];
545
568
  if (attributeInfo) {
546
- this.vertexArray.setConstant(attributeInfo.location, value);
569
+ this.vertexArray.setConstantWebGL(attributeInfo.location, value);
547
570
  } else {
548
571
  log.warn(
549
572
  `Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`
550
573
  )();
551
574
  }
552
575
  }
576
+ this.setNeedsRedraw('constants');
553
577
  }
554
578
 
579
+ // DEPRECATED METHODS
580
+
581
+ /**
582
+ * Sets individual uniforms
583
+ * @deprecated WebGL only, use uniform buffers for portability
584
+ * @param uniforms
585
+ */
586
+ setUniforms(uniforms: Record<string, UniformValue>): void {
587
+ if (!isObjectEmpty(uniforms)) {
588
+ this.pipeline.setUniformsWebGL(uniforms);
589
+ Object.assign(this.uniforms, uniforms);
590
+ }
591
+ this.setNeedsRedraw('uniforms');
592
+ }
593
+
594
+ /**
595
+ * @deprecated Updates shader module settings (which results in uniforms being set)
596
+ */
597
+ updateModuleSettings(props: Record<string, any>): void {
598
+ log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')();
599
+ const {bindings, uniforms} = splitUniformsAndBindings(this._getModuleUniforms(props));
600
+ Object.assign(this.bindings, bindings);
601
+ Object.assign(this.uniforms, uniforms);
602
+ this.setNeedsRedraw('moduleSettings');
603
+ }
604
+
605
+ // Internal methods
606
+
607
+ /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */
608
+ _getBindingsUpdateTimestamp(): number {
609
+ let timestamp = 0;
610
+ for (const binding of Object.values(this.bindings)) {
611
+ if (binding instanceof TextureView) {
612
+ timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
613
+ } else if (binding instanceof Buffer || binding instanceof Texture) {
614
+ timestamp = Math.max(timestamp, binding.updateTimestamp);
615
+ } else if (!(binding instanceof Sampler)) {
616
+ timestamp = Math.max(timestamp, binding.buffer.updateTimestamp);
617
+ }
618
+ }
619
+ return timestamp;
620
+ }
621
+
622
+ /**
623
+ * Updates the optional geometry attributes
624
+ * Geometry, sets several attributes, indexBuffer, and also vertex count
625
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
626
+ */
627
+ _setGeometryAttributes(gpuGeometry: GPUGeometry): void {
628
+ // Filter geometry attribute so that we don't issue warnings for unused attributes
629
+ const attributes = {...gpuGeometry.attributes};
630
+ for (const [attributeName] of Object.entries(attributes)) {
631
+ if (
632
+ !this.pipeline.shaderLayout.attributes.find(layout => layout.name === attributeName) &&
633
+ attributeName !== 'positions'
634
+ ) {
635
+ delete attributes[attributeName];
636
+ }
637
+ }
638
+
639
+ // TODO - delete previous geometry?
640
+ this.vertexCount = gpuGeometry.vertexCount;
641
+ this.setIndexBuffer(gpuGeometry.indices);
642
+ this.setAttributes(gpuGeometry.attributes, {ignoreUnknownAttributes: true});
643
+ this.setAttributes(attributes, {ignoreUnknownAttributes: this.props.ignoreUnknownAttributes});
644
+
645
+ this.setNeedsRedraw('geometry attributes');
646
+ }
647
+
648
+ /** Mark pipeline as needing update */
555
649
  _setPipelineNeedsUpdate(reason: string): void {
556
- this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
650
+ this._pipelineNeedsUpdate ||= reason;
651
+ this.setNeedsRedraw(reason);
557
652
  }
558
653
 
654
+ /** Update pipeline if needed */
559
655
  _updatePipeline(): RenderPipeline {
560
656
  if (this._pipelineNeedsUpdate) {
657
+ let prevShaderVs: Shader | null = null;
658
+ let prevShaderFs: Shader | null = null;
561
659
  if (this.pipeline) {
562
660
  log.log(
563
661
  1,
564
662
  `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`
565
663
  )();
664
+ prevShaderVs = this.pipeline.vs;
665
+ prevShaderFs = this.pipeline.fs;
566
666
  }
567
-
667
+
568
668
  this._pipelineNeedsUpdate = false;
569
669
 
570
- const vs = this.device.createShader({
670
+ const vs = this.shaderFactory.createShader({
571
671
  id: `${this.id}-vertex`,
572
672
  stage: 'vertex',
573
- source: this.vs
673
+ source: this.source || this.vs,
674
+ debug: this.props.debugShaders
574
675
  });
575
676
 
576
- const fs = this.fs
577
- ? this.device.createShader({
677
+ let fs: Shader | null = null;
678
+ if (this.source) {
679
+ fs = vs;
680
+ } else if (this.fs) {
681
+ fs = this.shaderFactory.createShader({
578
682
  id: `${this.id}-fragment`,
579
683
  stage: 'fragment',
580
- source: this.fs
581
- })
582
- : null;
684
+ source: this.source || this.fs,
685
+ debug: this.props.debugShaders
686
+ });
687
+ }
583
688
 
584
- this.pipeline = this.device.createRenderPipeline({
689
+ this.pipeline = this.pipelineFactory.createRenderPipeline({
585
690
  ...this.props,
586
691
  bufferLayout: this.bufferLayout,
587
692
  topology: this.topology,
@@ -594,6 +699,9 @@ export class Model {
594
699
  this.pipeline.shaderLayout,
595
700
  this.bufferLayout
596
701
  );
702
+
703
+ if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
704
+ if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
597
705
  }
598
706
  return this.pipeline;
599
707
  }
@@ -644,7 +752,7 @@ export class Model {
644
752
  const debugFramebuffers = log.get('framebuffer');
645
753
  this._drawCount++;
646
754
  // Update first 3 frames and then every 60 frames
647
- if (!debugFramebuffers || ((this._drawCount++ > 3) && (this._drawCount % 60))) {
755
+ if (!debugFramebuffers || (this._drawCount++ > 3 && this._drawCount % 60)) {
648
756
  return;
649
757
  }
650
758
  // TODO - display framebuffer output in debug window
@@ -710,11 +818,12 @@ function mergeBufferLayouts(layouts1: BufferLayout[], layouts2: BufferLayout[]):
710
818
  /** Create a shadertools platform info from the Device */
711
819
  export function getPlatformInfo(device: Device): PlatformInfo {
712
820
  return {
713
- type: device.info.type,
821
+ type: device.type,
714
822
  shaderLanguage: device.info.shadingLanguage,
715
823
  shaderLanguageVersion: device.info.shadingLanguageVersion as 100 | 300,
716
824
  gpu: device.info.gpu,
717
- features: device.features
825
+ // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API
826
+ features: device.features as unknown as Set<DeviceFeature>
718
827
  };
719
828
  }
720
829