@luma.gl/engine 9.0.0-alpha.9 → 9.0.0-beta.1

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 (191) 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 +6 -27
  5. package/dist/animation/key-frames.js.map +1 -1
  6. package/dist/animation/timeline.d.ts +8 -8
  7. package/dist/animation/timeline.d.ts.map +1 -1
  8. package/dist/animation/timeline.js +18 -49
  9. package/dist/animation/timeline.js.map +1 -1
  10. package/dist/animation-loop/animation-loop-template.d.ts +23 -0
  11. package/dist/animation-loop/animation-loop-template.d.ts.map +1 -0
  12. package/dist/animation-loop/animation-loop-template.js +7 -0
  13. package/dist/animation-loop/animation-loop-template.js.map +1 -0
  14. package/dist/{lib → animation-loop}/animation-loop.d.ts +30 -22
  15. package/dist/animation-loop/animation-loop.d.ts.map +1 -0
  16. package/dist/{lib → animation-loop}/animation-loop.js +77 -192
  17. package/dist/animation-loop/animation-loop.js.map +1 -0
  18. package/dist/{lib → animation-loop}/animation-props.d.ts +2 -3
  19. package/dist/animation-loop/animation-props.d.ts.map +1 -0
  20. package/dist/animation-loop/animation-props.js.map +1 -0
  21. package/dist/animation-loop/make-animation-loop.d.ts +6 -0
  22. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -0
  23. package/dist/animation-loop/make-animation-loop.js +28 -0
  24. package/dist/animation-loop/make-animation-loop.js.map +1 -0
  25. package/dist/debug/copy-texture-to-image.d.ts +26 -0
  26. package/dist/debug/copy-texture-to-image.d.ts.map +1 -0
  27. package/dist/debug/copy-texture-to-image.js +46 -0
  28. package/dist/debug/copy-texture-to-image.js.map +1 -0
  29. package/dist/debug/debug-framebuffer.d.ts +11 -0
  30. package/dist/debug/debug-framebuffer.d.ts.map +1 -0
  31. package/dist/debug/debug-framebuffer.js +43 -0
  32. package/dist/debug/debug-framebuffer.js.map +1 -0
  33. package/dist/debug/debug-shader-layout.d.ts +9 -0
  34. package/dist/debug/debug-shader-layout.d.ts.map +1 -0
  35. package/dist/debug/debug-shader-layout.js +28 -0
  36. package/dist/debug/debug-shader-layout.js.map +1 -0
  37. package/dist/debug/pixel-data-utils.d.ts +24 -0
  38. package/dist/debug/pixel-data-utils.d.ts.map +1 -0
  39. package/dist/debug/pixel-data-utils.js +41 -0
  40. package/dist/debug/pixel-data-utils.js.map +1 -0
  41. package/dist/dist.dev.js +10081 -0
  42. package/dist/geometries/cone-geometry.d.ts +1 -1
  43. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  44. package/dist/geometries/cone-geometry.js +6 -5
  45. package/dist/geometries/cone-geometry.js.map +1 -1
  46. package/dist/geometries/cube-geometry.d.ts +2 -2
  47. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  48. package/dist/geometries/cube-geometry.js +16 -10
  49. package/dist/geometries/cube-geometry.js.map +1 -1
  50. package/dist/geometries/cylinder-geometry.d.ts +1 -1
  51. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  52. package/dist/geometries/cylinder-geometry.js +6 -5
  53. package/dist/geometries/cylinder-geometry.js.map +1 -1
  54. package/dist/geometries/ico-sphere-geometry.d.ts +2 -2
  55. package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -1
  56. package/dist/geometries/ico-sphere-geometry.js +10 -19
  57. package/dist/geometries/ico-sphere-geometry.js.map +1 -1
  58. package/dist/geometries/plane-geometry.d.ts +2 -2
  59. package/dist/geometries/plane-geometry.d.ts.map +1 -1
  60. package/dist/geometries/plane-geometry.js +14 -23
  61. package/dist/geometries/plane-geometry.js.map +1 -1
  62. package/dist/geometries/sphere-geometry.d.ts +2 -2
  63. package/dist/geometries/sphere-geometry.d.ts.map +1 -1
  64. package/dist/geometries/sphere-geometry.js +9 -13
  65. package/dist/geometries/sphere-geometry.js.map +1 -1
  66. package/dist/geometries/truncated-cone-geometry.d.ts +2 -4
  67. package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -1
  68. package/dist/geometries/truncated-cone-geometry.js +9 -25
  69. package/dist/geometries/truncated-cone-geometry.js.map +1 -1
  70. package/dist/geometry/geometry-table.d.ts +2 -2
  71. package/dist/geometry/geometry-table.d.ts.map +1 -1
  72. package/dist/geometry/geometry-table.js.map +1 -1
  73. package/dist/geometry/geometry-utils.d.ts.map +1 -1
  74. package/dist/geometry/geometry-utils.js +0 -9
  75. package/dist/geometry/geometry-utils.js.map +1 -1
  76. package/dist/geometry/geometry.d.ts +43 -43
  77. package/dist/geometry/geometry.d.ts.map +1 -1
  78. package/dist/geometry/geometry.js +20 -86
  79. package/dist/geometry/geometry.js.map +1 -1
  80. package/dist/geometry/gpu-geometry.d.ts +37 -0
  81. package/dist/geometry/gpu-geometry.d.ts.map +1 -0
  82. package/dist/geometry/gpu-geometry.js +110 -0
  83. package/dist/geometry/gpu-geometry.js.map +1 -0
  84. package/dist/geometry/gpu-table.d.ts +1 -0
  85. package/dist/geometry/gpu-table.d.ts.map +1 -0
  86. package/dist/geometry/gpu-table.js +2 -0
  87. package/dist/geometry/gpu-table.js.map +1 -0
  88. package/dist/index.cjs +3128 -0
  89. package/dist/index.d.ts +24 -8
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +23 -14
  92. package/dist/index.js.map +1 -1
  93. package/dist/lib/clip-space.d.ts +8 -0
  94. package/dist/lib/clip-space.d.ts.map +1 -1
  95. package/dist/lib/clip-space.js +47 -0
  96. package/dist/lib/clip-space.js.map +1 -1
  97. package/dist/lib/pipeline-factory.d.ts +12 -45
  98. package/dist/lib/pipeline-factory.d.ts.map +1 -1
  99. package/dist/lib/pipeline-factory.js +42 -168
  100. package/dist/lib/pipeline-factory.js.map +1 -1
  101. package/dist/model/model.d.ts +206 -0
  102. package/dist/model/model.d.ts.map +1 -0
  103. package/dist/model/model.js +435 -0
  104. package/dist/model/model.js.map +1 -0
  105. package/dist/scenegraph/group-node.d.ts +21 -0
  106. package/dist/scenegraph/group-node.d.ts.map +1 -0
  107. package/dist/scenegraph/group-node.js +94 -0
  108. package/dist/scenegraph/group-node.js.map +1 -0
  109. package/dist/scenegraph/model-node.d.ts +18 -0
  110. package/dist/scenegraph/model-node.d.ts.map +1 -0
  111. package/dist/scenegraph/model-node.js +28 -0
  112. package/dist/scenegraph/model-node.js.map +1 -0
  113. package/dist/scenegraph/scenegraph-node.d.ts +56 -0
  114. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -0
  115. package/dist/scenegraph/scenegraph-node.js +141 -0
  116. package/dist/scenegraph/scenegraph-node.js.map +1 -0
  117. package/dist/shader-inputs.d.ts +63 -0
  118. package/dist/shader-inputs.d.ts.map +1 -0
  119. package/dist/shader-inputs.js +66 -0
  120. package/dist/shader-inputs.js.map +1 -0
  121. package/dist/transform/buffer-transform.d.ts +35 -0
  122. package/dist/transform/buffer-transform.d.ts.map +1 -0
  123. package/dist/transform/buffer-transform.js +62 -0
  124. package/dist/transform/buffer-transform.js.map +1 -0
  125. package/dist/transform/texture-transform.d.ts +57 -0
  126. package/dist/transform/texture-transform.d.ts.map +1 -0
  127. package/dist/transform/texture-transform.js +122 -0
  128. package/dist/transform/texture-transform.js.map +1 -0
  129. package/dist.min.js +293 -0
  130. package/package.json +21 -12
  131. package/src/animation/timeline.ts +15 -14
  132. package/src/animation-loop/animation-loop-template.ts +23 -0
  133. package/src/{lib → animation-loop}/animation-loop.ts +99 -88
  134. package/src/{lib → animation-loop}/animation-props.ts +1 -1
  135. package/src/animation-loop/make-animation-loop.ts +44 -0
  136. package/src/debug/copy-texture-to-image.ts +72 -0
  137. package/src/debug/debug-framebuffer.ts +57 -0
  138. package/src/debug/debug-shader-layout.ts +38 -0
  139. package/src/debug/pixel-data-utils.ts +57 -0
  140. package/src/geometries/cone-geometry.ts +1 -1
  141. package/src/geometries/cube-geometry.ts +47 -45
  142. package/src/geometries/cylinder-geometry.ts +2 -2
  143. package/src/geometries/ico-sphere-geometry.ts +6 -5
  144. package/src/geometries/plane-geometry.ts +5 -4
  145. package/src/geometries/sphere-geometry.ts +4 -3
  146. package/src/geometries/truncated-cone-geometry.ts +4 -13
  147. package/src/geometry/geometry-table.ts +1 -1
  148. package/src/geometry/geometry-utils.ts +3 -3
  149. package/src/geometry/geometry.ts +65 -110
  150. package/src/geometry/gpu-geometry.ts +125 -0
  151. package/src/geometry/gpu-table.ts +41 -0
  152. package/src/index.ts +34 -10
  153. package/src/lib/clip-space.ts +22 -21
  154. package/src/lib/pipeline-factory.ts +51 -168
  155. package/src/model/model.ts +726 -0
  156. package/src/scenegraph/group-node.ts +103 -0
  157. package/src/scenegraph/model-node.ts +50 -0
  158. package/src/scenegraph/scenegraph-node.ts +204 -0
  159. package/src/shader-inputs.ts +150 -0
  160. package/src/transform/buffer-transform.ts +94 -0
  161. package/src/transform/texture-transform.ts +169 -0
  162. package/dist/bundle.d.ts +0 -2
  163. package/dist/bundle.d.ts.map +0 -1
  164. package/dist/bundle.js +0 -5
  165. package/dist/bundle.js.map +0 -1
  166. package/dist/geometry/primitive-utils.d.ts +0 -1
  167. package/dist/geometry/primitive-utils.d.ts.map +0 -1
  168. package/dist/geometry/primitive-utils.js +0 -2
  169. package/dist/geometry/primitive-utils.js.map +0 -1
  170. package/dist/lib/animation-loop.d.ts.map +0 -1
  171. package/dist/lib/animation-loop.js.map +0 -1
  172. package/dist/lib/animation-props.d.ts.map +0 -1
  173. package/dist/lib/animation-props.js.map +0 -1
  174. package/dist/lib/model-utils.d.ts +0 -5
  175. package/dist/lib/model-utils.d.ts.map +0 -1
  176. package/dist/lib/model-utils.js +0 -45
  177. package/dist/lib/model-utils.js.map +0 -1
  178. package/dist/lib/model.d.ts +0 -41
  179. package/dist/lib/model.d.ts.map +0 -1
  180. package/dist/lib/model.js +0 -182
  181. package/dist/lib/model.js.map +0 -1
  182. package/dist/lib/render-loop.d.ts +0 -14
  183. package/dist/lib/render-loop.d.ts.map +0 -1
  184. package/dist/lib/render-loop.js +0 -49
  185. package/dist/lib/render-loop.js.map +0 -1
  186. package/src/bundle.ts +0 -4
  187. package/src/geometry/primitive-utils.ts +0 -30
  188. package/src/lib/model-utils.ts +0 -124
  189. package/src/lib/model.ts +0 -183
  190. package/src/lib/render-loop.ts +0 -58
  191. /package/dist/{lib → animation-loop}/animation-props.js +0 -0
@@ -0,0 +1,726 @@
1
+ // luma.gl, MIT license
2
+ // Copyright (c) vis.gl contributors
3
+
4
+ import type {TypedArray, RenderPipelineProps, RenderPipelineParameters} from '@luma.gl/core';
5
+ import type {BufferLayout, VertexArray, TransformFeedback} from '@luma.gl/core';
6
+ 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';
17
+ import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
18
+ import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
19
+ import {ShaderInputs} from '../shader-inputs';
20
+ import type {Geometry} from '../geometry/geometry';
21
+ import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
22
+ import {PipelineFactory} from '../lib/pipeline-factory';
23
+ import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
24
+ import {debugFramebuffer} from '../debug/debug-framebuffer';
25
+
26
+ const LOG_DRAW_PRIORITY = 2;
27
+ const LOG_DRAW_TIMEOUT = 10000;
28
+
29
+ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs'> & {
30
+ source?: string;
31
+ vs: {glsl?: string; wgsl?: string} | string | null;
32
+ fs: {glsl?: string; wgsl?: string} | string | null;
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
+ /** 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
+
46
+ /** Parameters that are built into the pipeline */
47
+ parameters?: RenderPipelineParameters;
48
+
49
+ /** Geometry */
50
+ geometry?: GPUGeometry | Geometry | null;
51
+
52
+ /** Vertex count */
53
+ vertexCount?: number;
54
+ /** instance count */
55
+ instanceCount?: number;
56
+
57
+ indexBuffer?: Buffer | null;
58
+ /** @note this is really a map of buffers, not a map of attributes */
59
+ attributes?: Record<string, Buffer>;
60
+ /** */
61
+ constantAttributes?: Record<string, TypedArray>;
62
+
63
+ /** @internal For use with {@link TransformFeedback}, WebGL 2 only. */
64
+ varyings?: string[];
65
+
66
+ transformFeedback?: TransformFeedback;
67
+
68
+ /** Mapped uniforms for shadertool modules */
69
+ moduleSettings?: Record<string, Record<string, any>>;
70
+ };
71
+
72
+ /**
73
+ * v9 Model API
74
+ * A model
75
+ * - automatically reuses pipelines (programs) when possible
76
+ * - automatically rebuilds pipelines if necessary to accommodate changed settings
77
+ * shadertools integration
78
+ * - accepts modules and performs shader transpilation
79
+ */
80
+ export class Model {
81
+ static defaultProps: Required<ModelProps> = {
82
+ ...RenderPipeline.defaultProps,
83
+ source: null,
84
+ vs: null,
85
+ fs: null,
86
+ id: 'unnamed',
87
+ handle: undefined,
88
+ userData: {},
89
+ defines: {},
90
+ modules: [],
91
+ moduleSettings: undefined!,
92
+ geometry: null,
93
+ indexBuffer: null,
94
+ attributes: {},
95
+ constantAttributes: {},
96
+ varyings: [],
97
+
98
+ shaderInputs: undefined!,
99
+ pipelineFactory: undefined!,
100
+ transformFeedback: undefined,
101
+ shaderAssembler: ShaderAssembler.getDefaultShaderAssembler()
102
+ };
103
+
104
+ readonly device: Device;
105
+ readonly id: string;
106
+ readonly vs: string;
107
+ readonly fs: string;
108
+ readonly pipelineFactory: PipelineFactory;
109
+ userData: {[key: string]: any} = {};
110
+
111
+ // Fixed properties (change can trigger pipeline rebuild)
112
+
113
+ /** The render pipeline GPU parameters, depth testing etc */
114
+ parameters: RenderPipelineParameters;
115
+
116
+ /** The primitive topology */
117
+ topology: PrimitiveTopology;
118
+ /** Buffer layout */
119
+ bufferLayout: BufferLayout[];
120
+
121
+ // Dynamic properties
122
+
123
+ /** Vertex count */
124
+ vertexCount: number;
125
+ /** instance count */
126
+ instanceCount: number = 0;
127
+
128
+ /** Index buffer */
129
+ indexBuffer: Buffer | null = null;
130
+ /** Buffer-valued attributes */
131
+ bufferAttributes: Record<string, Buffer> = {};
132
+ /** Constant-valued attributes */
133
+ constantAttributes: Record<string, TypedArray> = {};
134
+ /** Bindings (textures, samplers, uniform buffers) */
135
+ bindings: Record<string, Binding> = {};
136
+ /** Sets uniforms @deprecated Use uniform buffers and setBindings() for portability*/
137
+ uniforms: Record<string, UniformValue> = {};
138
+
139
+ /**
140
+ * VertexArray
141
+ * @note not implemented: if bufferLayout is updated, vertex array has to be rebuilt!
142
+ * @todo - allow application to define multiple vertex arrays?
143
+ * */
144
+ vertexArray: VertexArray;
145
+
146
+ /** TransformFeedback, WebGL 2 only. */
147
+ transformFeedback: TransformFeedback | null = null;
148
+
149
+ /** The underlying GPU "program". @note May be recreated if parameters change */
150
+ pipeline: RenderPipeline;
151
+
152
+ /** ShaderInputs instance */
153
+ shaderInputs: ShaderInputs;
154
+
155
+ _uniformStore: UniformStore;
156
+
157
+ _pipelineNeedsUpdate: string | false = 'newly created';
158
+ _attributeInfos: Record<string, AttributeInfo> = {};
159
+ _gpuGeometry: GPUGeometry | null = null;
160
+ private _getModuleUniforms: (props?: Record<string, Record<string, any>>) => Record<string, any>;
161
+ private props: Required<ModelProps>;
162
+
163
+ constructor(device: Device, props: ModelProps) {
164
+ this.props = {...Model.defaultProps, ...props};
165
+ props = this.props;
166
+ this.id = props.id || uid('model');
167
+ this.device = device;
168
+
169
+ Object.assign(this.userData, props.userData);
170
+
171
+ // Setup shader module inputs
172
+ const moduleMap = Object.fromEntries(
173
+ this.props.modules?.map(module => [module.name, module]) || []
174
+ );
175
+ this.setShaderInputs(props.shaderInputs || new ShaderInputs(moduleMap));
176
+
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
+ // Setup shader assembler
194
+ const platformInfo = getPlatformInfo(device);
195
+ const modules =
196
+ (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
+ this.vs = vs;
204
+ this.fs = fs;
205
+ this._getModuleUniforms = getUniforms;
206
+
207
+ this.vertexCount = this.props.vertexCount;
208
+ this.instanceCount = this.props.instanceCount;
209
+
210
+ this.topology = this.props.topology;
211
+ this.bufferLayout = this.props.bufferLayout;
212
+ this.parameters = this.props.parameters;
213
+
214
+ // Geometry, if provided, sets topology and vertex cound
215
+ if (props.geometry) {
216
+ this._gpuGeometry = this.setGeometry(props.geometry);
217
+ }
218
+
219
+ this.pipelineFactory =
220
+ props.pipelineFactory || PipelineFactory.getDefaultPipelineFactory(this.device);
221
+
222
+ // Create the pipeline
223
+ // @note order is important
224
+ this.pipeline = this._updatePipeline();
225
+
226
+ this.vertexArray = device.createVertexArray({
227
+ renderPipeline: this.pipeline
228
+ });
229
+
230
+ // Now we can apply geometry attributes
231
+ if (this._gpuGeometry) {
232
+ this._setGeometryAttributes(this._gpuGeometry);
233
+ }
234
+
235
+ // Apply any dynamic settings that will not trigger pipeline change
236
+ if (props.vertexCount) {
237
+ this.setVertexCount(props.vertexCount);
238
+ }
239
+ if (props.instanceCount) {
240
+ this.setInstanceCount(props.instanceCount);
241
+ }
242
+ // @ts-expect-error
243
+ if (props.indices) {
244
+ throw new Error('Model.props.indices removed. Use props.indexBuffer');
245
+ }
246
+ if (props.indexBuffer) {
247
+ this.setIndexBuffer(props.indexBuffer);
248
+ }
249
+ if (props.attributes) {
250
+ this.setAttributes(props.attributes);
251
+ }
252
+ if (props.constantAttributes) {
253
+ this.setConstantAttributes(props.constantAttributes);
254
+ }
255
+ if (props.bindings) {
256
+ this.setBindings(props.bindings);
257
+ }
258
+ if (props.uniforms) {
259
+ this.setUniforms(props.uniforms);
260
+ }
261
+ if (props.moduleSettings) {
262
+ log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')();
263
+ this.updateModuleSettings(props.moduleSettings);
264
+ }
265
+ if (props.transformFeedback) {
266
+ this.transformFeedback = props.transformFeedback;
267
+ }
268
+
269
+ // WebGL1?
270
+ // this.setUniforms(this._getModuleUniforms()); // Get all default module uniforms
271
+
272
+ // Catch any access to non-standard props
273
+ Object.seal(this);
274
+ }
275
+
276
+ destroy(): void {
277
+ this.pipelineFactory.release(this.pipeline);
278
+ this._uniformStore.destroy();
279
+ }
280
+
281
+ // Draw call
282
+
283
+ predraw() {
284
+ // Update uniform buffers if needed
285
+ this.updateShaderInputs();
286
+ }
287
+
288
+ draw(renderPass: RenderPass): void {
289
+ this.predraw();
290
+
291
+ try {
292
+ this._logDrawCallStart();
293
+
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()?
296
+ this.pipeline = this._updatePipeline();
297
+
298
+ // Set pipeline state, we may be sharing a pipeline so we need to set all state on every draw
299
+ // Any caching needs to be done inside the pipeline functions
300
+ this.pipeline.setBindings(this.bindings);
301
+ this.pipeline.setUniforms(this.uniforms);
302
+
303
+ this.pipeline.draw({
304
+ renderPass,
305
+ vertexArray: this.vertexArray,
306
+ vertexCount: this.vertexCount,
307
+ instanceCount: this.instanceCount,
308
+ transformFeedback: this.transformFeedback
309
+ });
310
+ } finally {
311
+ this._logDrawCallEnd();
312
+ }
313
+ this._logFramebuffer(renderPass);
314
+ }
315
+
316
+ // Update fixed fields (can trigger pipeline rebuild)
317
+
318
+ /**
319
+ * Updates the optional geometry
320
+ * Geometry, set topology and bufferLayout
321
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch on WebGPU
322
+ */
323
+ setGeometry(geometry: GPUGeometry | Geometry): GPUGeometry {
324
+ 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];
347
+ }
348
+ }
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);
355
+ }
356
+
357
+ /**
358
+ * Updates the primitive topology ('triangle-list', 'triangle-strip' etc).
359
+ * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
360
+ */
361
+ setTopology(topology: PrimitiveTopology): void {
362
+ if (topology !== this.topology) {
363
+ this.topology = topology;
364
+ this._setPipelineNeedsUpdate('topology');
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Updates the buffer layout.
370
+ * @note Triggers a pipeline rebuild / pipeline cache fetch on WebGPU
371
+ */
372
+ setBufferLayout(bufferLayout: BufferLayout[]): void {
373
+ this.bufferLayout = this._gpuGeometry
374
+ ? mergeBufferLayouts(bufferLayout, this._gpuGeometry.bufferLayout)
375
+ : bufferLayout;
376
+ this._setPipelineNeedsUpdate('bufferLayout');
377
+
378
+ // Recreate the pipeline
379
+ this.pipeline = this._updatePipeline();
380
+
381
+ // vertex array needs to be updated if we update buffer layout,
382
+ // but not if we update parameters
383
+ this.vertexArray = this.device.createVertexArray({
384
+ renderPipeline: this.pipeline
385
+ });
386
+
387
+ // Reapply geometry attributes to the new vertex array
388
+ if (this._gpuGeometry) {
389
+ this._setGeometryAttributes(this._gpuGeometry);
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Set GPU parameters.
395
+ * @note Can trigger a pipeline rebuild / pipeline cache fetch.
396
+ * @param parameters
397
+ */
398
+ setParameters(parameters: RenderPipelineParameters) {
399
+ if (!deepEqual(parameters, this.parameters, 2)) {
400
+ this.parameters = parameters;
401
+ this._setPipelineNeedsUpdate('parameters');
402
+ }
403
+ }
404
+
405
+ // Update dynamic fields
406
+
407
+ /**
408
+ * Updates the vertex count (used in draw calls)
409
+ * @note Any attributes with stepMode=vertex need to be at least this big
410
+ */
411
+ setVertexCount(vertexCount: number): void {
412
+ this.vertexCount = vertexCount;
413
+ }
414
+
415
+ /**
416
+ * Updates the instance count (used in draw calls)
417
+ * @note Any attributes with stepMode=instance need to be at least this big
418
+ */
419
+ setInstanceCount(instanceCount: number): void {
420
+ this.instanceCount = instanceCount;
421
+ }
422
+
423
+ setShaderInputs(shaderInputs: ShaderInputs): void {
424
+ this.shaderInputs = shaderInputs;
425
+ this._uniformStore = new UniformStore(this.shaderInputs.modules);
426
+ // Create uniform buffer bindings for all modules
427
+ for (const moduleName of Object.keys(this.shaderInputs.modules)) {
428
+ const uniformBuffer = this._uniformStore.getManagedUniformBuffer(this.device, moduleName);
429
+ this.bindings[`${moduleName}Uniforms`] = uniformBuffer;
430
+ }
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
+ }
450
+ }
451
+
452
+ updateShaderInputs(): void {
453
+ 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);
464
+ }
465
+
466
+ /**
467
+ * Sets bindings (textures, samplers, uniform buffers)
468
+ */
469
+ setBindings(bindings: Record<string, Binding>): void {
470
+ Object.assign(this.bindings, bindings);
471
+ }
472
+
473
+ /**
474
+ * Sets individual uniforms
475
+ * @deprecated WebGL only, use uniform buffers for portability
476
+ * @param uniforms
477
+ * @returns self for chaining
478
+ */
479
+ setUniforms(uniforms: Record<string, UniformValue>): void {
480
+ this.pipeline.setUniforms(uniforms);
481
+ Object.assign(this.uniforms, uniforms);
482
+ }
483
+
484
+ /**
485
+ * Sets the index buffer
486
+ * @todo - how to unset it if we change geometry?
487
+ */
488
+ setIndexBuffer(indexBuffer: Buffer | null): void {
489
+ 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;
497
+ }
498
+
499
+ /**
500
+ * Sets attributes (buffers)
501
+ * @note Overrides any attributes previously set with the same name
502
+ */
503
+ setAttributes(buffers: Record<string, Buffer>, _option?: 'ignore-unknown'): void {
504
+ if (buffers.indices) {
505
+ log.warn(
506
+ `Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
507
+ )();
508
+ }
509
+ for (const [bufferName, buffer] of Object.entries(buffers)) {
510
+ const bufferLayout = this.bufferLayout.find(layout => getAttributeNames(layout).includes(bufferName));
511
+ if (!bufferLayout) {
512
+ log.warn(`Model(${this.id}): Missing layout for buffer "${bufferName}".`)();
513
+ continue; // eslint-disable-line no-continue
514
+ }
515
+
516
+ // For an interleaved attribute we may need to set multiple attributes
517
+ const attributeNames = getAttributeNames(bufferLayout);
518
+ let set = false;
519
+ for (const attributeName of attributeNames) {
520
+ const attributeInfo = this._attributeInfos[attributeName];
521
+ if (attributeInfo) {
522
+ this.vertexArray.setBuffer(attributeInfo.location, buffer);
523
+ set = true;
524
+ }
525
+ }
526
+ if (!set && _option !== 'ignore-unknown') {
527
+ log.warn(
528
+ `Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`
529
+ )();
530
+ }
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Sets constant attributes
536
+ * @note Overrides any attributes previously set with the same name
537
+ * Constant attributes are only supported in WebGL, not in WebGPU
538
+ * Any attribute that is disabled in the current vertex array object
539
+ * is read from the context's global constant value for that attribute location.
540
+ * @param constantAttributes
541
+ */
542
+ setConstantAttributes(attributes: Record<string, TypedArray>): void {
543
+ for (const [attributeName, value] of Object.entries(attributes)) {
544
+ const attributeInfo = this._attributeInfos[attributeName];
545
+ if (attributeInfo) {
546
+ this.vertexArray.setConstant(attributeInfo.location, value);
547
+ } else {
548
+ log.warn(
549
+ `Model "${this.id}: Ignoring constant supplied for unknown attribute "${attributeName}"`
550
+ )();
551
+ }
552
+ }
553
+ }
554
+
555
+ _setPipelineNeedsUpdate(reason: string): void {
556
+ this._pipelineNeedsUpdate = this._pipelineNeedsUpdate || reason;
557
+ }
558
+
559
+ _updatePipeline(): RenderPipeline {
560
+ if (this._pipelineNeedsUpdate) {
561
+ if (this.pipeline) {
562
+ log.log(
563
+ 1,
564
+ `Model ${this.id}: Recreating pipeline because "${this._pipelineNeedsUpdate}".`
565
+ )();
566
+ }
567
+
568
+ this._pipelineNeedsUpdate = false;
569
+
570
+ const vs = this.device.createShader({
571
+ id: `${this.id}-vertex`,
572
+ stage: 'vertex',
573
+ source: this.vs
574
+ });
575
+
576
+ const fs = this.fs
577
+ ? this.device.createShader({
578
+ id: `${this.id}-fragment`,
579
+ stage: 'fragment',
580
+ source: this.fs
581
+ })
582
+ : null;
583
+
584
+ this.pipeline = this.device.createRenderPipeline({
585
+ ...this.props,
586
+ bufferLayout: this.bufferLayout,
587
+ topology: this.topology,
588
+ parameters: this.parameters,
589
+ vs,
590
+ fs
591
+ });
592
+
593
+ this._attributeInfos = getAttributeInfosFromLayouts(
594
+ this.pipeline.shaderLayout,
595
+ this.bufferLayout
596
+ );
597
+ }
598
+ return this.pipeline;
599
+ }
600
+
601
+ /** Throttle draw call logging */
602
+ _lastLogTime = 0;
603
+ _logOpen = false;
604
+
605
+ _logDrawCallStart(): void {
606
+ // IF level is 4 or higher, log every frame.
607
+ const logDrawTimeout = log.level > 3 ? 0 : LOG_DRAW_TIMEOUT;
608
+ if (log.level < 2 || Date.now() - this._lastLogTime < logDrawTimeout) {
609
+ return;
610
+ }
611
+
612
+ this._lastLogTime = Date.now();
613
+ this._logOpen = true;
614
+
615
+ log.group(LOG_DRAW_PRIORITY, `>>> DRAWING MODEL ${this.id}`, {collapsed: log.level <= 2})();
616
+ }
617
+
618
+ _logDrawCallEnd(): void {
619
+ if (this._logOpen) {
620
+ const shaderLayoutTable = getDebugTableForShaderLayout(this.pipeline.shaderLayout, this.id);
621
+
622
+ // log.table(logLevel, attributeTable)();
623
+ // log.table(logLevel, uniformTable)();
624
+ log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
625
+
626
+ const uniformTable = this.shaderInputs.getDebugTable();
627
+ // Add any global uniforms
628
+ for (const [name, value] of Object.entries(this.uniforms)) {
629
+ uniformTable[name] = {value};
630
+ }
631
+ log.table(LOG_DRAW_PRIORITY, uniformTable)();
632
+
633
+ const attributeTable = this._getAttributeDebugTable();
634
+ log.table(LOG_DRAW_PRIORITY, this._attributeInfos)();
635
+ log.table(LOG_DRAW_PRIORITY, attributeTable)();
636
+
637
+ log.groupEnd(LOG_DRAW_PRIORITY)();
638
+ this._logOpen = false;
639
+ }
640
+ }
641
+
642
+ protected _drawCount = 0;
643
+ _logFramebuffer(renderPass: RenderPass): void {
644
+ const debugFramebuffers = log.get('framebuffer');
645
+ this._drawCount++;
646
+ // Update first 3 frames and then every 60 frames
647
+ if (!debugFramebuffers || ((this._drawCount++ > 3) && (this._drawCount % 60))) {
648
+ return;
649
+ }
650
+ // TODO - display framebuffer output in debug window
651
+ const framebuffer = renderPass.props.framebuffer;
652
+ if (framebuffer) {
653
+ debugFramebuffer(framebuffer, {id: framebuffer.id, minimap: true});
654
+ // log.image({logLevel: LOG_DRAW_PRIORITY, message: `${framebuffer.id} %c sup?`, image})();
655
+ }
656
+ }
657
+
658
+ _getAttributeDebugTable(): Record<string, Record<string, unknown>> {
659
+ const table: Record<string, Record<string, unknown>> = {};
660
+ for (const [name, attributeInfo] of Object.entries(this._attributeInfos)) {
661
+ table[attributeInfo.location] = {
662
+ name,
663
+ type: attributeInfo.shaderType,
664
+ values: this._getBufferOrConstantValues(
665
+ this.vertexArray.attributes[attributeInfo.location],
666
+ attributeInfo.bufferDataType
667
+ )
668
+ };
669
+ }
670
+ if (this.vertexArray.indexBuffer) {
671
+ const {indexBuffer} = this.vertexArray;
672
+ const values =
673
+ indexBuffer.indexType === 'uint32'
674
+ ? new Uint32Array(indexBuffer.debugData)
675
+ : new Uint16Array(indexBuffer.debugData);
676
+ table.indices = {
677
+ name: 'indices',
678
+ type: indexBuffer.indexType,
679
+ values: values.toString()
680
+ };
681
+ }
682
+ return table;
683
+ }
684
+
685
+ // TODO - fix typing of luma data types
686
+ _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
687
+ const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
688
+ const typedArray =
689
+ attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
690
+ return typedArray.toString();
691
+ }
692
+ }
693
+
694
+ // HELPERS
695
+
696
+ /** TODO - move to core, document add tests */
697
+ function mergeBufferLayouts(layouts1: BufferLayout[], layouts2: BufferLayout[]): BufferLayout[] {
698
+ const layouts = [...layouts1];
699
+ for (const attribute of layouts2) {
700
+ const index = layouts.findIndex(attribute2 => attribute2.name === attribute.name);
701
+ if (index < 0) {
702
+ layouts.push(attribute);
703
+ } else {
704
+ layouts[index] = attribute;
705
+ }
706
+ }
707
+ return layouts;
708
+ }
709
+
710
+ /** Create a shadertools platform info from the Device */
711
+ export function getPlatformInfo(device: Device): PlatformInfo {
712
+ return {
713
+ type: device.info.type,
714
+ shaderLanguage: device.info.shadingLanguage,
715
+ shaderLanguageVersion: device.info.shadingLanguageVersion as 100 | 300,
716
+ gpu: device.info.gpu,
717
+ features: device.features
718
+ };
719
+ }
720
+
721
+ /** Get attribute names from a BufferLayout */
722
+ function getAttributeNames(bufferLayout: BufferLayout): string[] {
723
+ return bufferLayout.attributes
724
+ ? bufferLayout.attributes?.map(layout => layout.attribute)
725
+ : [bufferLayout.name];
726
+ }