@luma.gl/engine 9.1.9 → 9.2.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 (102) hide show
  1. package/README.md +5 -0
  2. package/dist/animation-loop/animation-loop.d.ts +12 -12
  3. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  4. package/dist/animation-loop/animation-loop.js +26 -62
  5. package/dist/animation-loop/animation-loop.js.map +1 -1
  6. package/dist/animation-loop/animation-props.d.ts +3 -4
  7. package/dist/animation-loop/animation-props.d.ts.map +1 -1
  8. package/dist/animation-loop/make-animation-loop.d.ts +4 -1
  9. package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
  10. package/dist/animation-loop/make-animation-loop.js +39 -7
  11. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  12. package/dist/async-texture/async-texture.d.ts +106 -2
  13. package/dist/async-texture/async-texture.d.ts.map +1 -1
  14. package/dist/async-texture/async-texture.js +281 -13
  15. package/dist/async-texture/async-texture.js.map +1 -1
  16. package/dist/compute/computation.d.ts +1 -1
  17. package/dist/compute/computation.d.ts.map +1 -1
  18. package/dist/compute/computation.js +2 -2
  19. package/dist/compute/computation.js.map +1 -1
  20. package/dist/compute/swap.d.ts.map +1 -1
  21. package/dist/compute/swap.js +6 -2
  22. package/dist/compute/swap.js.map +1 -1
  23. package/dist/compute/texture-transform.d.ts.map +1 -1
  24. package/dist/compute/texture-transform.js +4 -2
  25. package/dist/compute/texture-transform.js.map +1 -1
  26. package/dist/debug/copy-texture-to-image.d.ts +23 -1
  27. package/dist/debug/copy-texture-to-image.d.ts.map +1 -1
  28. package/dist/debug/copy-texture-to-image.js +37 -1
  29. package/dist/debug/copy-texture-to-image.js.map +1 -1
  30. package/dist/dist.dev.js +566 -232
  31. package/dist/dist.min.js +26 -26
  32. package/dist/factories/pipeline-factory.d.ts +11 -1
  33. package/dist/factories/pipeline-factory.d.ts.map +1 -1
  34. package/dist/factories/pipeline-factory.js +107 -25
  35. package/dist/factories/pipeline-factory.js.map +1 -1
  36. package/dist/factories/shader-factory.d.ts +5 -1
  37. package/dist/factories/shader-factory.d.ts.map +1 -1
  38. package/dist/factories/shader-factory.js +40 -6
  39. package/dist/factories/shader-factory.js.map +1 -1
  40. package/dist/geometries/cube-geometry.d.ts +3 -3
  41. package/dist/geometries/cube-geometry.d.ts.map +1 -1
  42. package/dist/geometry/geometry.d.ts.map +1 -1
  43. package/dist/geometry/geometry.js +3 -2
  44. package/dist/geometry/geometry.js.map +1 -1
  45. package/dist/index.cjs +581 -251
  46. package/dist/index.cjs.map +4 -4
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/model/model.d.ts +4 -25
  51. package/dist/model/model.d.ts.map +1 -1
  52. package/dist/model/model.js +26 -71
  53. package/dist/model/model.js.map +1 -1
  54. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  55. package/dist/models/billboard-texture-model.js +6 -4
  56. package/dist/models/billboard-texture-model.js.map +1 -1
  57. package/dist/modules/picking/legacy-picking-manager.d.ts +1 -1
  58. package/dist/modules/picking/legacy-picking-manager.d.ts.map +1 -1
  59. package/dist/modules/picking/legacy-picking-manager.js +1 -1
  60. package/dist/modules/picking/legacy-picking-manager.js.map +1 -1
  61. package/dist/modules/picking/picking-manager.d.ts +2 -2
  62. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  63. package/dist/modules/picking/picking-manager.js +2 -2
  64. package/dist/modules/picking/picking-manager.js.map +1 -1
  65. package/dist/passes/get-fragment-shader.js +2 -2
  66. package/dist/passes/shader-pass-renderer.d.ts +4 -4
  67. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  68. package/dist/passes/shader-pass-renderer.js +15 -5
  69. package/dist/passes/shader-pass-renderer.js.map +1 -1
  70. package/dist/shader-inputs.js +1 -1
  71. package/dist/shader-inputs.js.map +1 -1
  72. package/dist/utils/buffer-layout-helper.d.ts +12 -0
  73. package/dist/utils/buffer-layout-helper.d.ts.map +1 -0
  74. package/dist/utils/buffer-layout-helper.js +41 -0
  75. package/dist/utils/buffer-layout-helper.js.map +1 -0
  76. package/dist/utils/buffer-layout-order.d.ts +3 -0
  77. package/dist/utils/buffer-layout-order.d.ts.map +1 -0
  78. package/dist/utils/buffer-layout-order.js +16 -0
  79. package/dist/utils/buffer-layout-order.js.map +1 -0
  80. package/package.json +4 -4
  81. package/src/animation-loop/animation-loop.ts +31 -71
  82. package/src/animation-loop/animation-props.ts +3 -5
  83. package/src/animation-loop/make-animation-loop.ts +41 -9
  84. package/src/async-texture/async-texture.ts +386 -23
  85. package/src/async-texture/texture-setters.ts.disabled +296 -0
  86. package/src/compute/computation.ts +3 -3
  87. package/src/compute/swap.ts +7 -2
  88. package/src/compute/texture-transform.ts +4 -2
  89. package/src/debug/copy-texture-to-image.ts +52 -2
  90. package/src/factories/pipeline-factory.ts +122 -26
  91. package/src/factories/shader-factory.ts +43 -7
  92. package/src/geometry/geometry.ts +3 -2
  93. package/src/index.ts +12 -0
  94. package/src/model/model.ts +31 -86
  95. package/src/models/billboard-texture-model.ts +6 -4
  96. package/src/modules/picking/legacy-picking-manager.ts +2 -2
  97. package/src/modules/picking/picking-manager.ts +3 -3
  98. package/src/passes/get-fragment-shader.ts +2 -2
  99. package/src/passes/shader-pass-renderer.ts +18 -8
  100. package/src/shader-inputs.ts +1 -1
  101. package/src/utils/buffer-layout-helper.ts +51 -0
  102. package/src/utils/buffer-layout-order.ts +26 -0
@@ -0,0 +1,296 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Texture, TypedArray, TextureFormat, ExternalImage} from '@luma.gl/core';
6
+ import {isExternalImage, getExternalImageSize} from '@luma.gl/core';
7
+
8
+ /** Names of cube texture faces */
9
+ export type TextureCubeFace = '+X' | '-X' | '+Y' | '-Y' | '+Z' | '-Z';
10
+
11
+ /**
12
+ * One mip level
13
+ * Basic data structure is similar to `ImageData`
14
+ * additional optional fields can describe compressed texture data.
15
+ */
16
+ export type TextureLevelData = {
17
+ /** WebGPU style format string. Defaults to 'rgba8unorm' */
18
+ format?: TextureFormat;
19
+ data: TypedArray;
20
+ width: number;
21
+ height: number;
22
+
23
+ compressed?: boolean;
24
+ byteLength?: number;
25
+ hasAlpha?: boolean;
26
+ };
27
+
28
+ export type TextureLevelSource = TextureLevelData | ExternalImage;
29
+
30
+ /** Texture data can be one or more mip levels */
31
+ export type TextureData = TextureLevelData | ExternalImage | (TextureLevelData | ExternalImage)[];
32
+
33
+ /** @todo - define what data type is supported for 1D textures */
34
+ export type Texture1DData = TypedArray | TextureLevelData;
35
+
36
+ /** Texture data can be one or more mip levels */
37
+ export type Texture2DData =
38
+ | TypedArray
39
+ | TextureLevelData
40
+ | ExternalImage
41
+ | (TextureLevelData | ExternalImage)[];
42
+
43
+ /** Array of textures */
44
+ export type Texture3DData = TypedArray | TextureData[];
45
+
46
+ /** 6 face textures */
47
+ export type TextureCubeData = Record<TextureCubeFace, Texture2DData>;
48
+
49
+ /** Array of textures */
50
+ export type TextureArrayData = TextureData[];
51
+
52
+ /** Array of 6 face textures */
53
+ export type TextureCubeArrayData = Record<TextureCubeFace, TextureData>[];
54
+
55
+ export type TextureDataProps =
56
+ | Texture1DProps
57
+ | Texture2DProps
58
+ | Texture3DProps
59
+ | TextureArrayProps
60
+ | TextureCubeProps
61
+ | TextureCubeArrayProps;
62
+
63
+ export type Texture1DProps = {dimension: '1d'; data?: Texture1DData | null};
64
+ export type Texture2DProps = {dimension?: '2d'; data?: Texture2DData | null};
65
+ export type Texture3DProps = {dimension: '3d'; data?: Texture3DData | null};
66
+ export type TextureArrayProps = {dimension: '2d-array'; data?: TextureArrayData | null};
67
+ export type TextureCubeProps = {dimension: 'cube'; data?: TextureCubeData | null};
68
+ export type TextureCubeArrayProps = {dimension: 'cube-array'; data: TextureCubeArrayData | null};
69
+
70
+ export const CubeFaces: TextureCubeFace[] = ['+X', '-X', '+Y', '-Y', '+Z', '-Z'];
71
+
72
+ /** Check if texture data is a typed array */
73
+ export function isTextureLevelData(data: TextureData): data is TextureLevelData {
74
+ const typedArray = (data as TextureLevelData)?.data;
75
+ return ArrayBuffer.isView(typedArray);
76
+ }
77
+
78
+ /** Get the size of the texture described by the provided TextureData */
79
+ export function getTextureDataSize(
80
+ data: TextureData | TextureCubeData | TextureArrayData | TextureCubeArrayData | TypedArray
81
+ ): {width: number; height: number} | null {
82
+ if (!data) {
83
+ return null;
84
+ }
85
+ if (ArrayBuffer.isView(data)) {
86
+ return null;
87
+ }
88
+ // Recurse into arrays (array of miplevels)
89
+ if (Array.isArray(data)) {
90
+ return getTextureDataSize(data[0]);
91
+ }
92
+ if (isExternalImage(data)) {
93
+ return getExternalImageSize(data);
94
+ }
95
+ if (data && typeof data === 'object' && data.constructor === Object) {
96
+ const textureDataArray = Object.values(data) as Texture2DData[];
97
+ const untypedData = textureDataArray[0] as any;
98
+ return {width: untypedData.width, height: untypedData.height};
99
+ }
100
+ throw new Error('texture size deduction failed');
101
+ }
102
+
103
+ /**
104
+ * Normalize TextureData to an array of TextureLevelData / ExternalImages
105
+ * @param data
106
+ * @param options
107
+ * @returns array of TextureLevelData / ExternalImages
108
+ */
109
+ export function normalizeTextureData(
110
+ data: Texture2DData,
111
+ options: {width: number; height: number; depth: number}
112
+ ): (TextureLevelData | ExternalImage)[] {
113
+ let lodArray: (TextureLevelData | ExternalImage)[];
114
+ if (ArrayBuffer.isView(data)) {
115
+ lodArray = [
116
+ {
117
+ // ts-expect-error does data really need to be Uint8ClampedArray?
118
+ data,
119
+ width: options.width,
120
+ height: options.height
121
+ // depth: options.depth
122
+ }
123
+ ];
124
+ } else if (!Array.isArray(data)) {
125
+ lodArray = [data];
126
+ } else {
127
+ lodArray = data;
128
+ }
129
+ return lodArray;
130
+ }
131
+
132
+ /** Convert luma.gl cubemap face constants to depth index */
133
+ export function getCubeFaceDepth(face: TextureCubeFace): number {
134
+ // prettier-ignore
135
+ switch (face) {
136
+ case '+X': return 0;
137
+ case '-X': return 1;
138
+ case '+Y': return 2;
139
+ case '-Y': return 3;
140
+ case '+Z': return 4;
141
+ case '-Z': return 5;
142
+ default: throw new Error(face);
143
+ }
144
+ }
145
+
146
+ // EXPERIMENTAL
147
+
148
+ /** Set multiple mip levels */
149
+ export function setTexture1DData(texture: Texture, data: Texture1DData): void {
150
+ throw new Error('setTexture1DData not supported in WebGL.');
151
+ }
152
+
153
+ /** Set multiple mip levels */
154
+ export function setTexture2DData(texture: Texture, lodData: Texture2DData, depth = 0): void {
155
+ this.bind();
156
+
157
+ const lodArray = Texture.normalizeTextureData(lodData, this);
158
+
159
+ // If the user provides multiple LODs, then automatic mipmap
160
+ // generation generateMipmap() should be disabled to avoid overwriting them.
161
+ if (lodArray.length > 1 && this.props.mipmaps !== false) {
162
+ log.warn(`Texture ${this.id} mipmap and multiple LODs.`)();
163
+ }
164
+
165
+ for (let lodLevel = 0; lodLevel < lodArray.length; lodLevel++) {
166
+ const imageData = lodArray[lodLevel];
167
+ this._setMipLevel(depth, lodLevel, imageData);
168
+ }
169
+
170
+ this.unbind();
171
+ }
172
+
173
+ /**
174
+ * Sets 3D texture data: multiple depth slices, multiple mip levels
175
+ * @param data
176
+ */
177
+ export function setTexture3DData(texture: Texture, data: Texture3DData): void {
178
+ if (this.props.dimension !== '3d') {
179
+ throw new Error(this.id);
180
+ }
181
+ if (ArrayBuffer.isView(data)) {
182
+ this.bind();
183
+ copyCPUDataToMipLevel(this.device.gl, data, this);
184
+ this.unbind();
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Set Cube texture data, multiple faces, multiple mip levels
190
+ * @todo - could support TextureCubeArray with depth
191
+ * @param data
192
+ * @param index
193
+ */
194
+ export function setTextureCubeData(texture: Texture, data: TextureCubeData): void {
195
+ if (this.props.dimension !== 'cube') {
196
+ throw new Error(this.id);
197
+ }
198
+ for (const face of Texture.CubeFaces) {
199
+ this.setTextureCubeFaceData(data[face], face);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Sets texture array data, multiple levels, multiple depth slices
205
+ * @param data
206
+ */
207
+ export function setTextureArrayData(texture: Texture, data: TextureArrayData): void {
208
+ if (this.props.dimension !== '2d-array') {
209
+ throw new Error(this.id);
210
+ }
211
+ throw new Error('setTextureArrayData not implemented.');
212
+ }
213
+
214
+ /**
215
+ * Sets texture cube array, multiple faces, multiple levels, multiple mip levels
216
+ * @param data
217
+ */
218
+ export function setTextureCubeArrayData(texture: Texture, data: TextureCubeArrayData): void {
219
+ throw new Error('setTextureCubeArrayData not supported in WebGL2.');
220
+ }
221
+
222
+ export function setTextureCubeFaceData(
223
+ texture: Texture,
224
+ lodData: Texture2DData,
225
+ face: TextureCubeFace,
226
+ depth: number = 0
227
+ ): void {
228
+ // assert(this.props.dimension === 'cube');
229
+
230
+ // If the user provides multiple LODs, then automatic mipmap
231
+ // generation generateMipmap() should be disabled to avoid overwriting them.
232
+ if (Array.isArray(lodData) && lodData.length > 1 && this.props.mipmaps !== false) {
233
+ log.warn(`${this.id} has mipmap and multiple LODs.`)();
234
+ }
235
+
236
+ const faceDepth = Texture.CubeFaces.indexOf(face);
237
+
238
+ this.setTexture2DData(lodData, faceDepth);
239
+ }
240
+
241
+ // TODO - Remove when texture refactor is complete
242
+
243
+ /*
244
+ setCubeMapData(options: {
245
+ width: number;
246
+ height: number;
247
+ data: Record<GL, Texture2DData> | Record<TextureCubeFace, Texture2DData>;
248
+ format?: any;
249
+ type?: any;
250
+ /** @deprecated Use .data *
251
+ pixels: any;
252
+ }): void {
253
+ const {gl} = this;
254
+
255
+ const {width, height, pixels, data, format = GL.RGBA, type = GL.UNSIGNED_BYTE} = options;
256
+
257
+ // pixel data (imageDataMap) is an Object from Face to Image or Promise.
258
+ // For example:
259
+ // {
260
+ // GL.TEXTURE_CUBE_MAP_POSITIVE_X : Image-or-Promise,
261
+ // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : Image-or-Promise,
262
+ // ... }
263
+ // To provide multiple level-of-details (LODs) this can be Face to Array
264
+ // of Image or Promise, like this
265
+ // {
266
+ // GL.TEXTURE_CUBE_MAP_POSITIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
267
+ // GL.TEXTURE_CUBE_MAP_NEGATIVE_X : [Image-or-Promise-LOD-0, Image-or-Promise-LOD-1],
268
+ // ... }
269
+
270
+ const imageDataMap = this._getImageDataMap(pixels || data);
271
+
272
+ const resolvedFaces = WEBGLTexture.FACES.map(face => {
273
+ const facePixels = imageDataMap[face];
274
+ return Array.isArray(facePixels) ? facePixels : [facePixels];
275
+ });
276
+ this.bind();
277
+
278
+ WEBGLTexture.FACES.forEach((face, index) => {
279
+ if (resolvedFaces[index].length > 1 && this.props.mipmaps !== false) {
280
+ // If the user provides multiple LODs, then automatic mipmap
281
+ // generation generateMipmaps() should be disabled to avoid overwritting them.
282
+ log.warn(`${this.id} has mipmap and multiple LODs.`)();
283
+ }
284
+ resolvedFaces[index].forEach((image, lodLevel) => {
285
+ // TODO: adjust width & height for LOD!
286
+ if (width && height) {
287
+ gl.texImage2D(face, lodLevel, format, width, height, 0 /* border*, format, type, image);
288
+ } else {
289
+ gl.texImage2D(face, lodLevel, format, format, type, image);
290
+ }
291
+ });
292
+ });
293
+
294
+ this.unbind();
295
+ }
296
+ */
@@ -10,7 +10,7 @@ import {
10
10
  ComputePass,
11
11
  UniformStore,
12
12
  log,
13
- getTypedArrayFromDataType
13
+ getTypedArrayConstructor
14
14
  } from '@luma.gl/core';
15
15
  import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
16
16
  import {ShaderAssembler, getShaderLayoutFromWGSL} from '@luma.gl/shadertools';
@@ -30,7 +30,7 @@ export type ComputationProps = Omit<ComputePipelineProps, 'shader'> & {
30
30
  /** shadertool shader modules (added to shader code) */
31
31
  modules?: ShaderModule[];
32
32
  /** Shadertool module defines (configures shader code)*/
33
- defines?: Record<string, string | number | boolean>;
33
+ defines?: Record<string, boolean>;
34
34
  // TODO - injections, hooks etc?
35
35
 
36
36
  /** Shader inputs, used to generated uniform buffers and bindings */
@@ -340,7 +340,7 @@ export class Computation {
340
340
 
341
341
  // TODO - fix typing of luma data types
342
342
  _getBufferOrConstantValues(attribute: Buffer | TypedArray, dataType: any): string {
343
- const TypedArrayConstructor = getTypedArrayFromDataType(dataType);
343
+ const TypedArrayConstructor = getTypedArrayConstructor(dataType);
344
344
  const typedArray =
345
345
  attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
346
346
  return typedArray.toString();
@@ -46,7 +46,9 @@ export class SwapFramebuffers extends Swap<Framebuffer> {
46
46
  ? colorAttachment
47
47
  : device.createTexture({
48
48
  format: colorAttachment,
49
- usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT
49
+ usage: Texture.SAMPLE | Texture.RENDER | Texture.COPY_SRC | Texture.COPY_DST,
50
+ width: 1,
51
+ height: 1
50
52
  })
51
53
  );
52
54
 
@@ -57,7 +59,10 @@ export class SwapFramebuffers extends Swap<Framebuffer> {
57
59
  ? colorAttachment
58
60
  : device.createTexture({
59
61
  format: colorAttachment,
60
- usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT
62
+ usage:
63
+ Texture.TEXTURE | Texture.COPY_SRC | Texture.COPY_DST | Texture.RENDER_ATTACHMENT,
64
+ width: 1,
65
+ height: 1
61
66
  })
62
67
  );
63
68
 
@@ -3,8 +3,9 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {Buffer, Device, Framebuffer, RenderPassProps, Sampler, Texture} from '@luma.gl/core';
6
- import {Model, ModelProps} from '../model/model';
7
6
  import {getPassthroughFS} from '@luma.gl/shadertools';
7
+ import {Model, ModelProps} from '../model/model';
8
+ import {uid} from '../utils/uid';
8
9
 
9
10
  /**
10
11
  * Properties for creating a {@link TextureTransform}
@@ -60,7 +61,7 @@ export class TextureTransform {
60
61
  });
61
62
 
62
63
  this.model = new Model(this.device, {
63
- id: props.id || 'texture-transform-model',
64
+ id: props.id || uid('texture-transform-model'),
64
65
  fs:
65
66
  props.fs ||
66
67
  getPassthroughFS({
@@ -94,6 +95,7 @@ export class TextureTransform {
94
95
  const renderPass = this.device.beginRenderPass({framebuffer, ...options});
95
96
  this.model.draw(renderPass);
96
97
  renderPass.end();
98
+ this.device.submit();
97
99
  }
98
100
 
99
101
  getTargetTexture(): Texture {
@@ -2,8 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {Texture, Framebuffer} from '@luma.gl/core';
6
- import {flipRows, scalePixels} from './pixel-data-utils';
5
+ import {Texture, Framebuffer, TypedArray} from '@luma.gl/core';
7
6
 
8
7
  /**
9
8
  * Options for copying texture pixels to image
@@ -71,3 +70,54 @@ export function copyTextureToDataUrl(
71
70
 
72
71
  return canvas.toDataURL('image/png');
73
72
  }
73
+
74
+ // HELPERS
75
+
76
+ /**
77
+ * Flip rows (can be used on arrays returned from `Framebuffer.readPixels`)
78
+ * https: *stackoverflow.com/questions/41969562/
79
+ * how-can-i-flip-the-result-of-webglrenderingcontext-readpixels
80
+ * @param param0
81
+ */
82
+ export function flipRows(options: {
83
+ data: TypedArray;
84
+ width: number;
85
+ height: number;
86
+ bytesPerPixel?: number;
87
+ temp?: Uint8Array;
88
+ }): void {
89
+ const {data, width, height, bytesPerPixel = 4, temp} = options;
90
+ const bytesPerRow = width * bytesPerPixel;
91
+
92
+ // make a temp buffer to hold one row
93
+ const tempBuffer = temp || new Uint8Array(bytesPerRow);
94
+ for (let y = 0; y < height / 2; ++y) {
95
+ const topOffset = y * bytesPerRow;
96
+ const bottomOffset = (height - y - 1) * bytesPerRow;
97
+ // make copy of a row on the top half
98
+ tempBuffer.set(data.subarray(topOffset, topOffset + bytesPerRow));
99
+ // copy a row from the bottom half to the top
100
+ data.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
101
+ // copy the copy of the top half row to the bottom half
102
+ data.set(tempBuffer, bottomOffset);
103
+ }
104
+ }
105
+
106
+ export function scalePixels(options: {data: TypedArray; width: number; height: number}): {
107
+ data: Uint8Array;
108
+ width: number;
109
+ height: number;
110
+ } {
111
+ const {data, width, height} = options;
112
+ const newWidth = Math.round(width / 2);
113
+ const newHeight = Math.round(height / 2);
114
+ const newData = new Uint8Array(newWidth * newHeight * 4);
115
+ for (let y = 0; y < newHeight; y++) {
116
+ for (let x = 0; x < newWidth; x++) {
117
+ for (let c = 0; c < 4; c++) {
118
+ newData[(y * newWidth + x) * 4 + c] = data[(y * 2 * width + x * 2) * 4 + c];
119
+ }
120
+ }
121
+ }
122
+ return {data: newData, width: newWidth, height: newHeight};
123
+ }
@@ -3,7 +3,8 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core';
6
- import {Device, RenderPipeline, ComputePipeline} from '@luma.gl/core';
6
+ import {Device, RenderPipeline, ComputePipeline, log} from '@luma.gl/core';
7
+ import {uid} from '../utils/uid';
7
8
 
8
9
  export type PipelineFactoryProps = RenderPipelineProps;
9
10
 
@@ -18,78 +19,171 @@ export class PipelineFactory {
18
19
 
19
20
  /** Get the singleton default pipeline factory for the specified device */
20
21
  static getDefaultPipelineFactory(device: Device): PipelineFactory {
21
- device._lumaData.defaultPipelineFactory =
22
- device._lumaData.defaultPipelineFactory || new PipelineFactory(device);
23
- return device._lumaData.defaultPipelineFactory as PipelineFactory;
22
+ device._lumaData['defaultPipelineFactory'] =
23
+ device._lumaData['defaultPipelineFactory'] || new PipelineFactory(device);
24
+ return device._lumaData['defaultPipelineFactory'] as PipelineFactory;
24
25
  }
25
26
 
26
27
  readonly device: Device;
28
+ readonly cachingEnabled: boolean;
27
29
  readonly destroyPolicy: 'unused' | 'never';
30
+ readonly debug: boolean;
28
31
 
29
32
  private _hashCounter: number = 0;
30
33
  private readonly _hashes: Record<string, number> = {};
31
34
  private readonly _renderPipelineCache: Record<string, RenderPipelineCacheItem> = {};
32
35
  private readonly _computePipelineCache: Record<string, ComputePipelineCacheItem> = {};
33
36
 
37
+ get [Symbol.toStringTag](): string {
38
+ return 'PipelineFactory';
39
+ }
40
+
41
+ toString(): string {
42
+ return `PipelineFactory(${this.device.id})`;
43
+ }
44
+
34
45
  constructor(device: Device) {
35
46
  this.device = device;
36
- this.destroyPolicy = device.props._factoryDestroyPolicy;
47
+ this.cachingEnabled = device.props._cachePipelines;
48
+ this.destroyPolicy = device.props._cacheDestroyPolicy;
49
+ this.debug = device.props.debugFactories;
37
50
  }
38
51
 
39
- /** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */
52
+ /** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */
40
53
  createRenderPipeline(props: RenderPipelineProps): RenderPipeline {
54
+ if (!this.cachingEnabled) {
55
+ return this.device.createRenderPipeline(props);
56
+ }
57
+
41
58
  const allProps: Required<RenderPipelineProps> = {...RenderPipeline.defaultProps, ...props};
42
59
 
60
+ const cache = this._renderPipelineCache;
43
61
  const hash = this._hashRenderPipeline(allProps);
44
62
 
45
- if (!this._renderPipelineCache[hash]) {
46
- const pipeline = this.device.createRenderPipeline({
63
+ let pipeline: RenderPipeline = cache[hash]?.pipeline;
64
+ if (!pipeline) {
65
+ pipeline = this.device.createRenderPipeline({
47
66
  ...allProps,
48
- id: allProps.id ? `${allProps.id}-cached` : undefined
67
+ id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached')
49
68
  });
50
69
  pipeline.hash = hash;
51
- this._renderPipelineCache[hash] = {pipeline, useCount: 0};
70
+ cache[hash] = {pipeline, useCount: 1};
71
+ if (this.debug) {
72
+ log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
73
+ }
74
+ } else {
75
+ cache[hash].useCount++;
76
+ if (this.debug) {
77
+ log.warn(
78
+ `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
79
+ )();
80
+ }
52
81
  }
53
82
 
54
- this._renderPipelineCache[hash].useCount++;
55
- return this._renderPipelineCache[hash].pipeline;
83
+ return pipeline;
56
84
  }
57
85
 
86
+ /** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */
58
87
  createComputePipeline(props: ComputePipelineProps): ComputePipeline {
88
+ if (!this.cachingEnabled) {
89
+ return this.device.createComputePipeline(props);
90
+ }
91
+
59
92
  const allProps: Required<ComputePipelineProps> = {...ComputePipeline.defaultProps, ...props};
60
93
 
94
+ const cache = this._computePipelineCache;
61
95
  const hash = this._hashComputePipeline(allProps);
62
96
 
63
- if (!this._computePipelineCache[hash]) {
64
- const pipeline = this.device.createComputePipeline({
97
+ let pipeline: ComputePipeline = cache[hash]?.pipeline;
98
+ if (!pipeline) {
99
+ pipeline = this.device.createComputePipeline({
65
100
  ...allProps,
66
101
  id: allProps.id ? `${allProps.id}-cached` : undefined
67
102
  });
68
103
  pipeline.hash = hash;
69
- this._computePipelineCache[hash] = {pipeline, useCount: 0};
104
+ cache[hash] = {pipeline, useCount: 1};
105
+ if (this.debug) {
106
+ log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)();
107
+ }
108
+ } else {
109
+ cache[hash].useCount++;
110
+ if (this.debug) {
111
+ log.warn(
112
+ `${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`
113
+ )();
114
+ }
70
115
  }
71
116
 
72
- this._computePipelineCache[hash].useCount++;
73
- return this._computePipelineCache[hash].pipeline;
117
+ return pipeline;
74
118
  }
75
119
 
76
120
  release(pipeline: RenderPipeline | ComputePipeline): void {
121
+ if (!this.cachingEnabled) {
122
+ pipeline.destroy();
123
+ return;
124
+ }
125
+
126
+ const cache = this._getCache(pipeline);
77
127
  const hash = pipeline.hash;
78
- const cache =
79
- pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache;
128
+
80
129
  cache[hash].useCount--;
81
130
  if (cache[hash].useCount === 0) {
82
- if (this.destroyPolicy === 'unused') {
83
- cache[hash].pipeline.destroy();
84
- delete cache[hash];
131
+ this._destroyPipeline(pipeline);
132
+ if (this.debug) {
133
+ log.warn(`${this}: ${pipeline} released and destroyed`)();
85
134
  }
135
+ } else if (cache[hash].useCount < 0) {
136
+ log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)();
137
+ cache[hash].useCount = 0;
138
+ } else if (this.debug) {
139
+ log.warn(`${this}: ${pipeline} released, count=${cache[hash].useCount}`)();
86
140
  }
87
141
  }
88
142
 
89
143
  // PRIVATE
144
+
145
+ /** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */
146
+ private _destroyPipeline(pipeline: RenderPipeline | ComputePipeline): boolean {
147
+ const cache = this._getCache(pipeline);
148
+
149
+ switch (this.destroyPolicy) {
150
+ case 'never':
151
+ return false;
152
+ case 'unused':
153
+ delete cache[pipeline.hash];
154
+ pipeline.destroy();
155
+ return true;
156
+ }
157
+ }
158
+
159
+ /** Get the appropriate cache for the type of pipeline */
160
+ private _getCache(
161
+ pipeline: RenderPipeline | ComputePipeline
162
+ ): Record<string, RenderPipelineCacheItem> | Record<string, ComputePipelineCacheItem> {
163
+ let cache:
164
+ | Record<string, RenderPipelineCacheItem>
165
+ | Record<string, ComputePipelineCacheItem>
166
+ | undefined;
167
+ if (pipeline instanceof ComputePipeline) {
168
+ cache = this._computePipelineCache;
169
+ }
170
+ if (pipeline instanceof RenderPipeline) {
171
+ cache = this._renderPipelineCache;
172
+ }
173
+ if (!cache) {
174
+ throw new Error(`${this}`);
175
+ }
176
+ if (!cache[pipeline.hash]) {
177
+ throw new Error(`${this}: ${pipeline} matched incorrect entry`);
178
+ }
179
+ return cache;
180
+ }
181
+
182
+ /** Calculate a hash based on all the inputs for a compute pipeline */
90
183
  private _hashComputePipeline(props: ComputePipelineProps): string {
184
+ const {type} = this.device;
91
185
  const shaderHash = this._getHash(props.shader.source);
92
- return `${shaderHash}`;
186
+ return `${type}/C/${shaderHash}`;
93
187
  }
94
188
 
95
189
  /** Calculate a hash based on all the inputs for a render pipeline */
@@ -103,17 +197,19 @@ export class PipelineFactory {
103
197
  const varyingHash = '-'; // `${varyingHashes.join('/')}B${bufferMode}`
104
198
  const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout));
105
199
 
106
- switch (this.device.type) {
200
+ const {type} = this.device;
201
+ switch (type) {
107
202
  case 'webgl':
108
203
  // WebGL is more dynamic
109
- return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
204
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`;
110
205
 
206
+ case 'webgpu':
111
207
  default:
112
208
  // On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change
113
209
  const parameterHash = this._getHash(JSON.stringify(props.parameters));
114
210
  // TODO - Can json.stringify() generate different strings for equivalent objects if order of params is different?
115
211
  // create a deepHash() to deduplicate?
116
- return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
212
+ return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`;
117
213
  }
118
214
  }
119
215