@luma.gl/engine 9.0.0-alpha.9 → 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 (187) hide show
  1. package/LICENSE +3 -1
  2. package/dist/animation/key-frames.d.ts +1 -1
  3. package/dist/animation/key-frames.d.ts.map +1 -1
  4. package/dist/animation/key-frames.js +51 -72
  5. package/dist/animation/timeline.d.ts +8 -8
  6. package/dist/animation/timeline.d.ts.map +1 -1
  7. package/dist/animation/timeline.js +95 -131
  8. package/dist/animation-loop/animation-loop-template.d.ts +23 -0
  9. package/dist/animation-loop/animation-loop-template.d.ts.map +1 -0
  10. package/dist/animation-loop/animation-loop-template.js +21 -0
  11. package/dist/{lib → animation-loop}/animation-loop.d.ts +31 -23
  12. package/dist/animation-loop/animation-loop.d.ts.map +1 -0
  13. package/dist/animation-loop/animation-loop.js +442 -0
  14. package/dist/{lib → animation-loop}/animation-props.d.ts +4 -5
  15. package/dist/animation-loop/animation-props.d.ts.map +1 -0
  16. package/dist/animation-loop/animation-props.js +1 -0
  17. package/dist/animation-loop/make-animation-loop.d.ts +6 -0
  18. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -0
  19. package/dist/animation-loop/make-animation-loop.js +32 -0
  20. package/dist/computation.d.ts +95 -0
  21. package/dist/computation.d.ts.map +1 -0
  22. package/dist/computation.js +248 -0
  23. package/dist/debug/copy-texture-to-image.d.ts +26 -0
  24. package/dist/debug/copy-texture-to-image.d.ts.map +1 -0
  25. package/dist/debug/copy-texture-to-image.js +43 -0
  26. package/dist/debug/debug-framebuffer.d.ts +11 -0
  27. package/dist/debug/debug-framebuffer.d.ts.map +1 -0
  28. package/dist/debug/debug-framebuffer.js +46 -0
  29. package/dist/debug/debug-shader-layout.d.ts +9 -0
  30. package/dist/debug/debug-shader-layout.d.ts.map +1 -0
  31. package/dist/debug/debug-shader-layout.js +27 -0
  32. package/dist/debug/pixel-data-utils.d.ts +24 -0
  33. package/dist/debug/pixel-data-utils.d.ts.map +1 -0
  34. package/dist/debug/pixel-data-utils.js +39 -0
  35. package/dist/dist.dev.js +9592 -0
  36. package/dist/dist.min.js +102 -0
  37. package/dist/geometries/cone-geometry.d.ts +2 -2
  38. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  39. package/dist/geometries/cone-geometry.js +13 -18
  40. package/dist/geometries/cube-geometry.d.ts +2 -2
  41. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  42. package/dist/geometries/cube-geometry.js +192 -57
  43. package/dist/geometries/cylinder-geometry.d.ts +2 -2
  44. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  45. package/dist/geometries/cylinder-geometry.js +11 -15
  46. package/dist/geometries/ico-sphere-geometry.d.ts +2 -2
  47. package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -1
  48. package/dist/geometries/ico-sphere-geometry.js +143 -171
  49. package/dist/geometries/plane-geometry.d.ts +2 -2
  50. package/dist/geometries/plane-geometry.d.ts.map +1 -1
  51. package/dist/geometries/plane-geometry.js +95 -122
  52. package/dist/geometries/sphere-geometry.d.ts +2 -2
  53. package/dist/geometries/sphere-geometry.d.ts.map +1 -1
  54. package/dist/geometries/sphere-geometry.js +78 -101
  55. package/dist/geometries/truncated-cone-geometry.d.ts +2 -4
  56. package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -1
  57. package/dist/geometries/truncated-cone-geometry.js +100 -134
  58. package/dist/geometry/geometry-table.d.ts +2 -2
  59. package/dist/geometry/geometry-table.d.ts.map +1 -1
  60. package/dist/geometry/geometry-table.js +3 -1
  61. package/dist/geometry/geometry-utils.d.ts.map +1 -1
  62. package/dist/geometry/geometry-utils.js +35 -41
  63. package/dist/geometry/geometry.d.ts +43 -43
  64. package/dist/geometry/geometry.d.ts.map +1 -1
  65. package/dist/geometry/geometry.js +82 -139
  66. package/dist/geometry/gpu-geometry.d.ts +37 -0
  67. package/dist/geometry/gpu-geometry.d.ts.map +1 -0
  68. package/dist/geometry/gpu-geometry.js +90 -0
  69. package/dist/geometry/gpu-table.d.ts +1 -0
  70. package/dist/geometry/gpu-table.d.ts.map +1 -0
  71. package/dist/geometry/gpu-table.js +42 -0
  72. package/dist/index.cjs +3444 -0
  73. package/dist/index.cjs.map +7 -0
  74. package/dist/index.d.ts +43 -24
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +29 -15
  77. package/dist/lib/clip-space.d.ts +8 -0
  78. package/dist/lib/clip-space.d.ts.map +1 -1
  79. package/dist/lib/clip-space.js +43 -2
  80. package/dist/lib/pipeline-factory.d.ts +17 -51
  81. package/dist/lib/pipeline-factory.d.ts.map +1 -1
  82. package/dist/lib/pipeline-factory.js +84 -209
  83. package/dist/lib/shader-factory.d.ts +17 -0
  84. package/dist/lib/shader-factory.d.ts.map +1 -0
  85. package/dist/lib/shader-factory.js +46 -0
  86. package/dist/model/model.d.ts +219 -0
  87. package/dist/model/model.d.ts.map +1 -0
  88. package/dist/model/model.js +659 -0
  89. package/dist/scenegraph/group-node.d.ts +21 -0
  90. package/dist/scenegraph/group-node.d.ts.map +1 -0
  91. package/dist/scenegraph/group-node.js +84 -0
  92. package/dist/scenegraph/model-node.d.ts +18 -0
  93. package/dist/scenegraph/model-node.d.ts.map +1 -0
  94. package/dist/scenegraph/model-node.js +35 -0
  95. package/dist/scenegraph/scenegraph-node.d.ts +56 -0
  96. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -0
  97. package/dist/scenegraph/scenegraph-node.js +153 -0
  98. package/dist/shader-inputs.d.ts +63 -0
  99. package/dist/shader-inputs.d.ts.map +1 -0
  100. package/dist/shader-inputs.js +107 -0
  101. package/dist/transform/buffer-transform.d.ts +35 -0
  102. package/dist/transform/buffer-transform.d.ts.map +1 -0
  103. package/dist/transform/buffer-transform.js +70 -0
  104. package/dist/transform/texture-transform.d.ts +57 -0
  105. package/dist/transform/texture-transform.d.ts.map +1 -0
  106. package/dist/transform/texture-transform.js +117 -0
  107. package/dist.min.js +25 -0
  108. package/package.json +24 -14
  109. package/src/animation/timeline.ts +35 -34
  110. package/src/animation-loop/animation-loop-template.ts +25 -0
  111. package/src/{lib → animation-loop}/animation-loop.ts +114 -93
  112. package/src/{lib → animation-loop}/animation-props.ts +2 -2
  113. package/src/animation-loop/make-animation-loop.ts +53 -0
  114. package/src/computation.ts +346 -0
  115. package/src/debug/copy-texture-to-image.ts +70 -0
  116. package/src/debug/debug-framebuffer.ts +70 -0
  117. package/src/debug/debug-shader-layout.ts +38 -0
  118. package/src/debug/pixel-data-utils.ts +54 -0
  119. package/src/geometries/cone-geometry.ts +1 -1
  120. package/src/geometries/cube-geometry.ts +62 -56
  121. package/src/geometries/cylinder-geometry.ts +2 -2
  122. package/src/geometries/ico-sphere-geometry.ts +6 -5
  123. package/src/geometries/plane-geometry.ts +5 -4
  124. package/src/geometries/sphere-geometry.ts +4 -3
  125. package/src/geometries/truncated-cone-geometry.ts +6 -14
  126. package/src/geometry/geometry-table.ts +10 -7
  127. package/src/geometry/geometry-utils.ts +19 -3
  128. package/src/geometry/geometry.ts +68 -110
  129. package/src/geometry/gpu-geometry.ts +132 -0
  130. package/src/geometry/gpu-table.ts +41 -0
  131. package/src/index.ts +37 -10
  132. package/src/lib/clip-space.ts +32 -34
  133. package/src/lib/pipeline-factory.ts +83 -193
  134. package/src/lib/shader-factory.ts +57 -0
  135. package/src/model/model.ts +835 -0
  136. package/src/scenegraph/group-node.ts +107 -0
  137. package/src/scenegraph/model-node.ts +50 -0
  138. package/src/scenegraph/scenegraph-node.ts +204 -0
  139. package/src/shader-inputs.ts +157 -0
  140. package/src/transform/buffer-transform.ts +102 -0
  141. package/src/transform/texture-transform.ts +168 -0
  142. package/dist/animation/key-frames.js.map +0 -1
  143. package/dist/animation/timeline.js.map +0 -1
  144. package/dist/bundle.d.ts +0 -2
  145. package/dist/bundle.d.ts.map +0 -1
  146. package/dist/bundle.js +0 -5
  147. package/dist/bundle.js.map +0 -1
  148. package/dist/geometries/cone-geometry.js.map +0 -1
  149. package/dist/geometries/cube-geometry.js.map +0 -1
  150. package/dist/geometries/cylinder-geometry.js.map +0 -1
  151. package/dist/geometries/ico-sphere-geometry.js.map +0 -1
  152. package/dist/geometries/plane-geometry.js.map +0 -1
  153. package/dist/geometries/sphere-geometry.js.map +0 -1
  154. package/dist/geometries/truncated-cone-geometry.js.map +0 -1
  155. package/dist/geometry/geometry-table.js.map +0 -1
  156. package/dist/geometry/geometry-utils.js.map +0 -1
  157. package/dist/geometry/geometry.js.map +0 -1
  158. package/dist/geometry/primitive-utils.d.ts +0 -1
  159. package/dist/geometry/primitive-utils.d.ts.map +0 -1
  160. package/dist/geometry/primitive-utils.js +0 -2
  161. package/dist/geometry/primitive-utils.js.map +0 -1
  162. package/dist/index.js.map +0 -1
  163. package/dist/lib/animation-loop.d.ts.map +0 -1
  164. package/dist/lib/animation-loop.js +0 -480
  165. package/dist/lib/animation-loop.js.map +0 -1
  166. package/dist/lib/animation-props.d.ts.map +0 -1
  167. package/dist/lib/animation-props.js +0 -2
  168. package/dist/lib/animation-props.js.map +0 -1
  169. package/dist/lib/clip-space.js.map +0 -1
  170. package/dist/lib/model-utils.d.ts +0 -5
  171. package/dist/lib/model-utils.d.ts.map +0 -1
  172. package/dist/lib/model-utils.js +0 -45
  173. package/dist/lib/model-utils.js.map +0 -1
  174. package/dist/lib/model.d.ts +0 -41
  175. package/dist/lib/model.d.ts.map +0 -1
  176. package/dist/lib/model.js +0 -182
  177. package/dist/lib/model.js.map +0 -1
  178. package/dist/lib/pipeline-factory.js.map +0 -1
  179. package/dist/lib/render-loop.d.ts +0 -14
  180. package/dist/lib/render-loop.d.ts.map +0 -1
  181. package/dist/lib/render-loop.js +0 -49
  182. package/dist/lib/render-loop.js.map +0 -1
  183. package/src/bundle.ts +0 -4
  184. package/src/geometry/primitive-utils.ts +0 -30
  185. package/src/lib/model-utils.ts +0 -124
  186. package/src/lib/model.ts +0 -183
  187. package/src/lib/render-loop.ts +0 -58
@@ -0,0 +1,835 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ // A lot of imports, but then Model is where it all comes together...
6
+ import type {TypedArray, RenderPipelineProps, RenderPipelineParameters} from '@luma.gl/core';
7
+ import type {BufferLayout, Shader, VertexArray, TransformFeedback} from '@luma.gl/core';
8
+ import type {AttributeInfo, Binding, UniformValue, PrimitiveTopology} 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
+
14
+ import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
15
+ import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
16
+
17
+ import type {Geometry} from '../geometry/geometry';
18
+ import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
19
+ import {ShaderInputs} from '../shader-inputs';
20
+ import {PipelineFactory} from '../lib/pipeline-factory';
21
+ import {ShaderFactory} from '../lib/shader-factory';
22
+ import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
23
+ import {debugFramebuffer} from '../debug/debug-framebuffer';
24
+
25
+ const LOG_DRAW_PRIORITY = 2;
26
+ const LOG_DRAW_TIMEOUT = 10000;
27
+
28
+ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
29
+ source?: string;
30
+ vs: string | null;
31
+ fs: string | null;
32
+
33
+ /** shadertool shader modules (added to shader code) */
34
+ modules?: ShaderModule[];
35
+ /** Shadertool module defines (configures shader code)*/
36
+ defines?: Record<string, string | number | boolean>;
37
+ // TODO - injections, hooks etc?
38
+
39
+ /** Shader inputs, used to generated uniform buffers and bindings */
40
+ shaderInputs?: ShaderInputs;
41
+
42
+ /** Parameters that are built into the pipeline */
43
+ parameters?: RenderPipelineParameters;
44
+
45
+ /** Geometry */
46
+ geometry?: GPUGeometry | Geometry | null;
47
+
48
+ /** Vertex count */
49
+ vertexCount?: number;
50
+ /** instance count */
51
+ instanceCount?: number;
52
+
53
+ indexBuffer?: Buffer | null;
54
+ /** @note this is really a map of buffers, not a map of attributes */
55
+ attributes?: Record<string, Buffer>;
56
+ /** */
57
+ constantAttributes?: Record<string, TypedArray>;
58
+ /** Some applications intentionally supply unused attributes */
59
+ ignoreUnknownAttributes?: boolean;
60
+
61
+ /** @internal For use with {@link TransformFeedback}, WebGL only. */
62
+ varyings?: string[];
63
+
64
+ transformFeedback?: TransformFeedback;
65
+
66
+ /** Mapped uniforms for shadertool modules */
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;
78
+ };
79
+
80
+ /**
81
+ * v9 Model API
82
+ * A model
83
+ * - automatically reuses pipelines (programs) when possible
84
+ * - automatically rebuilds pipelines if necessary to accommodate changed settings
85
+ * shadertools integration
86
+ * - accepts modules and performs shader transpilation
87
+ */
88
+ export class Model {
89
+ static defaultProps: Required<ModelProps> = {
90
+ ...RenderPipeline.defaultProps,
91
+ source: null,
92
+ vs: null,
93
+ fs: null,
94
+ id: 'unnamed',
95
+ handle: undefined,
96
+ userData: {},
97
+ defines: {},
98
+ modules: [],
99
+ moduleSettings: undefined!,
100
+ geometry: null,
101
+ indexBuffer: null,
102
+ attributes: {},
103
+ constantAttributes: {},
104
+ varyings: [],
105
+
106
+ shaderInputs: undefined!,
107
+ pipelineFactory: undefined!,
108
+ shaderFactory: undefined!,
109
+ transformFeedback: undefined,
110
+ shaderAssembler: ShaderAssembler.getDefaultShaderAssembler(),
111
+
112
+ debugShaders: undefined,
113
+ ignoreUnknownAttributes: undefined
114
+ };
115
+
116
+ readonly device: Device;
117
+ readonly id: string;
118
+ readonly source: string;
119
+ readonly vs: string;
120
+ readonly fs: string;
121
+ readonly pipelineFactory: PipelineFactory;
122
+ readonly shaderFactory: ShaderFactory;
123
+ userData: {[key: string]: any} = {};
124
+
125
+ // Fixed properties (change can trigger pipeline rebuild)
126
+
127
+ /** The render pipeline GPU parameters, depth testing etc */
128
+ parameters: RenderPipelineParameters;
129
+
130
+ /** The primitive topology */
131
+ topology: PrimitiveTopology;
132
+ /** Buffer layout */
133
+ bufferLayout: BufferLayout[];
134
+
135
+ // Dynamic properties
136
+
137
+ /** Vertex count */
138
+ vertexCount: number;
139
+ /** instance count */
140
+ instanceCount: number = 0;
141
+
142
+ /** Index buffer */
143
+ indexBuffer: Buffer | null = null;
144
+ /** Buffer-valued attributes */
145
+ bufferAttributes: Record<string, Buffer> = {};
146
+ /** Constant-valued attributes */
147
+ constantAttributes: Record<string, TypedArray> = {};
148
+ /** Bindings (textures, samplers, uniform buffers) */
149
+ bindings: Record<string, Binding> = {};
150
+ /** Sets uniforms @deprecated Use uniform buffers and setBindings() for portability*/
151
+ uniforms: Record<string, UniformValue> = {};
152
+
153
+ /**
154
+ * VertexArray
155
+ * @note not implemented: if bufferLayout is updated, vertex array has to be rebuilt!
156
+ * @todo - allow application to define multiple vertex arrays?
157
+ * */
158
+ vertexArray: VertexArray;
159
+
160
+ /** TransformFeedback, WebGL 2 only. */
161
+ transformFeedback: TransformFeedback | null = null;
162
+
163
+ /** The underlying GPU "program". @note May be recreated if parameters change */
164
+ pipeline: RenderPipeline;
165
+
166
+ /** ShaderInputs instance */
167
+ shaderInputs: ShaderInputs;
168
+
169
+ _uniformStore: UniformStore;
170
+
171
+ _attributeInfos: Record<string, AttributeInfo> = {};
172
+ _gpuGeometry: GPUGeometry | null = null;
173
+ private _getModuleUniforms: (props?: Record<string, Record<string, any>>) => Record<string, any>;
174
+ private props: Required<ModelProps>;
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
+
183
+ constructor(device: Device, props: ModelProps) {
184
+ this.props = {...Model.defaultProps, ...props};
185
+ props = this.props;
186
+ this.id = props.id || uid('model');
187
+ this.device = device;
188
+
189
+ Object.assign(this.userData, props.userData);
190
+
191
+ // Setup shader module inputs
192
+ const moduleMap = Object.fromEntries(
193
+ this.props.modules?.map(module => [module.name, module]) || []
194
+ );
195
+ this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
196
+
197
+ // Setup shader assembler
198
+ const platformInfo = getPlatformInfo(device);
199
+
200
+ // Extract modules from shader inputs if not supplied
201
+ const modules =
202
+ (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || [];
203
+
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
+ }
231
+
232
+ this.vertexCount = this.props.vertexCount;
233
+ this.instanceCount = this.props.instanceCount;
234
+
235
+ this.topology = this.props.topology;
236
+ this.bufferLayout = this.props.bufferLayout;
237
+ this.parameters = this.props.parameters;
238
+
239
+ // Geometry, if provided, sets topology and vertex cound
240
+ if (props.geometry) {
241
+ this.setGeometry(props.geometry);
242
+ }
243
+
244
+ this.pipelineFactory =
245
+ props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
246
+ this.shaderFactory = props.shaderFactory || ShaderFactory.getDefaultShaderFactory(this.device);
247
+
248
+ // Create the pipeline
249
+ // @note order is important
250
+ this.pipeline = this._updatePipeline();
251
+
252
+ this.vertexArray = device.createVertexArray({
253
+ renderPipeline: this.pipeline
254
+ });
255
+
256
+ // Now we can apply geometry attributes
257
+ if (this._gpuGeometry) {
258
+ this._setGeometryAttributes(this._gpuGeometry);
259
+ }
260
+
261
+ // Apply any dynamic settings that will not trigger pipeline change
262
+ if (props.vertexCount) {
263
+ this.setVertexCount(props.vertexCount);
264
+ }
265
+ if (props.instanceCount) {
266
+ this.setInstanceCount(props.instanceCount);
267
+ }
268
+ // @ts-expect-error
269
+ if (props.indices) {
270
+ throw new Error('Model.props.indices removed. Use props.indexBuffer');
271
+ }
272
+ if (props.indexBuffer) {
273
+ this.setIndexBuffer(props.indexBuffer);
274
+ }
275
+ if (props.attributes) {
276
+ this.setAttributes(props.attributes, {
277
+ ignoreUnknownAttributes: props.ignoreUnknownAttributes
278
+ });
279
+ }
280
+ if (props.constantAttributes) {
281
+ this.setConstantAttributes(props.constantAttributes);
282
+ }
283
+ if (props.bindings) {
284
+ this.setBindings(props.bindings);
285
+ }
286
+ if (props.uniforms) {
287
+ this.setUniforms(props.uniforms);
288
+ }
289
+ if (props.moduleSettings) {
290
+ log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')();
291
+ this.updateModuleSettings(props.moduleSettings);
292
+ }
293
+ if (props.transformFeedback) {
294
+ this.transformFeedback = props.transformFeedback;
295
+ }
296
+
297
+ // Catch any access to non-standard props
298
+ Object.seal(this);
299
+ }
300
+
301
+ destroy(): void {
302
+ if (this._destroyed) return;
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
+ }
308
+ this._uniformStore.destroy();
309
+ // TODO - mark resource as managed and destroyIfManaged() ?
310
+ this._gpuGeometry?.destroy();
311
+ this._destroyed = true;
312
+ }
313
+
314
+ // Draw call
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
+
332
+ predraw() {
333
+ // Update uniform buffers if needed
334
+ this.updateShaderInputs();
335
+ // Check if the pipeline is invalidated
336
+ this.pipeline = this._updatePipeline();
337
+ }
338
+
339
+ draw(renderPass: RenderPass): boolean {
340
+ this.predraw();
341
+
342
+ let drawSuccess: boolean;
343
+ try {
344
+ this._logDrawCallStart();
345
+
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.
349
+ this.pipeline = this._updatePipeline();
350
+
351
+ // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
352
+ // Any caching needs to be done inside the pipeline functions
353
+ this.pipeline.setBindings(this.bindings);
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;
362
+
363
+ drawSuccess = this.pipeline.draw({
364
+ renderPass,
365
+ vertexArray: this.vertexArray,
366
+ vertexCount: this.vertexCount,
367
+ instanceCount: this.instanceCount,
368
+ indexCount,
369
+ transformFeedback: this.transformFeedback
370
+ });
371
+ } finally {
372
+ this._logDrawCallEnd();
373
+ }
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;
384
+ }
385
+
386
+ // Update fixed fields (can trigger pipeline rebuild)
387
+
388
+ /**
389
+ * Updates the optional geometry
390
+ * Geometry, set topology and bufferLayout
391
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
392
+ */
393
+ setGeometry(geometry: GPUGeometry | Geometry | null): void {
394
+ this._gpuGeometry?.destroy();
395
+ const gpuGeometry = geometry && makeGPUGeometry(this.device, geometry);
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);
401
+ }
402
+ }
403
+ this._gpuGeometry = gpuGeometry;
404
+ }
405
+
406
+ /**
407
+ * Updates the primitive topology ('triangle-list', 'triangle-strip' etc).
408
+ * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
409
+ */
410
+ setTopology(topology: PrimitiveTopology): void {
411
+ if (topology !== this.topology) {
412
+ this.topology = topology;
413
+ this._setPipelineNeedsUpdate('topology');
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Updates the buffer layout.
419
+ * @note Triggers a pipeline rebuild / pipeline cache fetch
420
+ */
421
+ setBufferLayout(bufferLayout: BufferLayout[]): void {
422
+ this.bufferLayout = this._gpuGeometry
423
+ ? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
424
+ : bufferLayout;
425
+ this._setPipelineNeedsUpdate('bufferLayout');
426
+
427
+ // Recreate the pipeline
428
+ this.pipeline = this._updatePipeline();
429
+
430
+ // vertex array needs to be updated if we update buffer layout,
431
+ // but not if we update parameters
432
+ this.vertexArray = this.device.createVertexArray({
433
+ renderPipeline: this.pipeline
434
+ });
435
+
436
+ // Reapply geometry attributes to the new vertex array
437
+ if (this._gpuGeometry) {
438
+ this._setGeometryAttributes(this._gpuGeometry);
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Set GPU parameters.
444
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch.
445
+ * @param parameters
446
+ */
447
+ setParameters(parameters: RenderPipelineParameters) {
448
+ if (!deepEqual(parameters, this.parameters, 2)) {
449
+ this.parameters = parameters;
450
+ this._setPipelineNeedsUpdate('parameters');
451
+ }
452
+ }
453
+
454
+ // Update dynamic fields
455
+
456
+ /**
457
+ * Updates the vertex count (used in draw calls)
458
+ * @note Any attributes with stepMode=vertex need to be at least this big
459
+ */
460
+ setVertexCount(vertexCount: number): void {
461
+ this.vertexCount = vertexCount;
462
+ this.setNeedsRedraw('vertexCount');
463
+ }
464
+
465
+ /**
466
+ * Updates the instance count (used in draw calls)
467
+ * @note Any attributes with stepMode=instance need to be at least this big
468
+ */
469
+ setInstanceCount(instanceCount: number): void {
470
+ this.instanceCount = instanceCount;
471
+ this.setNeedsRedraw('instanceCount');
472
+ }
473
+
474
+ setShaderInputs(shaderInputs: ShaderInputs): void {
475
+ this.shaderInputs = shaderInputs;
476
+ this._uniformStore = new UniformStore(this.shaderInputs.modules);
477
+ // Create uniform buffer bindings for all modules
478
+ for (const moduleName of Object.keys(this.shaderInputs.modules)) {
479
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
480
+ this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
481
+ }
482
+ this.setNeedsRedraw('shaderInputs');
483
+ }
484
+
485
+ updateShaderInputs(): void {
486
+ this._uniformStore.setUniforms(this.shaderInputs.getUniformValues());
487
+ // TODO - this is already tracked through buffer/texture update times?
488
+ this.setNeedsRedraw('shaderInputs');
489
+ }
490
+
491
+ /**
492
+ * Sets bindings (textures, samplers, uniform buffers)
493
+ */
494
+ setBindings(bindings: Record<string, Binding>): void {
495
+ Object.assign(this.bindings, bindings);
496
+ this.setNeedsRedraw('bindings');
497
+ }
498
+
499
+ /**
500
+ * Updates optional transform feedback. WebGL only.
501
+ */
502
+ setTransformFeedback(transformFeedback: TransformFeedback | null): void {
503
+ this.transformFeedback = transformFeedback;
504
+ this.setNeedsRedraw('transformFeedback');
505
+ }
506
+
507
+ /**
508
+ * Sets the index buffer
509
+ * @todo - how to unset it if we change geometry?
510
+ */
511
+ setIndexBuffer(indexBuffer: Buffer | null): void {
512
+ this.vertexArray.setIndexBuffer(indexBuffer);
513
+ this.setNeedsRedraw('indexBuffer');
514
+ }
515
+
516
+ /**
517
+ * Sets attributes (buffers)
518
+ * @note Overrides any attributes previously set with the same name
519
+ */
520
+ setAttributes(
521
+ buffers: Record<string, Buffer>,
522
+ options?: {ignoreUnknownAttributes?: boolean}
523
+ ): void {
524
+ if (buffers.indices) {
525
+ log.warn(
526
+ `Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
527
+ )();
528
+ }
529
+ for (const [bufferName, buffer] of Object.entries(buffers)) {
530
+ const bufferLayout = this.bufferLayout.find(layout =>
531
+ getAttributeNames(layout).includes(bufferName)
532
+ );
533
+ if (!bufferLayout) {
534
+ log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
535
+ continue; // eslint-disable-line no-continue
536
+ }
537
+
538
+ // For an interleaved attribute we may need to set multiple attributes
539
+ const attributeNames = getAttributeNames(bufferLayout);
540
+ let set = false;
541
+ for (const attributeName of attributeNames) {
542
+ const attributeInfo = this._attributeInfos[attributeName];
543
+ if (attributeInfo) {
544
+ this.vertexArray.setBuffer(attributeInfo.location, buffer);
545
+ set = true;
546
+ }
547
+ }
548
+ if (!set && (options?.ignoreUnknownAttributes || this.props.ignoreUnknownAttributes)) {
549
+ log.warn(
550
+ `Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`
551
+ )();
552
+ }
553
+ }
554
+ this.setNeedsRedraw('attributes');
555
+ }
556
+
557
+ /**
558
+ * Sets constant attributes
559
+ * @note Overrides any attributes previously set with the same name
560
+ * Constant attributes are only supported in WebGL, not in WebGPU
561
+ * Any attribute that is disabled in the current vertex array object
562
+ * is read from the context's global constant value for that attribute location.
563
+ * @param constantAttributes
564
+ */
565
+ setConstantAttributes(attributes: Record<string, TypedArray>): void {
566
+ for (const [attributeName, value] of Object.entries(attributes)) {
567
+ const attributeInfo = this._attributeInfos[attributeName];
568
+ if (attributeInfo) {
569
+ this.vertexArray.setConstantWebGL(attributeInfo.location, value);
570
+ } else {
571
+ log.warn(
572
+ `Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`
573
+ )();
574
+ }
575
+ }
576
+ this.setNeedsRedraw('constants');
577
+ }
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 */
649
+ _setPipelineNeedsUpdate(reason: string): void {
650
+ this._pipelineNeedsUpdate ||= reason;
651
+ this.setNeedsRedraw(reason);
652
+ }
653
+
654
+ /** Update pipeline if needed */
655
+ _updatePipeline(): RenderPipeline {
656
+ if (this._pipelineNeedsUpdate) {
657
+ let prevShaderVs: Shader | null = null;
658
+ let prevShaderFs: Shader | null = null;
659
+ if (this.pipeline) {
660
+ log.log(
661
+ 1,
662
+ `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`
663
+ )();
664
+ prevShaderVs = this.pipeline.vs;
665
+ prevShaderFs = this.pipeline.fs;
666
+ }
667
+
668
+ this._pipelineNeedsUpdate = false;
669
+
670
+ const vs = this.shaderFactory.createShader({
671
+ id: `${this.id}-vertex`,
672
+ stage: 'vertex',
673
+ source: this.source || this.vs,
674
+ debug: this.props.debugShaders
675
+ });
676
+
677
+ let fs: Shader | null = null;
678
+ if (this.source) {
679
+ fs = vs;
680
+ } else if (this.fs) {
681
+ fs = this.shaderFactory.createShader({
682
+ id: `${this.id}-fragment`,
683
+ stage: 'fragment',
684
+ source: this.source || this.fs,
685
+ debug: this.props.debugShaders
686
+ });
687
+ }
688
+
689
+ this.pipeline = this.pipelineFactory.createRenderPipeline({
690
+ ...this.props,
691
+ bufferLayout: this.bufferLayout,
692
+ topology: this.topology,
693
+ parameters: this.parameters,
694
+ vs,
695
+ fs
696
+ });
697
+
698
+ this._attributeInfos = getAttributeInfosFromLayouts(
699
+ this.pipeline.shaderLayout,
700
+ this.bufferLayout
701
+ );
702
+
703
+ if (prevShaderVs) this.shaderFactory.release(prevShaderVs);
704
+ if (prevShaderFs) this.shaderFactory.release(prevShaderFs);
705
+ }
706
+ return this.pipeline;
707
+ }
708
+
709
+ /** Throttle draw call logging */
710
+ _lastLogTime = 0;
711
+ _logOpen = false;
712
+
713
+ _logDrawCallStart(): void {
714
+ // IF level is 4 or higher, log every frame.
715
+ const logDrawTimeout = log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
716
+ if (log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
717
+ return;
718
+ }
719
+
720
+ this._lastLogTime = Date.now();
721
+ this._logOpen = true;
722
+
723
+ log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, {collapsed: log.level <= 2})();
724
+ }
725
+
726
+ _logDrawCallEnd(): void {
727
+ if (this._logOpen) {
728
+ const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id);
729
+
730
+ // log.table(logLevel, attributeTable)();
731
+ // log.table(logLevel, uniformTable)();
732
+ log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
733
+
734
+ const uniformTable = this.shaderInputs.getDebugTable();
735
+ // Add any global uniforms
736
+ for (const [name, value] of Object.entries(this.uniforms)) {
737
+ uniformTable[name] = {value};
738
+ }
739
+ log.table(LOG_DRAW_PRIORITY, uniformTable)();
740
+
741
+ const attributeTable = this._getAttributeDebugTable();
742
+ log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
743
+ log.table(LOG_DRAW_PRIORITY, attributeTable)();
744
+
745
+ log.groupEnd(LOG_DRAW_PRIORITY)();
746
+ this._logOpen = false;
747
+ }
748
+ }
749
+
750
+ protected _drawCount = 0;
751
+ _logFramebuffer(renderPass: RenderPass): void {
752
+ const debugFramebuffers = log.get('framebuffer');
753
+ this._drawCount++;
754
+ // Update first 3 frames and then every 60 frames
755
+ if (!debugFramebuffers || (this._drawCount++ > 3 && this._drawCount % 60)) {
756
+ return;
757
+ }
758
+ // TODO - display framebuffer output in debug window
759
+ const framebuffer = renderPass.props.framebuffer;
760
+ if (framebuffer) {
761
+ debugFramebuffer(framebuffer, {id: framebuffer.id, minimap: true});
762
+ // log.image({logLevel: LOG_DRAW_PRIORITY, message: `${framebuffer.id} %c sup?`, image})();
763
+ }
764
+ }
765
+
766
+ _getAttributeDebugTable(): Record<string, Record<string, unknown>> {
767
+ const table: Record<string, Record<string, unknown>> = {};
768
+ for (const [name, attributeInfo] of Object.entries(this._attributeInfos)) {
769
+ table[attributeInfo.location] = {
770
+ name,
771
+ type: attributeInfo.shaderType,
772
+ values: this._getBufferOrConstantValues(
773
+ this.vertexArray.attributes[attributeInfo.location],
774
+ attributeInfo.bufferDataType
775
+ )
776
+ };
777
+ }
778
+ if (this.vertexArray.indexBuffer) {
779
+ const {indexBuffer} = this.vertexArray;
780
+ const values =
781
+ indexBuffer.indexType === 'uint32'
782
+ ? new Uint32Array(indexBuffer.debugData)
783
+ : new Uint16Array(indexBuffer.debugData);
784
+ table.indices = {
785
+ name: 'indices',
786
+ type: indexBuffer.indexType,
787
+ values: values.toString()
788
+ };
789
+ }
790
+ return table;
791
+ }
792
+
793
+ // TODO - fix typing of luma data types
794
+ _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
795
+ const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
796
+ const typedArray =
797
+ attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
798
+ return typedArray.toString();
799
+ }
800
+ }
801
+
802
+ // HELPERS
803
+
804
+ /** TODO - move to core, document add tests */
805
+ function mergeBufferLayouts(layouts1: BufferLayout[], layouts2: BufferLayout[]): BufferLayout[] {
806
+ const layouts = [...layouts1];
807
+ for (const attribute of layouts2) {
808
+ const index = layouts.findIndex(attribute2 => attribute2.name === attribute.name);
809
+ if (index < 0) {
810
+ layouts.push(attribute);
811
+ } else {
812
+ layouts[index] = attribute;
813
+ }
814
+ }
815
+ return layouts;
816
+ }
817
+
818
+ /** Create a shadertools platform info from the Device */
819
+ export function getPlatformInfo(device: Device): PlatformInfo {
820
+ return {
821
+ type: device.type,
822
+ shaderLanguage: device.info.shadingLanguage,
823
+ shaderLanguageVersion: device.info.shadingLanguageVersion as 100 | 300,
824
+ gpu: device.info.gpu,
825
+ // HACK - we pretend that the DeviceFeatures is a Set, it has a similar API
826
+ features: device.features as unknown as Set<DeviceFeature>
827
+ };
828
+ }
829
+
830
+ /** Get attribute names from a BufferLayout */
831
+ function getAttributeNames(bufferLayout: BufferLayout): string[] {
832
+ return bufferLayout.attributes
833
+ ? bufferLayout.attributes?.map(layout => layout.attribute)
834
+ : [bufferLayout.name];
835
+ }