@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.
- package/README.md +5 -0
- package/dist/animation-loop/animation-loop.d.ts +12 -12
- package/dist/animation-loop/animation-loop.d.ts.map +1 -1
- package/dist/animation-loop/animation-loop.js +26 -62
- package/dist/animation-loop/animation-loop.js.map +1 -1
- package/dist/animation-loop/animation-props.d.ts +3 -4
- package/dist/animation-loop/animation-props.d.ts.map +1 -1
- package/dist/animation-loop/make-animation-loop.d.ts +4 -1
- package/dist/animation-loop/make-animation-loop.d.ts.map +1 -1
- package/dist/animation-loop/make-animation-loop.js +39 -7
- package/dist/animation-loop/make-animation-loop.js.map +1 -1
- package/dist/async-texture/async-texture.d.ts +106 -2
- package/dist/async-texture/async-texture.d.ts.map +1 -1
- package/dist/async-texture/async-texture.js +281 -13
- package/dist/async-texture/async-texture.js.map +1 -1
- package/dist/compute/computation.d.ts +1 -1
- package/dist/compute/computation.d.ts.map +1 -1
- package/dist/compute/computation.js +2 -2
- package/dist/compute/computation.js.map +1 -1
- package/dist/compute/swap.d.ts.map +1 -1
- package/dist/compute/swap.js +6 -2
- package/dist/compute/swap.js.map +1 -1
- package/dist/compute/texture-transform.d.ts.map +1 -1
- package/dist/compute/texture-transform.js +4 -2
- package/dist/compute/texture-transform.js.map +1 -1
- package/dist/debug/copy-texture-to-image.d.ts +23 -1
- package/dist/debug/copy-texture-to-image.d.ts.map +1 -1
- package/dist/debug/copy-texture-to-image.js +37 -1
- package/dist/debug/copy-texture-to-image.js.map +1 -1
- package/dist/dist.dev.js +566 -232
- package/dist/dist.min.js +26 -26
- package/dist/factories/pipeline-factory.d.ts +11 -1
- package/dist/factories/pipeline-factory.d.ts.map +1 -1
- package/dist/factories/pipeline-factory.js +107 -25
- package/dist/factories/pipeline-factory.js.map +1 -1
- package/dist/factories/shader-factory.d.ts +5 -1
- package/dist/factories/shader-factory.d.ts.map +1 -1
- package/dist/factories/shader-factory.js +40 -6
- package/dist/factories/shader-factory.js.map +1 -1
- package/dist/geometries/cube-geometry.d.ts +3 -3
- package/dist/geometries/cube-geometry.d.ts.map +1 -1
- package/dist/geometry/geometry.d.ts.map +1 -1
- package/dist/geometry/geometry.js +3 -2
- package/dist/geometry/geometry.js.map +1 -1
- package/dist/index.cjs +581 -251
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/model.d.ts +4 -25
- package/dist/model/model.d.ts.map +1 -1
- package/dist/model/model.js +26 -71
- package/dist/model/model.js.map +1 -1
- package/dist/models/billboard-texture-model.d.ts.map +1 -1
- package/dist/models/billboard-texture-model.js +6 -4
- package/dist/models/billboard-texture-model.js.map +1 -1
- package/dist/modules/picking/legacy-picking-manager.d.ts +1 -1
- package/dist/modules/picking/legacy-picking-manager.d.ts.map +1 -1
- package/dist/modules/picking/legacy-picking-manager.js +1 -1
- package/dist/modules/picking/legacy-picking-manager.js.map +1 -1
- package/dist/modules/picking/picking-manager.d.ts +2 -2
- package/dist/modules/picking/picking-manager.d.ts.map +1 -1
- package/dist/modules/picking/picking-manager.js +2 -2
- package/dist/modules/picking/picking-manager.js.map +1 -1
- package/dist/passes/get-fragment-shader.js +2 -2
- package/dist/passes/shader-pass-renderer.d.ts +4 -4
- package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
- package/dist/passes/shader-pass-renderer.js +15 -5
- package/dist/passes/shader-pass-renderer.js.map +1 -1
- package/dist/shader-inputs.js +1 -1
- package/dist/shader-inputs.js.map +1 -1
- package/dist/utils/buffer-layout-helper.d.ts +12 -0
- package/dist/utils/buffer-layout-helper.d.ts.map +1 -0
- package/dist/utils/buffer-layout-helper.js +41 -0
- package/dist/utils/buffer-layout-helper.js.map +1 -0
- package/dist/utils/buffer-layout-order.d.ts +3 -0
- package/dist/utils/buffer-layout-order.d.ts.map +1 -0
- package/dist/utils/buffer-layout-order.js +16 -0
- package/dist/utils/buffer-layout-order.js.map +1 -0
- package/package.json +4 -4
- package/src/animation-loop/animation-loop.ts +31 -71
- package/src/animation-loop/animation-props.ts +3 -5
- package/src/animation-loop/make-animation-loop.ts +41 -9
- package/src/async-texture/async-texture.ts +386 -23
- package/src/async-texture/texture-setters.ts.disabled +296 -0
- package/src/compute/computation.ts +3 -3
- package/src/compute/swap.ts +7 -2
- package/src/compute/texture-transform.ts +4 -2
- package/src/debug/copy-texture-to-image.ts +52 -2
- package/src/factories/pipeline-factory.ts +122 -26
- package/src/factories/shader-factory.ts +43 -7
- package/src/geometry/geometry.ts +3 -2
- package/src/index.ts +12 -0
- package/src/model/model.ts +31 -86
- package/src/models/billboard-texture-model.ts +6 -4
- package/src/modules/picking/legacy-picking-manager.ts +2 -2
- package/src/modules/picking/picking-manager.ts +3 -3
- package/src/passes/get-fragment-shader.ts +2 -2
- package/src/passes/shader-pass-renderer.ts +18 -8
- package/src/shader-inputs.ts +1 -1
- package/src/utils/buffer-layout-helper.ts +51 -0
- 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
|
-
|
|
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,
|
|
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 =
|
|
343
|
+
const TypedArrayConstructor = getTypedArrayConstructor(dataType);
|
|
344
344
|
const typedArray =
|
|
345
345
|
attribute instanceof Buffer ? new TypedArrayConstructor(attribute.debugData) : attribute;
|
|
346
346
|
return typedArray.toString();
|
package/src/compute/swap.ts
CHANGED
|
@@ -46,7 +46,9 @@ export class SwapFramebuffers extends Swap<Framebuffer> {
|
|
|
46
46
|
? colorAttachment
|
|
47
47
|
: device.createTexture({
|
|
48
48
|
format: colorAttachment,
|
|
49
|
-
usage: Texture.
|
|
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:
|
|
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
|
|
22
|
-
device._lumaData
|
|
23
|
-
return device._lumaData
|
|
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.
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
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` :
|
|
67
|
+
id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached')
|
|
49
68
|
});
|
|
50
69
|
pipeline.hash = hash;
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache;
|
|
128
|
+
|
|
80
129
|
cache[hash].useCount--;
|
|
81
130
|
if (cache[hash].useCount === 0) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
|