@luma.gl/engine 9.3.0-alpha.4 → 9.3.0-alpha.8

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 (177) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +8 -4
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +73 -43
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/compute/computation.d.ts +3 -7
  11. package/dist/compute/computation.d.ts.map +1 -1
  12. package/dist/compute/computation.js +14 -12
  13. package/dist/compute/computation.js.map +1 -1
  14. package/dist/dist.dev.js +2310 -1638
  15. package/dist/dist.min.js +307 -360
  16. package/dist/dynamic-texture/dynamic-texture.d.ts +12 -5
  17. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -1
  18. package/dist/dynamic-texture/dynamic-texture.js +222 -55
  19. package/dist/dynamic-texture/dynamic-texture.js.map +1 -1
  20. package/dist/dynamic-texture/texture-data.d.ts +8 -1
  21. package/dist/dynamic-texture/texture-data.d.ts.map +1 -1
  22. package/dist/dynamic-texture/texture-data.js +27 -2
  23. package/dist/dynamic-texture/texture-data.js.map +1 -1
  24. package/dist/geometries/cone-geometry.d.ts +3 -1
  25. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  26. package/dist/geometries/cone-geometry.js.map +1 -1
  27. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  28. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  29. package/dist/geometries/cylinder-geometry.js.map +1 -1
  30. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  31. package/dist/geometry/gpu-geometry.js +8 -3
  32. package/dist/geometry/gpu-geometry.js.map +1 -1
  33. package/dist/index.cjs +2897 -2272
  34. package/dist/index.cjs.map +4 -4
  35. package/dist/index.d.ts +12 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +8 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/material/material-factory.d.ts +73 -0
  40. package/dist/material/material-factory.d.ts.map +1 -0
  41. package/dist/material/material-factory.js +111 -0
  42. package/dist/material/material-factory.js.map +1 -0
  43. package/dist/material/material.d.ts +84 -0
  44. package/dist/material/material.d.ts.map +1 -0
  45. package/dist/material/material.js +176 -0
  46. package/dist/material/material.js.map +1 -0
  47. package/dist/model/model.d.ts +17 -7
  48. package/dist/model/model.d.ts.map +1 -1
  49. package/dist/model/model.js +80 -34
  50. package/dist/model/model.js.map +1 -1
  51. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  52. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  53. package/dist/model/split-uniforms-and-bindings.js +2 -2
  54. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  55. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  56. package/dist/models/billboard-texture-model.js +10 -8
  57. package/dist/models/billboard-texture-model.js.map +1 -1
  58. package/dist/models/clip-space.js +7 -7
  59. package/dist/models/directional-light-model.d.ts +7 -0
  60. package/dist/models/directional-light-model.d.ts.map +1 -0
  61. package/dist/models/directional-light-model.js +23 -0
  62. package/dist/models/directional-light-model.js.map +1 -0
  63. package/dist/models/light-model-utils.d.ts +69 -0
  64. package/dist/models/light-model-utils.d.ts.map +1 -0
  65. package/dist/models/light-model-utils.js +395 -0
  66. package/dist/models/light-model-utils.js.map +1 -0
  67. package/dist/models/point-light-model.d.ts +7 -0
  68. package/dist/models/point-light-model.d.ts.map +1 -0
  69. package/dist/models/point-light-model.js +22 -0
  70. package/dist/models/point-light-model.js.map +1 -0
  71. package/dist/models/spot-light-model.d.ts +7 -0
  72. package/dist/models/spot-light-model.d.ts.map +1 -0
  73. package/dist/models/spot-light-model.js +23 -0
  74. package/dist/models/spot-light-model.js.map +1 -0
  75. package/dist/modules/picking/color-picking.d.ts +5 -9
  76. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  77. package/dist/modules/picking/color-picking.js +122 -115
  78. package/dist/modules/picking/color-picking.js.map +1 -1
  79. package/dist/modules/picking/index-picking.d.ts +2 -2
  80. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  81. package/dist/modules/picking/index-picking.js +36 -16
  82. package/dist/modules/picking/index-picking.js.map +1 -1
  83. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  84. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  85. package/dist/modules/picking/legacy-color-picking.js +7 -0
  86. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  87. package/dist/modules/picking/picking-manager.d.ts +29 -3
  88. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  89. package/dist/modules/picking/picking-manager.js +188 -41
  90. package/dist/modules/picking/picking-manager.js.map +1 -1
  91. package/dist/modules/picking/picking-uniforms.d.ts +12 -11
  92. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  93. package/dist/modules/picking/picking-uniforms.js +26 -13
  94. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  95. package/dist/modules/picking/picking.d.ts +25 -0
  96. package/dist/modules/picking/picking.d.ts.map +1 -0
  97. package/dist/modules/picking/picking.js +18 -0
  98. package/dist/modules/picking/picking.js.map +1 -0
  99. package/dist/passes/get-fragment-shader.js +11 -30
  100. package/dist/passes/get-fragment-shader.js.map +1 -1
  101. package/dist/passes/shader-pass-renderer.d.ts +0 -2
  102. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  103. package/dist/passes/shader-pass-renderer.js +4 -31
  104. package/dist/passes/shader-pass-renderer.js.map +1 -1
  105. package/dist/scenegraph/group-node.d.ts +5 -0
  106. package/dist/scenegraph/group-node.d.ts.map +1 -1
  107. package/dist/scenegraph/group-node.js +12 -0
  108. package/dist/scenegraph/group-node.js.map +1 -1
  109. package/dist/scenegraph/model-node.d.ts +2 -2
  110. package/dist/scenegraph/model-node.d.ts.map +1 -1
  111. package/dist/scenegraph/model-node.js.map +1 -1
  112. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  113. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  114. package/dist/scenegraph/scenegraph-node.js +23 -15
  115. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  116. package/dist/shader-inputs.d.ts +9 -7
  117. package/dist/shader-inputs.d.ts.map +1 -1
  118. package/dist/shader-inputs.js +84 -4
  119. package/dist/shader-inputs.js.map +1 -1
  120. package/dist/utils/shader-module-utils.d.ts +7 -0
  121. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  122. package/dist/utils/shader-module-utils.js +46 -0
  123. package/dist/utils/shader-module-utils.js.map +1 -0
  124. package/package.json +4 -4
  125. package/src/animation-loop/animation-loop.ts +78 -46
  126. package/src/animation-loop/make-animation-loop.ts +13 -5
  127. package/src/animation-loop/request-animation-frame.ts +32 -6
  128. package/src/compute/computation.ts +31 -17
  129. package/src/dynamic-texture/dynamic-texture.ts +301 -68
  130. package/src/dynamic-texture/texture-data.ts +39 -4
  131. package/src/geometries/cone-geometry.ts +6 -1
  132. package/src/geometries/cylinder-geometry.ts +5 -1
  133. package/src/geometry/gpu-geometry.ts +8 -3
  134. package/src/index.ts +29 -4
  135. package/src/material/material-factory.ts +157 -0
  136. package/src/material/material.ts +254 -0
  137. package/src/model/model.ts +122 -50
  138. package/src/model/split-uniforms-and-bindings.ts +8 -6
  139. package/src/models/billboard-texture-model.ts +10 -8
  140. package/src/models/clip-space.ts +7 -7
  141. package/src/models/directional-light-model.ts +32 -0
  142. package/src/models/light-model-utils.ts +587 -0
  143. package/src/models/point-light-model.ts +31 -0
  144. package/src/models/spot-light-model.ts +32 -0
  145. package/src/modules/picking/color-picking.ts +123 -122
  146. package/src/modules/picking/index-picking.ts +36 -16
  147. package/src/modules/picking/legacy-color-picking.ts +8 -0
  148. package/src/modules/picking/picking-manager.ts +252 -50
  149. package/src/modules/picking/picking-uniforms.ts +38 -23
  150. package/src/modules/picking/picking.ts +22 -0
  151. package/src/passes/get-fragment-shader.ts +11 -30
  152. package/src/passes/shader-pass-renderer.ts +4 -33
  153. package/src/scenegraph/group-node.ts +16 -0
  154. package/src/scenegraph/model-node.ts +2 -2
  155. package/src/scenegraph/scenegraph-node.ts +27 -16
  156. package/src/shader-inputs.ts +165 -15
  157. package/src/utils/shader-module-utils.ts +65 -0
  158. package/dist/dynamic-texture/mipmaps.d.ts +0 -6
  159. package/dist/dynamic-texture/mipmaps.d.ts.map +0 -1
  160. package/dist/dynamic-texture/mipmaps.js +0 -441
  161. package/dist/dynamic-texture/mipmaps.js.map +0 -1
  162. package/dist/factories/pipeline-factory.d.ts +0 -37
  163. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  164. package/dist/factories/pipeline-factory.js +0 -181
  165. package/dist/factories/pipeline-factory.js.map +0 -1
  166. package/dist/factories/shader-factory.d.ts +0 -22
  167. package/dist/factories/shader-factory.d.ts.map +0 -1
  168. package/dist/factories/shader-factory.js +0 -89
  169. package/dist/factories/shader-factory.js.map +0 -1
  170. package/dist/types.d.ts +0 -7
  171. package/dist/types.d.ts.map +0 -1
  172. package/dist/types.js +0 -5
  173. package/dist/types.js.map +0 -1
  174. package/src/dynamic-texture/mipmaps.ts +0 -517
  175. package/src/factories/pipeline-factory.ts +0 -225
  176. package/src/factories/shader-factory.ts +0 -105
  177. package/src/types.ts +0 -11
@@ -1,9 +1,16 @@
1
1
  // luma.gl, MIT license
2
2
  // Copyright (c) vis.gl contributors
3
3
 
4
- import type {TextureProps, SamplerProps, TextureView, Device} from '@luma.gl/core';
4
+ import type {
5
+ TextureProps,
6
+ SamplerProps,
7
+ TextureView,
8
+ Device,
9
+ TextureFormat,
10
+ TextureReadOptions
11
+ } from '@luma.gl/core';
5
12
 
6
- import {Texture, Sampler, log} from '@luma.gl/core';
13
+ import {Buffer, Texture, Sampler, log} from '@luma.gl/core';
7
14
 
8
15
  // import {loadImageBitmap} from '../application-utils/load-file';
9
16
  import {uid} from '../utils/uid';
@@ -26,10 +33,10 @@ import {
26
33
  type TextureArrayData,
27
34
  type TextureCubeArrayData,
28
35
  type TextureCubeData,
29
- type TextureImageData,
30
36
 
31
37
  // Helpers
32
38
  getTextureSizeFromData,
39
+ resolveTextureImageFormat,
33
40
  getTexture1DSubresources,
34
41
  getTexture2DSubresources,
35
42
  getTexture3DSubresources,
@@ -37,7 +44,6 @@ import {
37
44
  getTextureArraySubresources,
38
45
  getTextureCubeArraySubresources
39
46
  } from './texture-data';
40
- import {generateMipmap} from './mipmaps';
41
47
 
42
48
  /**
43
49
  * Properties for a dynamic texture
@@ -131,7 +137,7 @@ export class DynamicTexture {
131
137
  }
132
138
 
133
139
  /** @note Fire and forget; caller can await `ready` */
134
- async initAsync(originalPropsWithAsyncData: TextureDataAsyncProps): Promise<void> {
140
+ async initAsync(originalPropsWithAsyncData: DynamicTextureProps): Promise<void> {
135
141
  try {
136
142
  // TODO - Accept URL string for 2D: turn into ExternalImage promise
137
143
  // const dataProps =
@@ -141,6 +147,18 @@ export class DynamicTexture {
141
147
 
142
148
  const propsWithSyncData = await this._loadAllData(originalPropsWithAsyncData);
143
149
  this._checkNotDestroyed();
150
+ const subresources = propsWithSyncData.data
151
+ ? getTextureSubresources({
152
+ ...propsWithSyncData,
153
+ width: originalPropsWithAsyncData.width,
154
+ height: originalPropsWithAsyncData.height,
155
+ format: originalPropsWithAsyncData.format
156
+ })
157
+ : [];
158
+ const userProvidedFormat =
159
+ 'format' in originalPropsWithAsyncData && originalPropsWithAsyncData.format !== undefined;
160
+ const userProvidedUsage =
161
+ 'usage' in originalPropsWithAsyncData && originalPropsWithAsyncData.usage !== undefined;
144
162
 
145
163
  // Deduce size when not explicitly provided
146
164
  // TODO - what about depth?
@@ -162,15 +180,32 @@ export class DynamicTexture {
162
180
  throw new Error(`${this} size could not be determined or was zero`);
163
181
  }
164
182
 
183
+ // Normalize caller-provided subresources into one validated mip chain description.
184
+ const textureData = analyzeTextureSubresources(this.device, subresources, size, {
185
+ format: userProvidedFormat ? originalPropsWithAsyncData.format : undefined
186
+ });
187
+ const resolvedFormat = textureData.format ?? this.props.format;
188
+
165
189
  // Create a minimal TextureProps and validate via `satisfies`
166
190
  const baseTextureProps = {
167
191
  ...this.props,
168
192
  ...size,
193
+ format: resolvedFormat,
169
194
  mipLevels: 1, // temporary; updated below
170
195
  data: undefined
171
196
  } satisfies TextureProps;
172
197
 
173
- if (this.device.type === 'webgpu' && this.props.mipmaps) {
198
+ if (this.device.isTextureFormatCompressed(resolvedFormat) && !userProvidedUsage) {
199
+ baseTextureProps.usage = Texture.SAMPLE | Texture.COPY_DST;
200
+ }
201
+
202
+ // Explicit mip arrays take ownership of the mip chain; otherwise we may auto-generate it.
203
+ const shouldGenerateMipmaps =
204
+ this.props.mipmaps &&
205
+ !textureData.hasExplicitMipChain &&
206
+ !this.device.isTextureFormatCompressed(resolvedFormat);
207
+
208
+ if (this.device.type === 'webgpu' && shouldGenerateMipmaps) {
174
209
  const requiredUsage =
175
210
  this.props.dimension === '3d'
176
211
  ? Texture.SAMPLE | Texture.STORAGE | Texture.COPY_DST | Texture.COPY_SRC
@@ -180,8 +215,9 @@ export class DynamicTexture {
180
215
 
181
216
  // Compute mip levels (auto clamps to max)
182
217
  const maxMips = this.device.getMipLevelCount(baseTextureProps.width, baseTextureProps.height);
183
- const desired =
184
- this.props.mipLevels === 'auto'
218
+ const desired = textureData.hasExplicitMipChain
219
+ ? textureData.mipLevels
220
+ : this.props.mipLevels === 'auto'
185
221
  ? maxMips
186
222
  : Math.max(1, Math.min(maxMips, this.props.mipLevels ?? 1));
187
223
 
@@ -192,33 +228,15 @@ export class DynamicTexture {
192
228
  this._view = this.texture.view;
193
229
 
194
230
  // Upload data if provided
195
- if (propsWithSyncData.data) {
196
- switch (propsWithSyncData.dimension) {
197
- case '1d':
198
- this.setTexture1DData(propsWithSyncData.data);
199
- break;
200
- case '2d':
201
- this.setTexture2DData(propsWithSyncData.data);
202
- break;
203
- case '3d':
204
- this.setTexture3DData(propsWithSyncData.data);
205
- break;
206
- case '2d-array':
207
- this.setTextureArrayData(propsWithSyncData.data);
208
- break;
209
- case 'cube':
210
- this.setTextureCubeData(propsWithSyncData.data);
211
- break;
212
- case 'cube-array':
213
- this.setTextureCubeArrayData(propsWithSyncData.data);
214
- break;
215
- default: {
216
- throw new Error(`Unhandled dimension ${propsWithSyncData.dimension}`);
217
- }
218
- }
231
+ if (textureData.subresources.length) {
232
+ this._setTextureSubresources(textureData.subresources);
233
+ }
234
+
235
+ if (this.props.mipmaps && !textureData.hasExplicitMipChain && !shouldGenerateMipmaps) {
236
+ log.warn(`${this} skipping auto-generated mipmaps for compressed texture format`)();
219
237
  }
220
238
 
221
- if (this.props.mipmaps) {
239
+ if (shouldGenerateMipmaps) {
222
240
  this.generateMipmaps();
223
241
  }
224
242
 
@@ -229,7 +247,6 @@ export class DynamicTexture {
229
247
  } catch (e) {
230
248
  const err = e instanceof Error ? e : new Error(String(e));
231
249
  this.rejectReady(err);
232
- throw err;
233
250
  }
234
251
  }
235
252
 
@@ -247,7 +264,7 @@ export class DynamicTexture {
247
264
  if (this.device.type === 'webgl') {
248
265
  this.texture.generateMipmapsWebGL();
249
266
  } else if (this.device.type === 'webgpu') {
250
- generateMipmap(this.device, this.texture);
267
+ this.device.generateMipmapsWebGPU(this.texture);
251
268
  } else {
252
269
  log.warn(`${this} mipmaps not supported on ${this.device.type}`);
253
270
  }
@@ -261,6 +278,59 @@ export class DynamicTexture {
261
278
  this._sampler = s;
262
279
  }
263
280
 
281
+ /**
282
+ * Copies texture contents into a GPU buffer and waits until the copy is complete.
283
+ * The caller owns the returned buffer and must destroy it when finished.
284
+ */
285
+ async readBuffer(options: TextureReadOptions = {}): Promise<Buffer> {
286
+ if (!this.isReady) {
287
+ await this.ready;
288
+ }
289
+
290
+ const width = options.width ?? this.texture.width;
291
+ const height = options.height ?? this.texture.height;
292
+ const depthOrArrayLayers = options.depthOrArrayLayers ?? this.texture.depth;
293
+ const layout = this.texture.computeMemoryLayout({width, height, depthOrArrayLayers});
294
+
295
+ const buffer = this.device.createBuffer({
296
+ byteLength: layout.byteLength,
297
+ usage: Buffer.COPY_DST | Buffer.MAP_READ
298
+ });
299
+
300
+ this.texture.readBuffer(
301
+ {
302
+ ...options,
303
+ width,
304
+ height,
305
+ depthOrArrayLayers
306
+ },
307
+ buffer
308
+ );
309
+
310
+ const fence = this.device.createFence();
311
+ await fence.signaled;
312
+ fence.destroy();
313
+
314
+ return buffer;
315
+ }
316
+
317
+ /** Reads texture contents back to CPU memory. */
318
+ async readAsync(options: TextureReadOptions = {}): Promise<ArrayBuffer> {
319
+ if (!this.isReady) {
320
+ await this.ready;
321
+ }
322
+
323
+ const width = options.width ?? this.texture.width;
324
+ const height = options.height ?? this.texture.height;
325
+ const depthOrArrayLayers = options.depthOrArrayLayers ?? this.texture.depth;
326
+ const layout = this.texture.computeMemoryLayout({width, height, depthOrArrayLayers});
327
+
328
+ const buffer = await this.readBuffer(options);
329
+ const data = await buffer.readAsync(0, layout.byteLength);
330
+ buffer.destroy();
331
+ return data.buffer;
332
+ }
333
+
264
334
  /**
265
335
  * Resize by cloning the underlying immutable texture.
266
336
  * Does not copy contents; caller may need to re-upload and/or regenerate mips.
@@ -342,7 +412,7 @@ export class DynamicTexture {
342
412
  }
343
413
 
344
414
  /** Cube array: multiple cubes (faces×layers), each face may carry multiple mips */
345
- private setTextureCubeArrayData(data: TextureCubeArrayData): void {
415
+ setTextureCubeArrayData(data: TextureCubeArrayData): void {
346
416
  if (this.texture.props.dimension !== 'cube-array') {
347
417
  throw new Error(`${this} is not cube-array`);
348
418
  }
@@ -367,10 +437,13 @@ export class DynamicTexture {
367
437
  this.texture.copyExternalImage({image, z, mipLevel, flipY});
368
438
  break;
369
439
  case 'texture-data':
370
- const {data} = subresource;
371
- // TODO - we are throwing away some of the info in data.
372
- // Did we not need it in the first place? Can we use it to validate?
373
- this.texture.writeData(getAlignedUploadData(this.texture, data), {
440
+ const {data, textureFormat} = subresource;
441
+ if (textureFormat && textureFormat !== this.texture.format) {
442
+ throw new Error(
443
+ `${this} mip level ${mipLevel} uses format "${textureFormat}" but texture format is "${this.texture.format}"`
444
+ );
445
+ }
446
+ this.texture.writeData(data.data, {
374
447
  x: 0,
375
448
  y: 0,
376
449
  z,
@@ -415,36 +488,196 @@ export class DynamicTexture {
415
488
  };
416
489
  }
417
490
 
418
- function getAlignedUploadData(
419
- texture: DynamicTexture['texture'],
420
- data: TextureImageData
421
- ): ArrayBuffer | Uint8Array {
422
- const {width, height, data: uploadData} = data;
423
- const {bytesPerPixel} = texture.device.getTextureFormatInfo(texture.format);
424
- const bytesPerRow = width * bytesPerPixel;
425
- const alignedBytesPerRow = Math.ceil(bytesPerRow / texture.byteAlignment) * texture.byteAlignment;
426
-
427
- if (alignedBytesPerRow === bytesPerRow) {
428
- return uploadData;
429
- }
430
-
431
- const sourceBytes = new Uint8Array(
432
- uploadData.buffer,
433
- uploadData.byteOffset,
434
- uploadData.byteLength
435
- );
436
- const paddedBytes = new Uint8Array(alignedBytesPerRow * height);
437
-
438
- for (let row = 0; row < height; row++) {
439
- const sourceOffset = row * bytesPerRow;
440
- const destinationOffset = row * alignedBytesPerRow;
441
- paddedBytes.set(
442
- sourceBytes.subarray(sourceOffset, sourceOffset + bytesPerRow),
443
- destinationOffset
491
+ type TextureSubresourceAnalysis = {
492
+ readonly subresources: TextureSubresource[];
493
+ readonly mipLevels: number;
494
+ readonly format?: TextureFormat;
495
+ readonly hasExplicitMipChain: boolean;
496
+ };
497
+
498
+ // Flatten dimension-specific texture data into one list of uploadable subresources.
499
+ function getTextureSubresources(
500
+ props: TextureDataProps & Partial<Pick<TextureProps, 'width' | 'height' | 'format'>>
501
+ ): TextureSubresource[] {
502
+ if (!props.data) {
503
+ return [];
504
+ }
505
+
506
+ const baseLevelSize =
507
+ props.width && props.height ? {width: props.width, height: props.height} : undefined;
508
+ const textureFormat = 'format' in props ? props.format : undefined;
509
+
510
+ switch (props.dimension) {
511
+ case '1d':
512
+ return getTexture1DSubresources(props.data);
513
+ case '2d':
514
+ return getTexture2DSubresources(0, props.data, baseLevelSize, textureFormat);
515
+ case '3d':
516
+ return getTexture3DSubresources(props.data);
517
+ case '2d-array':
518
+ return getTextureArraySubresources(props.data);
519
+ case 'cube':
520
+ return getTextureCubeSubresources(props.data);
521
+ case 'cube-array':
522
+ return getTextureCubeArraySubresources(props.data);
523
+ default:
524
+ throw new Error(`Unhandled dimension ${(props as TextureDataProps).dimension}`);
525
+ }
526
+ }
527
+
528
+ // Resolve a consistent texture format and the longest mip chain valid across all slices.
529
+ function analyzeTextureSubresources(
530
+ device: Device,
531
+ subresources: TextureSubresource[],
532
+ size: {width: number; height: number},
533
+ options: {format?: TextureFormat}
534
+ ): TextureSubresourceAnalysis {
535
+ if (subresources.length === 0) {
536
+ return {
537
+ subresources,
538
+ mipLevels: 1,
539
+ format: options.format,
540
+ hasExplicitMipChain: false
541
+ };
542
+ }
543
+
544
+ const groupedSubresources = new Map<number, TextureSubresource[]>();
545
+ for (const subresource of subresources) {
546
+ const group = groupedSubresources.get(subresource.z) ?? [];
547
+ group.push(subresource);
548
+ groupedSubresources.set(subresource.z, group);
549
+ }
550
+
551
+ const hasExplicitMipChain = subresources.some(subresource => subresource.mipLevel > 0);
552
+ let resolvedFormat = options.format;
553
+ let resolvedMipLevels = Number.POSITIVE_INFINITY;
554
+ const validSubresources: TextureSubresource[] = [];
555
+
556
+ for (const [z, sliceSubresources] of groupedSubresources) {
557
+ // Validate each slice independently, then keep only the mip levels that are valid everywhere.
558
+ const sortedSubresources = [...sliceSubresources].sort(
559
+ (left, right) => left.mipLevel - right.mipLevel
444
560
  );
561
+ const baseLevel = sortedSubresources[0];
562
+ if (!baseLevel || baseLevel.mipLevel !== 0) {
563
+ throw new Error(`DynamicTexture: slice ${z} is missing mip level 0`);
564
+ }
565
+
566
+ const baseSize = getTextureSubresourceSize(device, baseLevel);
567
+ if (baseSize.width !== size.width || baseSize.height !== size.height) {
568
+ throw new Error(
569
+ `DynamicTexture: slice ${z} base level dimensions ${baseSize.width}x${baseSize.height} do not match expected ${size.width}x${size.height}`
570
+ );
571
+ }
572
+
573
+ const baseFormat = getTextureSubresourceFormat(baseLevel);
574
+ if (baseFormat) {
575
+ if (resolvedFormat && resolvedFormat !== baseFormat) {
576
+ throw new Error(
577
+ `DynamicTexture: slice ${z} base level format "${baseFormat}" does not match texture format "${resolvedFormat}"`
578
+ );
579
+ }
580
+ resolvedFormat = baseFormat;
581
+ }
582
+
583
+ const mipLevelLimit =
584
+ resolvedFormat && device.isTextureFormatCompressed(resolvedFormat)
585
+ ? // Block-compressed formats cannot have mips smaller than a single compression block.
586
+ getMaxCompressedMipLevels(device, baseSize.width, baseSize.height, resolvedFormat)
587
+ : device.getMipLevelCount(baseSize.width, baseSize.height);
588
+
589
+ let validMipLevelsForSlice = 0;
590
+ for (
591
+ let expectedMipLevel = 0;
592
+ expectedMipLevel < sortedSubresources.length;
593
+ expectedMipLevel++
594
+ ) {
595
+ const subresource = sortedSubresources[expectedMipLevel];
596
+ // Stop at the first gap so callers can provide extra trailing data without breaking creation.
597
+ if (!subresource || subresource.mipLevel !== expectedMipLevel) {
598
+ break;
599
+ }
600
+ if (expectedMipLevel >= mipLevelLimit) {
601
+ break;
602
+ }
603
+
604
+ const subresourceSize = getTextureSubresourceSize(device, subresource);
605
+ const expectedWidth = Math.max(1, baseSize.width >> expectedMipLevel);
606
+ const expectedHeight = Math.max(1, baseSize.height >> expectedMipLevel);
607
+ if (subresourceSize.width !== expectedWidth || subresourceSize.height !== expectedHeight) {
608
+ break;
609
+ }
610
+
611
+ const subresourceFormat = getTextureSubresourceFormat(subresource);
612
+ if (subresourceFormat) {
613
+ if (!resolvedFormat) {
614
+ resolvedFormat = subresourceFormat;
615
+ }
616
+ // Later mip levels must stay on the same format as the validated base level.
617
+ if (subresourceFormat !== resolvedFormat) {
618
+ break;
619
+ }
620
+ }
621
+
622
+ validMipLevelsForSlice++;
623
+ validSubresources.push(subresource);
624
+ }
625
+
626
+ resolvedMipLevels = Math.min(resolvedMipLevels, validMipLevelsForSlice);
445
627
  }
446
628
 
447
- return paddedBytes;
629
+ const mipLevels = Number.isFinite(resolvedMipLevels) ? Math.max(1, resolvedMipLevels) : 1;
630
+
631
+ return {
632
+ // Keep every slice trimmed to the same mip count so the texture shape stays internally consistent.
633
+ subresources: validSubresources.filter(subresource => subresource.mipLevel < mipLevels),
634
+ mipLevels,
635
+ format: resolvedFormat,
636
+ hasExplicitMipChain
637
+ };
638
+ }
639
+
640
+ // Read the per-level format using the transitional textureFormat -> format fallback rules.
641
+ function getTextureSubresourceFormat(subresource: TextureSubresource): TextureFormat | undefined {
642
+ if (subresource.type !== 'texture-data') {
643
+ return undefined;
644
+ }
645
+ return subresource.textureFormat ?? resolveTextureImageFormat(subresource.data);
646
+ }
647
+
648
+ // Resolve dimensions from either raw bytes or external-image subresources.
649
+ function getTextureSubresourceSize(
650
+ device: Device,
651
+ subresource: TextureSubresource
652
+ ): {width: number; height: number} {
653
+ switch (subresource.type) {
654
+ case 'external-image':
655
+ return device.getExternalImageSize(subresource.image);
656
+ case 'texture-data':
657
+ return {width: subresource.data.width, height: subresource.data.height};
658
+ default:
659
+ throw new Error('Unsupported texture subresource');
660
+ }
661
+ }
662
+
663
+ // Count the mip levels that stay at or above one compression block in each dimension.
664
+ function getMaxCompressedMipLevels(
665
+ device: Device,
666
+ baseWidth: number,
667
+ baseHeight: number,
668
+ format: TextureFormat
669
+ ): number {
670
+ const {blockWidth = 1, blockHeight = 1} = device.getTextureFormatInfo(format);
671
+ let mipLevels = 1;
672
+ for (let mipLevel = 1; ; mipLevel++) {
673
+ const width = Math.max(1, baseWidth >> mipLevel);
674
+ const height = Math.max(1, baseHeight >> mipLevel);
675
+ if (width < blockWidth || height < blockHeight) {
676
+ break;
677
+ }
678
+ mipLevels++;
679
+ }
680
+ return mipLevels;
448
681
  }
449
682
 
450
683
  // HELPERS
@@ -457,7 +690,7 @@ async function awaitAllPromises(x: any): Promise<any> {
457
690
  }
458
691
  if (x && typeof x === 'object' && x.constructor === Object) {
459
692
  const object: Record<string, any> = x;
460
- const values = await Promise.all(Object.values(object));
693
+ const values = await Promise.all(Object.values(object).map(awaitAllPromises));
461
694
  const keys = Object.keys(object);
462
695
  const resolvedObject: Record<string, any> = {};
463
696
  for (let i = 0; i < keys.length; i++) {
@@ -9,6 +9,8 @@ export type TextureImageSource = ExternalImage;
9
9
  * additional optional fields can describe compressed texture data.
10
10
  */
11
11
  export type TextureImageData = {
12
+ /** Preferred WebGPU style format string. */
13
+ textureFormat?: TextureFormat;
12
14
  /** WebGPU style format string. Defaults to 'rgba8unorm' */
13
15
  format?: TextureFormat;
14
16
  /** Typed Array with the bytes of the image. @note beware row byte alignment requirements */
@@ -104,6 +106,7 @@ export type TextureSubresource = {
104
106
  | {
105
107
  type: 'texture-data';
106
108
  data: TextureImageData;
109
+ textureFormat?: TextureFormat;
107
110
  }
108
111
  );
109
112
 
@@ -173,8 +176,8 @@ function getTextureMipLevelSize(data: TextureMipLevelData): {width: number; heig
173
176
  throw new Error('Unsupported mip-level data');
174
177
  }
175
178
 
176
- /** Type guard: is a mip-level `TextureImageData` (vs ExternalImage) */
177
- function isTextureImageData(data: TextureMipLevelData): data is TextureImageData {
179
+ /** Type guard: is a mip-level `TextureImageData` (vs ExternalImage or bare typed array) */
180
+ function isTextureImageData(data: unknown): data is TextureImageData {
178
181
  return (
179
182
  typeof data === 'object' &&
180
183
  data !== null &&
@@ -184,6 +187,20 @@ function isTextureImageData(data: TextureMipLevelData): data is TextureImageData
184
187
  );
185
188
  }
186
189
 
190
+ function isTypedArrayMipLevelData(data: unknown): data is TypedArray {
191
+ return ArrayBuffer.isView(data);
192
+ }
193
+
194
+ export function resolveTextureImageFormat(data: TextureImageData): TextureFormat | undefined {
195
+ const {textureFormat, format} = data;
196
+ if (textureFormat && format && textureFormat !== format) {
197
+ throw new Error(
198
+ `Conflicting texture formats "${textureFormat}" and "${format}" provided for the same mip level`
199
+ );
200
+ }
201
+ return textureFormat ?? format;
202
+ }
203
+
187
204
  /** Resolve size for a single mip-level datum */
188
205
  // function getTextureMipLevelSizeFromData(data: TextureMipLevelData): {
189
206
  // width: number;
@@ -222,14 +239,18 @@ export function getTexture1DSubresources(data: Texture1DData): TextureSubresourc
222
239
  }
223
240
 
224
241
  /** Normalize 2D layer payload into an array of mip-level items */
225
- function _normalizeTexture2DData(data: Texture2DData): (TextureImageData | ExternalImage)[] {
242
+ function _normalizeTexture2DData(
243
+ data: Texture2DData
244
+ ): (TextureImageData | ExternalImage | TypedArray)[] {
226
245
  return Array.isArray(data) ? data : [data];
227
246
  }
228
247
 
229
248
  /** Experimental: Set multiple mip levels (2D), optionally at `z` (depth/array index) */
230
249
  export function getTexture2DSubresources(
231
250
  slice: number,
232
- lodData: Texture2DData
251
+ lodData: Texture2DData,
252
+ baseLevelSize?: {width: number; height: number},
253
+ textureFormat?: TextureFormat
233
254
  ): TextureSubresource[] {
234
255
  const lodArray = _normalizeTexture2DData(lodData);
235
256
  const z = slice;
@@ -249,6 +270,20 @@ export function getTexture2DSubresources(
249
270
  subresources.push({
250
271
  type: 'texture-data',
251
272
  data: imageData,
273
+ textureFormat: resolveTextureImageFormat(imageData),
274
+ z,
275
+ mipLevel
276
+ });
277
+ } else if (isTypedArrayMipLevelData(imageData) && baseLevelSize) {
278
+ subresources.push({
279
+ type: 'texture-data',
280
+ data: {
281
+ data: imageData,
282
+ width: Math.max(1, baseLevelSize.width >> mipLevel),
283
+ height: Math.max(1, baseLevelSize.height >> mipLevel),
284
+ ...(textureFormat ? {format: textureFormat} : {})
285
+ },
286
+ textureFormat,
252
287
  z,
253
288
  mipLevel
254
289
  });
@@ -2,13 +2,18 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {TruncatedConeGeometryProps} from './truncated-cone-geometry';
5
6
  import {TruncatedConeGeometry} from './truncated-cone-geometry';
6
7
  import {uid} from '../utils/uid';
7
8
 
8
- export type ConeGeometryProps = {
9
+ export type ConeGeometryProps = Omit<
10
+ TruncatedConeGeometryProps,
11
+ 'topRadius' | 'bottomRadius' | 'topCap' | 'bottomCap'
12
+ > & {
9
13
  id?: string;
10
14
  radius?: number;
11
15
  cap?: boolean;
16
+ attributes?: any;
12
17
  };
13
18
 
14
19
  export class ConeGeometry extends TruncatedConeGeometry {
@@ -2,10 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import type {TruncatedConeGeometryProps} from './truncated-cone-geometry';
5
6
  import {TruncatedConeGeometry} from './truncated-cone-geometry';
6
7
  import {uid} from '../utils/uid';
7
8
 
8
- export type CylinderGeometryProps = {
9
+ export type CylinderGeometryProps = Omit<
10
+ TruncatedConeGeometryProps,
11
+ 'topRadius' | 'bottomRadius'
12
+ > & {
9
13
  id?: string;
10
14
  radius?: number;
11
15
  attributes?: any;
@@ -3,7 +3,7 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {PrimitiveTopology, BufferLayout} from '@luma.gl/core';
6
- import {Device, Buffer, getVertexFormatFromAttribute} from '@luma.gl/core';
6
+ import {Device, Buffer, vertexFormatDecoder} from '@luma.gl/core';
7
7
  import type {Geometry} from '../geometry/geometry';
8
8
  import {uid} from '../utils/uid';
9
9
 
@@ -127,8 +127,13 @@ export function getAttributeBuffersFromGeometry(
127
127
  id: `${attributeName}-buffer`
128
128
  });
129
129
  const {value, size, normalized} = attribute;
130
- // @ts-expect-error
131
- bufferLayout.push({name, format: getVertexFormatFromAttribute(value, size, normalized)});
130
+ if (size === undefined) {
131
+ throw new Error(`Attribute ${attributeName} is missing a size`);
132
+ }
133
+ bufferLayout.push({
134
+ name,
135
+ format: vertexFormatDecoder.getVertexFormatFromAttribute(value, size, normalized)
136
+ });
132
137
  }
133
138
  }
134
139