@luma.gl/engine 9.2.5 → 9.3.0-alpha.2

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 (71) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +3 -1
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +10 -4
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/compute/computation.d.ts.map +1 -1
  6. package/dist/compute/computation.js +3 -2
  7. package/dist/compute/computation.js.map +1 -1
  8. package/dist/compute/swap.d.ts +2 -0
  9. package/dist/compute/swap.d.ts.map +1 -1
  10. package/dist/compute/swap.js +10 -5
  11. package/dist/compute/swap.js.map +1 -1
  12. package/dist/dist.dev.js +554 -358
  13. package/dist/dist.min.js +59 -50
  14. package/dist/dynamic-texture/dynamic-texture.d.ts +95 -0
  15. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  16. package/dist/dynamic-texture/dynamic-texture.js +356 -0
  17. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  18. package/dist/dynamic-texture/texture-data.d.ts +137 -0
  19. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  20. package/dist/dynamic-texture/texture-data.js +183 -0
  21. package/dist/dynamic-texture/texture-data.js.map +1 -0
  22. package/dist/factories/pipeline-factory.d.ts.map +1 -1
  23. package/dist/factories/pipeline-factory.js +3 -3
  24. package/dist/factories/pipeline-factory.js.map +1 -1
  25. package/dist/factories/shader-factory.d.ts.map +1 -1
  26. package/dist/factories/shader-factory.js +3 -2
  27. package/dist/factories/shader-factory.js.map +1 -1
  28. package/dist/index.cjs +566 -370
  29. package/dist/index.cjs.map +4 -4
  30. package/dist/index.d.ts +8 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/model/model.d.ts +31 -10
  35. package/dist/model/model.d.ts.map +1 -1
  36. package/dist/model/model.js +34 -14
  37. package/dist/model/model.js.map +1 -1
  38. package/dist/models/billboard-texture-model.d.ts +8 -5
  39. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  40. package/dist/models/billboard-texture-model.js +70 -18
  41. package/dist/models/billboard-texture-model.js.map +1 -1
  42. package/dist/passes/get-fragment-shader.js +15 -11
  43. package/dist/passes/get-fragment-shader.js.map +1 -1
  44. package/dist/passes/shader-pass-renderer.d.ts +5 -5
  45. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  46. package/dist/passes/shader-pass-renderer.js +13 -12
  47. package/dist/passes/shader-pass-renderer.js.map +1 -1
  48. package/dist/types.d.ts +7 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +5 -0
  51. package/dist/types.js.map +1 -0
  52. package/package.json +4 -4
  53. package/src/animation-loop/animation-loop.ts +11 -4
  54. package/src/compute/computation.ts +3 -2
  55. package/src/compute/swap.ts +13 -7
  56. package/src/dynamic-texture/dynamic-texture.ts +451 -0
  57. package/src/dynamic-texture/texture-data.ts +301 -0
  58. package/src/factories/pipeline-factory.ts +4 -3
  59. package/src/factories/shader-factory.ts +4 -2
  60. package/src/index.ts +9 -4
  61. package/src/model/model.ts +37 -18
  62. package/src/models/billboard-texture-model.ts +81 -22
  63. package/src/passes/get-fragment-shader.ts +15 -11
  64. package/src/passes/shader-pass-renderer.ts +22 -16
  65. package/src/types.ts +11 -0
  66. package/dist/async-texture/async-texture.d.ts +0 -166
  67. package/dist/async-texture/async-texture.d.ts.map +0 -1
  68. package/dist/async-texture/async-texture.js +0 -386
  69. package/dist/async-texture/async-texture.js.map +0 -1
  70. package/src/async-texture/async-texture.ts +0 -551
  71. /package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +0 -0
@@ -0,0 +1,301 @@
1
+ import type {TypedArray, TextureFormat, ExternalImage} from '@luma.gl/core';
2
+ import {isExternalImage, getExternalImageSize} from '@luma.gl/core';
3
+
4
+ export type TextureImageSource = ExternalImage;
5
+
6
+ /**
7
+ * One mip level
8
+ * Basic data structure is similar to `ImageData`
9
+ * additional optional fields can describe compressed texture data.
10
+ */
11
+ export type TextureImageData = {
12
+ /** WebGPU style format string. Defaults to 'rgba8unorm' */
13
+ format?: TextureFormat;
14
+ /** Typed Array with the bytes of the image. @note beware row byte alignment requirements */
15
+ data: TypedArray;
16
+ /** Width of the image, in pixels, @note beware row byte alignment requirements */
17
+ width: number;
18
+ /** Height of the image, in rows */
19
+ height: number;
20
+ };
21
+
22
+ /**
23
+ * A single mip-level can be initialized by data or an ImageBitmap etc
24
+ * @note in the WebGPU spec a mip-level is called a subresource
25
+ */
26
+ export type TextureMipLevelData = TextureImageData | TextureImageSource;
27
+
28
+ /**
29
+ * Texture data for one image "slice" (which can consist of multiple miplevels)
30
+ * Thus data for one slice be a single mip level or an array of miplevels
31
+ * @note in the WebGPU spec each cross-section image in a 3D texture is called a "slice",
32
+ * in a array texture each image in the array is called an array "layer"
33
+ * luma.gl calls one image in a GPU texture a "slice" regardless of context.
34
+ */
35
+ export type TextureSliceData = TextureMipLevelData | TextureMipLevelData[];
36
+
37
+ /** Names of cube texture faces */
38
+ export type TextureCubeFace = '+X' | '-X' | '+Y' | '-Y' | '+Z' | '-Z';
39
+
40
+ /** Array of cube texture faces. @note: index in array is the face index */
41
+ // prettier-ignore
42
+ export const TEXTURE_CUBE_FACES = ['+X', '-X', '+Y', '-Y', '+Z', '-Z'] as const satisfies readonly TextureCubeFace[];
43
+
44
+ /** Map of cube texture face names to face indexes */
45
+ // prettier-ignore
46
+ export const TEXTURE_CUBE_FACE_MAP = {'+X': 0, '-X': 1, '+Y': 2, '-Y': 3, '+Z': 4, '-Z': 5} as const satisfies Record<TextureCubeFace, number>;
47
+
48
+ /** @todo - Define what data type is supported for 1D textures. TextureImageData with height = 1 */
49
+ export type Texture1DData = TextureSliceData;
50
+
51
+ /** Texture data can be one or more mip levels */
52
+ export type Texture2DData = TextureSliceData;
53
+
54
+ /** 6 face textures */
55
+ export type TextureCubeData = Record<TextureCubeFace, TextureSliceData>;
56
+
57
+ /** Array of textures */
58
+ export type Texture3DData = TextureSliceData[];
59
+
60
+ /** Array of textures */
61
+ export type TextureArrayData = TextureSliceData[];
62
+
63
+ /** Array of 6 face textures */
64
+ export type TextureCubeArrayData = Record<TextureCubeFace, TextureSliceData>[];
65
+
66
+ type TextureData =
67
+ | Texture1DData
68
+ | Texture3DData
69
+ | TextureArrayData
70
+ | TextureCubeArrayData
71
+ | TextureCubeData;
72
+
73
+ /** Sync data props */
74
+ export type TextureDataProps =
75
+ | {dimension: '1d'; data: Texture1DData | null}
76
+ | {dimension?: '2d'; data: Texture2DData | null}
77
+ | {dimension: '3d'; data: Texture3DData | null}
78
+ | {dimension: '2d-array'; data: TextureArrayData | null}
79
+ | {dimension: 'cube'; data: TextureCubeData | null}
80
+ | {dimension: 'cube-array'; data: TextureCubeArrayData | null};
81
+
82
+ /** Async data props */
83
+ export type TextureDataAsyncProps =
84
+ | {dimension: '1d'; data?: Promise<Texture1DData> | Texture1DData | null}
85
+ | {dimension?: '2d'; data?: Promise<Texture2DData> | Texture2DData | null}
86
+ | {dimension: '3d'; data?: Promise<Texture3DData> | Texture3DData | null}
87
+ | {dimension: '2d-array'; data?: Promise<TextureArrayData> | TextureArrayData | null}
88
+ | {dimension: 'cube'; data?: Promise<TextureCubeData> | TextureCubeData | null}
89
+ | {dimension: 'cube-array'; data?: Promise<TextureCubeArrayData> | TextureCubeArrayData | null};
90
+
91
+ /** Describes data for one sub resource (one mip level of one slice (depth or array layer)) */
92
+ export type TextureSubresource = {
93
+ /** slice (depth or array layer)) */
94
+ z: number;
95
+ /** mip level (0 - max mip levels) */
96
+ mipLevel: number;
97
+ } & (
98
+ | {
99
+ type: 'external-image';
100
+ image: ExternalImage;
101
+ /** @deprecated is this an appropriate place for this flag? */
102
+ flipY?: boolean;
103
+ }
104
+ | {
105
+ type: 'texture-data';
106
+ data: TextureImageData;
107
+ }
108
+ );
109
+
110
+ /** Check if texture data is a typed array */
111
+ export function isTextureSliceData(data: TextureData): data is TextureImageData {
112
+ const typedArray = (data as TextureImageData)?.data;
113
+ return ArrayBuffer.isView(typedArray);
114
+ }
115
+
116
+ export function getFirstMipLevel(layer: TextureSliceData | null): TextureMipLevelData | null {
117
+ if (!layer) return null;
118
+ return Array.isArray(layer) ? (layer[0] ?? null) : layer;
119
+ }
120
+
121
+ export function getTextureSizeFromData(
122
+ props: TextureDataProps
123
+ ): {width: number; height: number} | null {
124
+ const {dimension, data} = props;
125
+ if (!data) {
126
+ return null;
127
+ }
128
+
129
+ switch (dimension) {
130
+ case '1d': {
131
+ const mipLevel = getFirstMipLevel(data);
132
+ if (!mipLevel) return null;
133
+ const {width} = getTextureMipLevelSize(mipLevel);
134
+ return {width, height: 1};
135
+ }
136
+ case '2d': {
137
+ const mipLevel = getFirstMipLevel(data);
138
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
139
+ }
140
+ case '3d':
141
+ case '2d-array': {
142
+ if (!Array.isArray(data) || data.length === 0) return null;
143
+ const mipLevel = getFirstMipLevel(data[0]);
144
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
145
+ }
146
+ case 'cube': {
147
+ const face = (Object.keys(data)[0] as TextureCubeFace) ?? null;
148
+ if (!face) return null;
149
+ const faceData = (data as Record<TextureCubeFace, TextureSliceData>)[face];
150
+ const mipLevel = getFirstMipLevel(faceData);
151
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
152
+ }
153
+ case 'cube-array': {
154
+ if (!Array.isArray(data) || data.length === 0) return null;
155
+ const firstCube = data[0];
156
+ const face = (Object.keys(firstCube)[0] as TextureCubeFace) ?? null;
157
+ if (!face) return null;
158
+ const mipLevel = getFirstMipLevel(firstCube[face]);
159
+ return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
160
+ }
161
+ default:
162
+ return null;
163
+ }
164
+ }
165
+
166
+ function getTextureMipLevelSize(data: TextureMipLevelData): {width: number; height: number} {
167
+ if (isExternalImage(data)) {
168
+ return getExternalImageSize(data);
169
+ }
170
+ if (typeof data === 'object' && 'width' in data && 'height' in data) {
171
+ return {width: data.width, height: data.height};
172
+ }
173
+ throw new Error('Unsupported mip-level data');
174
+ }
175
+
176
+ /** Type guard: is a mip-level `TextureImageData` (vs ExternalImage) */
177
+ function isTextureImageData(data: TextureMipLevelData): data is TextureImageData {
178
+ return (
179
+ typeof data === 'object' &&
180
+ data !== null &&
181
+ 'data' in data &&
182
+ 'width' in data &&
183
+ 'height' in data
184
+ );
185
+ }
186
+
187
+ /** Resolve size for a single mip-level datum */
188
+ // function getTextureMipLevelSizeFromData(data: TextureMipLevelData): {
189
+ // width: number;
190
+ // height: number;
191
+ // } {
192
+ // if (this.device.isExternalImage(data)) {
193
+ // return this.device.getExternalImageSize(data);
194
+ // }
195
+ // if (this.isTextureImageData(data)) {
196
+ // return {width: data.width, height: data.height};
197
+ // }
198
+ // // Fallback (should not happen with current types)
199
+ // throw new Error('Unsupported mip-level data');
200
+ // }
201
+
202
+ /** Convert cube face label to depth index */
203
+ export function getCubeFaceIndex(face: TextureCubeFace): number {
204
+ const idx = TEXTURE_CUBE_FACE_MAP[face];
205
+ if (idx === undefined) throw new Error(`Invalid cube face: ${face}`);
206
+ return idx;
207
+ }
208
+
209
+ /** Convert cube face label to texture slice index. Index can be used with `setTexture2DData()`. */
210
+ export function getCubeArrayFaceIndex(cubeIndex: number, face: TextureCubeFace): number {
211
+ return 6 * cubeIndex + getCubeFaceIndex(face);
212
+ }
213
+
214
+ // ------------------ Upload helpers ------------------
215
+
216
+ /** Experimental: Set multiple mip levels (1D) */
217
+ export function getTexture1DSubresources(data: Texture1DData): TextureSubresource[] {
218
+ // Not supported in WebGL; left explicit
219
+ throw new Error('setTexture1DData not supported in WebGL.');
220
+ // const subresources: TextureSubresource[] = [];
221
+ // return subresources;
222
+ }
223
+
224
+ /** Normalize 2D layer payload into an array of mip-level items */
225
+ function _normalizeTexture2DData(data: Texture2DData): (TextureImageData | ExternalImage)[] {
226
+ return Array.isArray(data) ? data : [data];
227
+ }
228
+
229
+ /** Experimental: Set multiple mip levels (2D), optionally at `z` (depth/array index) */
230
+ export function getTexture2DSubresources(
231
+ slice: number,
232
+ lodData: Texture2DData
233
+ ): TextureSubresource[] {
234
+ const lodArray = _normalizeTexture2DData(lodData);
235
+ const z = slice;
236
+
237
+ const subresources: TextureSubresource[] = [];
238
+
239
+ for (let mipLevel = 0; mipLevel < lodArray.length; mipLevel++) {
240
+ const imageData = lodArray[mipLevel];
241
+ if (isExternalImage(imageData)) {
242
+ subresources.push({
243
+ type: 'external-image',
244
+ image: imageData,
245
+ z,
246
+ mipLevel
247
+ });
248
+ } else if (isTextureImageData(imageData)) {
249
+ subresources.push({
250
+ type: 'texture-data',
251
+ data: imageData,
252
+ z,
253
+ mipLevel
254
+ });
255
+ } else {
256
+ throw new Error('Unsupported 2D mip-level payload');
257
+ }
258
+ }
259
+
260
+ return subresources;
261
+ }
262
+
263
+ /** 3D: multiple depth slices, each may carry multiple mip levels */
264
+ export function getTexture3DSubresources(data: Texture3DData): TextureSubresource[] {
265
+ const subresources: TextureSubresource[] = [];
266
+ for (let depth = 0; depth < data.length; depth++) {
267
+ subresources.push(...getTexture2DSubresources(depth, data[depth]));
268
+ }
269
+ return subresources;
270
+ }
271
+
272
+ /** 2D array: multiple layers, each may carry multiple mip levels */
273
+ export function getTextureArraySubresources(data: TextureArrayData): TextureSubresource[] {
274
+ const subresources: TextureSubresource[] = [];
275
+ for (let layer = 0; layer < data.length; layer++) {
276
+ subresources.push(...getTexture2DSubresources(layer, data[layer]));
277
+ }
278
+ return subresources;
279
+ }
280
+
281
+ /** Cube: 6 faces, each may carry multiple mip levels */
282
+ export function getTextureCubeSubresources(data: TextureCubeData): TextureSubresource[] {
283
+ const subresources: TextureSubresource[] = [];
284
+ for (const [face, faceData] of Object.entries(data) as [TextureCubeFace, TextureSliceData][]) {
285
+ const faceDepth = getCubeFaceIndex(face);
286
+ subresources.push(...getTexture2DSubresources(faceDepth, faceData));
287
+ }
288
+ return subresources;
289
+ }
290
+
291
+ /** Cube array: multiple cubes (faces×layers), each face may carry multiple mips */
292
+ export function getTextureCubeArraySubresources(data: TextureCubeArrayData): TextureSubresource[] {
293
+ const subresources: TextureSubresource[] = [];
294
+ data.forEach((cubeData, cubeIndex) => {
295
+ for (const [face, faceData] of Object.entries(cubeData)) {
296
+ const faceDepth = getCubeArrayFaceIndex(cubeIndex, face as TextureCubeFace);
297
+ getTexture2DSubresources(faceDepth, faceData);
298
+ }
299
+ });
300
+ return subresources;
301
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core';
6
6
  import {Device, RenderPipeline, ComputePipeline, log} from '@luma.gl/core';
7
+ import type {EngineModuleState} from '../types';
7
8
  import {uid} from '../utils/uid';
8
9
 
9
10
  export type PipelineFactoryProps = RenderPipelineProps;
@@ -19,9 +20,9 @@ export class PipelineFactory {
19
20
 
20
21
  /** Get the singleton default pipeline factory for the specified device */
21
22
  static getDefaultPipelineFactory(device: Device): PipelineFactory {
22
- device._lumaData['defaultPipelineFactory'] =
23
- device._lumaData['defaultPipelineFactory'] || new PipelineFactory(device);
24
- return device._lumaData['defaultPipelineFactory'] as PipelineFactory;
23
+ const moduleData = device.getModuleData<EngineModuleState>('@luma.gl/engine');
24
+ moduleData.defaultPipelineFactory ||= new PipelineFactory(device);
25
+ return moduleData.defaultPipelineFactory;
25
26
  }
26
27
 
27
28
  readonly device: Device;
@@ -3,6 +3,7 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {Device, Shader, ShaderProps, log} from '@luma.gl/core';
6
+ import type {EngineModuleState} from '../types';
6
7
 
7
8
  /** Manages a cached pool of Shaders for reuse. */
8
9
  export class ShaderFactory {
@@ -10,8 +11,9 @@ export class ShaderFactory {
10
11
 
11
12
  /** Returns the default ShaderFactory for the given {@link Device}, creating one if necessary. */
12
13
  static getDefaultShaderFactory(device: Device): ShaderFactory {
13
- device._lumaData['defaultShaderFactory'] ||= new ShaderFactory(device);
14
- return device._lumaData['defaultShaderFactory'] as ShaderFactory;
14
+ const moduleData = device.getModuleData<EngineModuleState>('@luma.gl/engine');
15
+ moduleData.defaultShaderFactory ||= new ShaderFactory(device);
16
+ return moduleData.defaultShaderFactory;
15
17
  }
16
18
 
17
19
  public readonly device: Device;
package/src/index.ts CHANGED
@@ -83,17 +83,16 @@ export {Computation} from './compute/computation';
83
83
  export type {
84
84
  TextureCubeFace,
85
85
  TextureImageData,
86
- TextureData,
87
86
  Texture1DData,
88
87
  Texture2DData,
89
88
  Texture3DData,
90
89
  TextureCubeData,
91
90
  TextureArrayData,
92
91
  TextureCubeArrayData
93
- } from './async-texture/async-texture';
92
+ } from './dynamic-texture/texture-data';
94
93
 
95
- export type {AsyncTextureProps} from './async-texture/async-texture';
96
- export {AsyncTexture} from './async-texture/async-texture';
94
+ export type {DynamicTextureProps} from './dynamic-texture/dynamic-texture';
95
+ export {DynamicTexture} from './dynamic-texture/dynamic-texture';
97
96
 
98
97
  export {PickingManager} from './modules/picking/picking-manager';
99
98
  export {picking as indexPicking} from './modules/picking/index-picking';
@@ -107,3 +106,9 @@ export {
107
106
  // DEPRECATED
108
107
 
109
108
  export {LegacyPickingManager} from './modules/picking/legacy-picking-manager';
109
+
110
+ import {DynamicTexture, type DynamicTextureProps} from './dynamic-texture/dynamic-texture';
111
+ /** @deprecated use DynamicTexture */
112
+ export const AsyncTexture = DynamicTexture;
113
+ /** @deprecated use DynamicTextureProps */
114
+ export type AsyncTextureProps = DynamicTextureProps;
@@ -31,7 +31,7 @@ import {
31
31
  } from '@luma.gl/core';
32
32
 
33
33
  import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
34
- import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
34
+ import {ShaderAssembler} from '@luma.gl/shadertools';
35
35
 
36
36
  import type {Geometry} from '../geometry/geometry';
37
37
  import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
@@ -44,7 +44,7 @@ import {BufferLayoutHelper} from '../utils/buffer-layout-helper';
44
44
  import {sortedBufferLayoutByShaderSourceLocations} from '../utils/buffer-layout-order';
45
45
  import {uid} from '../utils/uid';
46
46
  import {ShaderInputs} from '../shader-inputs';
47
- import {AsyncTexture} from '../async-texture/async-texture';
47
+ import {DynamicTexture} from '../dynamic-texture/dynamic-texture';
48
48
 
49
49
  const LOG_DRAW_PRIORITY = 2;
50
50
  const LOG_DRAW_TIMEOUT = 10000;
@@ -63,7 +63,7 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
63
63
  /** Shader inputs, used to generated uniform buffers and bindings */
64
64
  shaderInputs?: ShaderInputs;
65
65
  /** Bindings */
66
- bindings?: Record<string, Binding | AsyncTexture>;
66
+ bindings?: Record<string, Binding | DynamicTexture>;
67
67
  /** Parameters that are built into the pipeline */
68
68
  parameters?: RenderPipelineParameters;
69
69
 
@@ -103,12 +103,19 @@ export type ModelProps = Omit<RenderPipelineProps, 'vs' | 'fs' | 'bindings'> & {
103
103
  };
104
104
 
105
105
  /**
106
- * v9 Model API
107
- * A model
108
- * - automatically reuses pipelines (programs) when possible
109
- * - automatically rebuilds pipelines if necessary to accommodate changed settings
110
- * shadertools integration
111
- * - accepts modules and performs shader transpilation
106
+ * High level draw API for luma.gl.
107
+ *
108
+ * A `Model` encapsulates shaders, geometry attributes, bindings and render
109
+ * pipeline state into a single object. It automatically reuses and rebuilds
110
+ * pipelines as render parameters change and exposes convenient hooks for
111
+ * updating uniforms and attributes.
112
+ *
113
+ * Features:
114
+ * - Reuses and lazily recompiles {@link RenderPipeline | pipelines} as needed.
115
+ * - Integrates with `@luma.gl/shadertools` to assemble GLSL or WGSL from shader modules.
116
+ * - Manages geometry attributes and buffer bindings.
117
+ * - Accepts textures, samplers and uniform buffers as bindings, including `AsyncTexture`.
118
+ * - Provides detailed debug logging and optional shader source inspection.
112
119
  */
113
120
  export class Model {
114
121
  static defaultProps: Required<ModelProps> = {
@@ -141,16 +148,24 @@ export class Model {
141
148
  disableWarnings: undefined!
142
149
  };
143
150
 
151
+ /** Device that created this model */
144
152
  readonly device: Device;
153
+ /** Application provided identifier */
145
154
  readonly id: string;
155
+ /** WGSL shader source when using unified shader */
146
156
  // @ts-expect-error assigned in function called from constructor
147
157
  readonly source: string;
158
+ /** GLSL vertex shader source */
148
159
  // @ts-expect-error assigned in function called from constructor
149
160
  readonly vs: string;
161
+ /** GLSL fragment shader source */
150
162
  // @ts-expect-error assigned in function called from constructor
151
163
  readonly fs: string;
164
+ /** Factory used to create render pipelines */
152
165
  readonly pipelineFactory: PipelineFactory;
166
+ /** Factory used to create shaders */
153
167
  readonly shaderFactory: ShaderFactory;
168
+ /** User-supplied per-model data */
154
169
  userData: {[key: string]: any} = {};
155
170
 
156
171
  // Fixed properties (change can trigger pipeline rebuild)
@@ -179,7 +194,7 @@ export class Model {
179
194
  /** Constant-valued attributes */
180
195
  constantAttributes: Record<string, TypedArray> = {};
181
196
  /** Bindings (textures, samplers, uniform buffers) */
182
- bindings: Record<string, Binding | AsyncTexture> = {};
197
+ bindings: Record<string, Binding | DynamicTexture> = {};
183
198
 
184
199
  /**
185
200
  * VertexArray
@@ -262,7 +277,8 @@ export class Model {
262
277
  // @ts-expect-error
263
278
  this._getModuleUniforms = getUniforms;
264
279
  // Extract shader layout after modules have been added to WGSL source, to include any bindings added by modules
265
- this.props.shaderLayout ||= getShaderLayoutFromWGSL(this.source);
280
+ // @ts-expect-error Method on WebGPUDevice
281
+ this.props.shaderLayout ||= device.getShaderLayout(this.source);
266
282
  } else {
267
283
  // GLSL
268
284
  const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleGLSLShaderPair({
@@ -333,9 +349,6 @@ export class Model {
333
349
  if (props.transformFeedback) {
334
350
  this.transformFeedback = props.transformFeedback;
335
351
  }
336
-
337
- // Catch any access to non-standard props
338
- Object.seal(this);
339
352
  }
340
353
 
341
354
  destroy(): void {
@@ -372,6 +385,7 @@ export class Model {
372
385
  this._needsRedraw ||= reason;
373
386
  }
374
387
 
388
+ /** Update uniforms and pipeline state prior to drawing. */
375
389
  predraw(): void {
376
390
  // Update uniform buffers if needed
377
391
  this.updateShaderInputs();
@@ -379,6 +393,11 @@ export class Model {
379
393
  this.pipeline = this._updatePipeline();
380
394
  }
381
395
 
396
+ /**
397
+ * Issue one draw call.
398
+ * @param renderPass - render pass to draw into
399
+ * @returns `true` if the draw call was executed, `false` if resources were not ready.
400
+ */
382
401
  draw(renderPass: RenderPass): boolean {
383
402
  const loadingBinding = this._areBindingsLoading();
384
403
  if (loadingBinding) {
@@ -571,7 +590,7 @@ export class Model {
571
590
  /**
572
591
  * Sets bindings (textures, samplers, uniform buffers)
573
592
  */
574
- setBindings(bindings: Record<string, Binding | AsyncTexture>): void {
593
+ setBindings(bindings: Record<string, Binding | DynamicTexture>): void {
575
594
  Object.assign(this.bindings, bindings);
576
595
  this.setNeedsRedraw('bindings');
577
596
  }
@@ -678,7 +697,7 @@ export class Model {
678
697
  /** Check that bindings are loaded. Returns id of first binding that is still loading. */
679
698
  _areBindingsLoading(): string | false {
680
699
  for (const binding of Object.values(this.bindings)) {
681
- if (binding instanceof AsyncTexture && !binding.isReady) {
700
+ if (binding instanceof DynamicTexture && !binding.isReady) {
682
701
  return binding.id;
683
702
  }
684
703
  }
@@ -690,7 +709,7 @@ export class Model {
690
709
  const validBindings: Record<string, Binding> = {};
691
710
 
692
711
  for (const [name, binding] of Object.entries(this.bindings)) {
693
- if (binding instanceof AsyncTexture) {
712
+ if (binding instanceof DynamicTexture) {
694
713
  // Check that async textures are loaded
695
714
  if (binding.isReady) {
696
715
  validBindings[name] = binding.texture;
@@ -711,7 +730,7 @@ export class Model {
711
730
  timestamp = Math.max(timestamp, binding.texture.updateTimestamp);
712
731
  } else if (binding instanceof Buffer || binding instanceof Texture) {
713
732
  timestamp = Math.max(timestamp, binding.updateTimestamp);
714
- } else if (binding instanceof AsyncTexture) {
733
+ } else if (binding instanceof DynamicTexture) {
715
734
  timestamp = binding.texture
716
735
  ? Math.max(timestamp, binding.texture.updateTimestamp)
717
736
  : // The texture will become available in the future