@luma.gl/engine 9.3.0-alpha.2 → 9.3.0-alpha.4
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/dist/dist.dev.js +752 -271
- package/dist/dist.min.js +182 -23
- package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -1
- package/dist/dynamic-texture/dynamic-texture.js +37 -4
- package/dist/dynamic-texture/dynamic-texture.js.map +1 -1
- package/dist/dynamic-texture/mipmaps.d.ts +6 -0
- package/dist/dynamic-texture/mipmaps.d.ts.map +1 -0
- package/dist/dynamic-texture/mipmaps.js +441 -0
- package/dist/dynamic-texture/mipmaps.js.map +1 -0
- package/dist/dynamic-texture/texture-data.js +1 -1
- package/dist/dynamic-texture/texture-data.js.map +1 -1
- package/dist/index.cjs +759 -295
- package/dist/index.cjs.map +4 -4
- package/dist/utils/buffer-layout-order.d.ts.map +1 -1
- package/dist/utils/buffer-layout-order.js +12 -2
- package/dist/utils/buffer-layout-order.js.map +1 -1
- package/package.json +4 -4
- package/src/dynamic-texture/dynamic-texture.ts +54 -6
- package/src/dynamic-texture/mipmaps.ts +517 -0
- package/src/dynamic-texture/texture-data.ts +1 -1
- package/src/utils/buffer-layout-order.ts +18 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffer-layout-order.d.ts","sourceRoot":"","sources":["../../src/utils/buffer-layout-order.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,KAAK,YAAY,EAAE,KAAK,YAAY,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"buffer-layout-order.d.ts","sourceRoot":"","sources":["../../src/utils/buffer-layout-order.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,KAAK,YAAY,EAAE,KAAK,YAAY,EAAC,MAAM,eAAe,CAAC;AAkBnE,wBAAgB,yCAAyC,CACvD,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EAAE,GAC3B,YAAY,EAAE,CAgBhB"}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
// luma.gl
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
|
+
function getMinLocation(attributeNames, shaderLayoutMap) {
|
|
5
|
+
let minLocation = Infinity;
|
|
6
|
+
for (const name of attributeNames) {
|
|
7
|
+
const location = shaderLayoutMap[name];
|
|
8
|
+
if (location !== undefined) {
|
|
9
|
+
minLocation = Math.min(minLocation, location);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return minLocation;
|
|
13
|
+
}
|
|
4
14
|
export function sortedBufferLayoutByShaderSourceLocations(shaderLayout, bufferLayout) {
|
|
5
15
|
const shaderLayoutMap = Object.fromEntries(shaderLayout.attributes.map(attr => [attr.name, attr.location]));
|
|
6
16
|
const sortedLayout = bufferLayout.slice();
|
|
7
17
|
sortedLayout.sort((a, b) => {
|
|
8
18
|
const attributeNamesA = a.attributes ? a.attributes.map(attr => attr.attribute) : [a.name];
|
|
9
19
|
const attributeNamesB = b.attributes ? b.attributes.map(attr => attr.attribute) : [b.name];
|
|
10
|
-
const minLocationA =
|
|
11
|
-
const minLocationB =
|
|
20
|
+
const minLocationA = getMinLocation(attributeNamesA, shaderLayoutMap);
|
|
21
|
+
const minLocationB = getMinLocation(attributeNamesB, shaderLayoutMap);
|
|
12
22
|
return minLocationA - minLocationB;
|
|
13
23
|
});
|
|
14
24
|
return sortedLayout;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffer-layout-order.js","sourceRoot":"","sources":["../../src/utils/buffer-layout-order.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAIpC,MAAM,UAAU,yCAAyC,CACvD,YAA0B,EAC1B,YAA4B;IAE5B,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,CACxC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAChE,CAAC;IAEF,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC;IAC1C,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACzB,MAAM,eAAe,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3F,MAAM,eAAe,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3F,MAAM,YAAY,GAAG,
|
|
1
|
+
{"version":3,"file":"buffer-layout-order.js","sourceRoot":"","sources":["../../src/utils/buffer-layout-order.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAIpC,SAAS,cAAc,CACrB,cAAwB,EACxB,eAAmD;IAEnD,IAAI,WAAW,GAAG,QAAQ,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,yCAAyC,CACvD,YAA0B,EAC1B,YAA4B;IAE5B,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,CACxC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAChE,CAAC;IAEF,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC;IAC1C,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACzB,MAAM,eAAe,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3F,MAAM,eAAe,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3F,MAAM,YAAY,GAAG,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QACtE,MAAM,YAAY,GAAG,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;QAEtE,OAAO,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luma.gl/engine",
|
|
3
|
-
"version": "9.3.0-alpha.
|
|
3
|
+
"version": "9.3.0-alpha.4",
|
|
4
4
|
"description": "3D Engine Components for luma.gl",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@math.gl/core": "^4.1.0",
|
|
48
48
|
"@math.gl/types": "^4.1.0",
|
|
49
|
-
"@probe.gl/log": "^4.
|
|
50
|
-
"@probe.gl/stats": "^4.
|
|
49
|
+
"@probe.gl/log": "^4.1.1",
|
|
50
|
+
"@probe.gl/stats": "^4.1.1"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "7486e7b0377fb6ab961b4499828681bede60f3b1"
|
|
53
53
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type TextureArrayData,
|
|
27
27
|
type TextureCubeArrayData,
|
|
28
28
|
type TextureCubeData,
|
|
29
|
+
type TextureImageData,
|
|
29
30
|
|
|
30
31
|
// Helpers
|
|
31
32
|
getTextureSizeFromData,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
getTextureArraySubresources,
|
|
37
38
|
getTextureCubeArraySubresources
|
|
38
39
|
} from './texture-data';
|
|
40
|
+
import {generateMipmap} from './mipmaps';
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Properties for a dynamic texture
|
|
@@ -168,6 +170,14 @@ export class DynamicTexture {
|
|
|
168
170
|
data: undefined
|
|
169
171
|
} satisfies TextureProps;
|
|
170
172
|
|
|
173
|
+
if (this.device.type === 'webgpu' && this.props.mipmaps) {
|
|
174
|
+
const requiredUsage =
|
|
175
|
+
this.props.dimension === '3d'
|
|
176
|
+
? Texture.SAMPLE | Texture.STORAGE | Texture.COPY_DST | Texture.COPY_SRC
|
|
177
|
+
: Texture.SAMPLE | Texture.RENDER | Texture.COPY_DST | Texture.COPY_SRC;
|
|
178
|
+
baseTextureProps.usage |= requiredUsage;
|
|
179
|
+
}
|
|
180
|
+
|
|
171
181
|
// Compute mip levels (auto clamps to max)
|
|
172
182
|
const maxMips = this.device.getMipLevelCount(baseTextureProps.width, baseTextureProps.height);
|
|
173
183
|
const desired =
|
|
@@ -234,14 +244,12 @@ export class DynamicTexture {
|
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
generateMipmaps(): void {
|
|
237
|
-
// Call the WebGL-style mipmap generation helper
|
|
238
|
-
// WebGL implementation generates mipmaps, WebGPU logs a warning
|
|
239
247
|
if (this.device.type === 'webgl') {
|
|
240
248
|
this.texture.generateMipmapsWebGL();
|
|
249
|
+
} else if (this.device.type === 'webgpu') {
|
|
250
|
+
generateMipmap(this.device, this.texture);
|
|
241
251
|
} else {
|
|
242
|
-
log.warn(
|
|
243
|
-
'Mipmap generation not yet implemented on WebGPU: your texture data will not be correctly initialized'
|
|
244
|
-
);
|
|
252
|
+
log.warn(`${this} mipmaps not supported on ${this.device.type}`);
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
255
|
|
|
@@ -362,7 +370,15 @@ export class DynamicTexture {
|
|
|
362
370
|
const {data} = subresource;
|
|
363
371
|
// TODO - we are throwing away some of the info in data.
|
|
364
372
|
// Did we not need it in the first place? Can we use it to validate?
|
|
365
|
-
this.texture.
|
|
373
|
+
this.texture.writeData(getAlignedUploadData(this.texture, data), {
|
|
374
|
+
x: 0,
|
|
375
|
+
y: 0,
|
|
376
|
+
z,
|
|
377
|
+
width: data.width,
|
|
378
|
+
height: data.height,
|
|
379
|
+
depthOrArrayLayers: 1,
|
|
380
|
+
mipLevel
|
|
381
|
+
});
|
|
366
382
|
break;
|
|
367
383
|
default:
|
|
368
384
|
throw new Error('Unsupported 2D mip-level payload');
|
|
@@ -399,6 +415,38 @@ export class DynamicTexture {
|
|
|
399
415
|
};
|
|
400
416
|
}
|
|
401
417
|
|
|
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
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return paddedBytes;
|
|
448
|
+
}
|
|
449
|
+
|
|
402
450
|
// HELPERS
|
|
403
451
|
|
|
404
452
|
/** Resolve all promises in a nested data structure */
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
// luma.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
// Forked from https://github.com/greggman/webgpu-utils under MIT license
|
|
6
|
+
// Copyright (c) 2022 Gregg Tavares
|
|
7
|
+
|
|
8
|
+
import type {Device, Texture, TextureFormat, TextureFormatColor} from '@luma.gl/core';
|
|
9
|
+
import {Buffer, textureFormatDecoder} from '@luma.gl/core';
|
|
10
|
+
import {Model} from '../model/model';
|
|
11
|
+
import {Computation} from '../compute/computation';
|
|
12
|
+
|
|
13
|
+
type RenderTextureViewDimension = '2d' | '2d-array' | 'cube' | 'cube-array';
|
|
14
|
+
type TextureCapability = 'render' | 'filter' | 'store';
|
|
15
|
+
type MipmapPath = 'render' | 'compute';
|
|
16
|
+
|
|
17
|
+
const RENDER_DIMENSIONS: ReadonlyArray<RenderTextureViewDimension> = [
|
|
18
|
+
'2d',
|
|
19
|
+
'2d-array',
|
|
20
|
+
'cube',
|
|
21
|
+
'cube-array'
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const WORKGROUP_SIZE = {
|
|
25
|
+
x: 4,
|
|
26
|
+
y: 4,
|
|
27
|
+
z: 4
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates mip levels from level 0 to the last mip for an existing texture.
|
|
32
|
+
*/
|
|
33
|
+
export function generateMipmap(device: Device, texture: Texture): void {
|
|
34
|
+
if (texture.mipLevels <= 1) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (device.type !== 'webgpu') {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Cannot generate mipmaps on device type "${device.type}". Use generateMipmapsWebGL for WebGL devices.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (texture.dimension === '3d') {
|
|
45
|
+
generateMipmaps3D(device, texture);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (RENDER_DIMENSIONS.includes(texture.dimension as RenderTextureViewDimension)) {
|
|
50
|
+
generateMipmapsRender(device, texture);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Cannot generate mipmaps for texture dimension "${texture.dimension}" with WebGPU.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function generateMipmapsRender(device: Device, texture: Texture): void {
|
|
60
|
+
validateFormatCapabilities(device, texture, ['render', 'filter'], 'render');
|
|
61
|
+
const colorAttachmentFormat = getColorAttachmentFormat(
|
|
62
|
+
texture.format,
|
|
63
|
+
'render',
|
|
64
|
+
texture.dimension
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const viewDimension = texture.dimension as RenderTextureViewDimension;
|
|
68
|
+
const shader = getRenderMipmapWGSL(viewDimension);
|
|
69
|
+
const sampler = device.createSampler({minFilter: 'linear', magFilter: 'linear'});
|
|
70
|
+
const uniformValues = new Uint32Array(1);
|
|
71
|
+
const uniformsBuffer = device.createBuffer({
|
|
72
|
+
byteLength: 16,
|
|
73
|
+
usage: Buffer.UNIFORM | Buffer.COPY_DST
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const model = new Model(device, {
|
|
77
|
+
source: shader,
|
|
78
|
+
colorAttachmentFormats: [colorAttachmentFormat],
|
|
79
|
+
topology: 'triangle-list',
|
|
80
|
+
vertexCount: 3,
|
|
81
|
+
shaderLayout: {
|
|
82
|
+
attributes: [],
|
|
83
|
+
bindings: [
|
|
84
|
+
{type: 'sampler', name: 'sourceSampler', group: 0, location: 0},
|
|
85
|
+
{
|
|
86
|
+
type: 'texture',
|
|
87
|
+
name: 'sourceTexture',
|
|
88
|
+
group: 0,
|
|
89
|
+
location: 1,
|
|
90
|
+
viewDimension,
|
|
91
|
+
sampleType: 'float'
|
|
92
|
+
},
|
|
93
|
+
{type: 'uniform', name: 'uniforms', group: 0, location: 2}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
bindings: {
|
|
97
|
+
sourceSampler: sampler,
|
|
98
|
+
sourceTexture: texture,
|
|
99
|
+
uniforms: uniformsBuffer
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let sourceWidth = texture.width;
|
|
104
|
+
let sourceHeight = texture.height;
|
|
105
|
+
const layerCount = texture.dimension === '2d' ? 1 : texture.depth;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
for (let baseMipLevel = 1; baseMipLevel < texture.mipLevels; ++baseMipLevel) {
|
|
109
|
+
validateFormatCapabilities(device, texture, ['render', 'filter'], 'render');
|
|
110
|
+
const sourceMipLevel = baseMipLevel - 1;
|
|
111
|
+
const destinationWidth = Math.max(1, sourceWidth >> 1);
|
|
112
|
+
const destinationHeight = Math.max(1, sourceHeight >> 1);
|
|
113
|
+
|
|
114
|
+
const sourceView = texture.createView({
|
|
115
|
+
dimension: viewDimension,
|
|
116
|
+
baseMipLevel: sourceMipLevel,
|
|
117
|
+
mipLevelCount: 1,
|
|
118
|
+
baseArrayLayer: 0,
|
|
119
|
+
arrayLayerCount: texture.depth
|
|
120
|
+
});
|
|
121
|
+
model.setBindings({sourceTexture: sourceView});
|
|
122
|
+
|
|
123
|
+
for (let baseArrayLayer = 0; baseArrayLayer < layerCount; ++baseArrayLayer) {
|
|
124
|
+
uniformValues[0] = baseArrayLayer;
|
|
125
|
+
uniformsBuffer.write(uniformValues);
|
|
126
|
+
|
|
127
|
+
const destinationView = texture.createView({
|
|
128
|
+
dimension: '2d',
|
|
129
|
+
baseMipLevel,
|
|
130
|
+
mipLevelCount: 1,
|
|
131
|
+
baseArrayLayer,
|
|
132
|
+
arrayLayerCount: 1
|
|
133
|
+
});
|
|
134
|
+
const framebuffer = device.createFramebuffer({
|
|
135
|
+
colorAttachments: [destinationView]
|
|
136
|
+
});
|
|
137
|
+
const renderPass = device.beginRenderPass({
|
|
138
|
+
id: `mipmap-generation:${texture.format}:${baseMipLevel}:${baseArrayLayer}`,
|
|
139
|
+
framebuffer
|
|
140
|
+
});
|
|
141
|
+
renderPass.setParameters({
|
|
142
|
+
viewport: [0, 0, destinationWidth, destinationHeight, 0, 1],
|
|
143
|
+
scissorRect: [0, 0, destinationWidth, destinationHeight]
|
|
144
|
+
});
|
|
145
|
+
model.draw(renderPass);
|
|
146
|
+
renderPass.end();
|
|
147
|
+
device.submit();
|
|
148
|
+
|
|
149
|
+
destinationView.destroy();
|
|
150
|
+
framebuffer.destroy();
|
|
151
|
+
}
|
|
152
|
+
sourceView.destroy();
|
|
153
|
+
|
|
154
|
+
sourceWidth = destinationWidth;
|
|
155
|
+
sourceHeight = destinationHeight;
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
model.destroy();
|
|
159
|
+
sampler.destroy();
|
|
160
|
+
uniformsBuffer.destroy();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getColorAttachmentFormat(
|
|
165
|
+
format: TextureFormat,
|
|
166
|
+
path: MipmapPath,
|
|
167
|
+
dimension: string
|
|
168
|
+
): TextureFormatColor {
|
|
169
|
+
if (textureFormatDecoder.isColor(format)) {
|
|
170
|
+
return format;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Cannot run ${path} mipmap generation for ${dimension} texture with format "${format}". ` +
|
|
175
|
+
`Only color textures can be used for this operation. ` +
|
|
176
|
+
`Required capabilities: color. ` +
|
|
177
|
+
`Actual capabilities: color=false.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function generateMipmaps3D(device: Device, texture: Texture): void {
|
|
182
|
+
validateFormatCapabilities(device, texture, ['filter', 'store'], 'compute');
|
|
183
|
+
const format = getColorAttachmentFormat(texture.format, 'compute', texture.dimension);
|
|
184
|
+
|
|
185
|
+
const shaderSource = get3DComputeMipmapWGSL(format);
|
|
186
|
+
const uniformsBuffer = device.createBuffer({
|
|
187
|
+
byteLength: 32,
|
|
188
|
+
usage: Buffer.UNIFORM | Buffer.COPY_DST
|
|
189
|
+
});
|
|
190
|
+
const uniformValues = new Uint32Array(8);
|
|
191
|
+
|
|
192
|
+
let sourceWidth = texture.width;
|
|
193
|
+
let sourceHeight = texture.height;
|
|
194
|
+
let sourceDepth = texture.depth;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
for (
|
|
198
|
+
let destinationMipLevel = 1;
|
|
199
|
+
destinationMipLevel < texture.mipLevels;
|
|
200
|
+
++destinationMipLevel
|
|
201
|
+
) {
|
|
202
|
+
validateFormatCapabilities(device, texture, ['filter', 'store'], 'compute');
|
|
203
|
+
const destinationWidth = Math.max(1, sourceWidth >> 1);
|
|
204
|
+
const destinationHeight = Math.max(1, sourceHeight >> 1);
|
|
205
|
+
const destinationDepth = Math.max(1, sourceDepth >> 1);
|
|
206
|
+
|
|
207
|
+
uniformValues[0] = sourceWidth;
|
|
208
|
+
uniformValues[1] = sourceHeight;
|
|
209
|
+
uniformValues[2] = sourceDepth;
|
|
210
|
+
uniformValues[3] = destinationWidth;
|
|
211
|
+
uniformValues[4] = destinationHeight;
|
|
212
|
+
uniformValues[5] = destinationDepth;
|
|
213
|
+
uniformValues[6] = 0;
|
|
214
|
+
uniformsBuffer.write(uniformValues);
|
|
215
|
+
|
|
216
|
+
const sourceView = texture.createView({
|
|
217
|
+
dimension: '3d',
|
|
218
|
+
baseMipLevel: destinationMipLevel - 1,
|
|
219
|
+
mipLevelCount: 1,
|
|
220
|
+
baseArrayLayer: 0,
|
|
221
|
+
arrayLayerCount: 1
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const destinationView = texture.createView({
|
|
225
|
+
dimension: '3d',
|
|
226
|
+
baseMipLevel: destinationMipLevel,
|
|
227
|
+
mipLevelCount: 1,
|
|
228
|
+
baseArrayLayer: 0,
|
|
229
|
+
arrayLayerCount: 1
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const computation = new Computation(device, {
|
|
233
|
+
source: shaderSource,
|
|
234
|
+
shaderLayout: {
|
|
235
|
+
bindings: [
|
|
236
|
+
{
|
|
237
|
+
type: 'texture',
|
|
238
|
+
name: 'sourceTexture',
|
|
239
|
+
group: 0,
|
|
240
|
+
location: 0,
|
|
241
|
+
viewDimension: '3d',
|
|
242
|
+
sampleType: 'float'
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: 'storage',
|
|
246
|
+
name: 'destinationTexture',
|
|
247
|
+
group: 0,
|
|
248
|
+
location: 1,
|
|
249
|
+
format,
|
|
250
|
+
viewDimension: '3d',
|
|
251
|
+
access: 'write-only'
|
|
252
|
+
},
|
|
253
|
+
{type: 'uniform', name: 'uniforms', group: 0, location: 2}
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
bindings: {
|
|
257
|
+
sourceTexture: sourceView,
|
|
258
|
+
destinationTexture: destinationView,
|
|
259
|
+
uniforms: uniformsBuffer
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const workgroupsX = Math.ceil(destinationWidth / WORKGROUP_SIZE.x);
|
|
264
|
+
const workgroupsY = Math.ceil(destinationHeight / WORKGROUP_SIZE.y);
|
|
265
|
+
const workgroupsZ = Math.ceil(destinationDepth / WORKGROUP_SIZE.z);
|
|
266
|
+
|
|
267
|
+
const computePass = device.beginComputePass({});
|
|
268
|
+
computation.dispatch(computePass, workgroupsX, workgroupsY, workgroupsZ);
|
|
269
|
+
computePass.end();
|
|
270
|
+
device.submit();
|
|
271
|
+
|
|
272
|
+
computation.destroy();
|
|
273
|
+
sourceView.destroy();
|
|
274
|
+
destinationView.destroy();
|
|
275
|
+
|
|
276
|
+
sourceWidth = destinationWidth;
|
|
277
|
+
sourceHeight = destinationHeight;
|
|
278
|
+
sourceDepth = destinationDepth;
|
|
279
|
+
}
|
|
280
|
+
} finally {
|
|
281
|
+
uniformsBuffer.destroy();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function validateFormatCapabilities(
|
|
286
|
+
device: Device,
|
|
287
|
+
texture: Texture,
|
|
288
|
+
requiredCapabilities: ReadonlyArray<TextureCapability>,
|
|
289
|
+
path: MipmapPath
|
|
290
|
+
): void {
|
|
291
|
+
const {format, dimension} = texture;
|
|
292
|
+
const capabilities = device.getTextureFormatCapabilities(format);
|
|
293
|
+
const missingCapabilities = requiredCapabilities.filter(capability => !capabilities[capability]);
|
|
294
|
+
|
|
295
|
+
if (missingCapabilities.length > 0) {
|
|
296
|
+
const required = requiredCapabilities.join(' + ');
|
|
297
|
+
const actual = requiredCapabilities
|
|
298
|
+
.map(capability => `${capability}=${capabilities[capability]}`)
|
|
299
|
+
.join(', ');
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Cannot run ${path} mipmap generation for ${dimension} texture with format "${format}". ` +
|
|
302
|
+
`Required capabilities: ${required}. ` +
|
|
303
|
+
`Actual capabilities: ${actual}.`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function getSourceTextureType(dimension: RenderTextureViewDimension): string {
|
|
309
|
+
switch (dimension) {
|
|
310
|
+
case '2d':
|
|
311
|
+
return 'texture_2d<f32>';
|
|
312
|
+
case '2d-array':
|
|
313
|
+
return 'texture_2d_array<f32>';
|
|
314
|
+
case 'cube':
|
|
315
|
+
return 'texture_cube<f32>';
|
|
316
|
+
case 'cube-array':
|
|
317
|
+
return 'texture_cube_array<f32>';
|
|
318
|
+
default:
|
|
319
|
+
throw new Error(`Unsupported render dimension "${dimension}" for mipmap generation.`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getRenderMipmapWGSL(dimension: RenderTextureViewDimension): string {
|
|
324
|
+
const sourceSnippet = getRenderMipmapSampleSnippet(dimension);
|
|
325
|
+
|
|
326
|
+
return `
|
|
327
|
+
struct MipmapUniforms {
|
|
328
|
+
sourceLayer: u32,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
fn _touchUniform(uniforms: MipmapUniforms) {
|
|
332
|
+
let unusedSourceLayer = uniforms.sourceLayer;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const faceMat = array(
|
|
336
|
+
mat3x3f(
|
|
337
|
+
0.0, 0.0, -2.0,
|
|
338
|
+
0.0, -2.0, 0.0,
|
|
339
|
+
1.0, 1.0, 1.0
|
|
340
|
+
), // pos-x
|
|
341
|
+
mat3x3f(
|
|
342
|
+
0.0, 0.0, 2.0,
|
|
343
|
+
0.0, -2.0, 0.0,
|
|
344
|
+
-1.0, 1.0, -1.0
|
|
345
|
+
), // neg-x
|
|
346
|
+
mat3x3f(
|
|
347
|
+
2.0, 0.0, 0.0,
|
|
348
|
+
0.0, 0.0, 2.0,
|
|
349
|
+
-1.0, 1.0, -1.0
|
|
350
|
+
), // pos-y
|
|
351
|
+
mat3x3f(
|
|
352
|
+
2.0, 0.0, 0.0,
|
|
353
|
+
0.0, 0.0, -2.0,
|
|
354
|
+
-1.0, -1.0, 1.0
|
|
355
|
+
), // neg-y
|
|
356
|
+
mat3x3f(
|
|
357
|
+
2.0, 0.0, 0.0,
|
|
358
|
+
0.0, -2.0, 0.0,
|
|
359
|
+
-1.0, 1.0, 1.0
|
|
360
|
+
), // pos-z
|
|
361
|
+
mat3x3f(
|
|
362
|
+
-2.0, 0.0, 0.0,
|
|
363
|
+
0.0, -2.0, 0.0,
|
|
364
|
+
1.0, 1.0, -1.0
|
|
365
|
+
) // neg-z
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
struct FragmentInputs {
|
|
369
|
+
@builtin(position) position: vec4f,
|
|
370
|
+
@location(0) texcoord: vec2f
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
struct VertexOutput {
|
|
374
|
+
@builtin(position) position: vec4f,
|
|
375
|
+
@location(0) texcoord: vec2f
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
@group(0) @binding(0) var sourceSampler: sampler;
|
|
379
|
+
@group(0) @binding(1) var sourceTexture: ${getSourceTextureType(dimension)};
|
|
380
|
+
@group(0) @binding(2) var<uniform> uniforms: MipmapUniforms;
|
|
381
|
+
|
|
382
|
+
@vertex
|
|
383
|
+
fn vertexMain(
|
|
384
|
+
@builtin(vertex_index) vertexIndex: u32
|
|
385
|
+
) -> VertexOutput {
|
|
386
|
+
const positions = array(
|
|
387
|
+
vec2f(-1.0, -1.0),
|
|
388
|
+
vec2f(-1.0, 3.0),
|
|
389
|
+
vec2f( 3.0, -1.0)
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
let xy = positions[vertexIndex];
|
|
393
|
+
return VertexOutput(
|
|
394
|
+
vec4f(xy, 0.0, 1.0),
|
|
395
|
+
xy * vec2f(0.5, -0.5) + vec2f(0.5)
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
@fragment
|
|
400
|
+
fn fragmentMain(fsInput: VertexOutput) -> @location(0) vec4f {
|
|
401
|
+
_touchUniform(uniforms);
|
|
402
|
+
return ${sourceSnippet};
|
|
403
|
+
}
|
|
404
|
+
`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function getRenderMipmapSampleSnippet(dimension: RenderTextureViewDimension): string {
|
|
408
|
+
const layer = 'uniforms.sourceLayer';
|
|
409
|
+
|
|
410
|
+
switch (dimension) {
|
|
411
|
+
case '2d':
|
|
412
|
+
return 'textureSampleLevel(sourceTexture, sourceSampler, fsInput.texcoord, 0.0)';
|
|
413
|
+
case '2d-array':
|
|
414
|
+
return (
|
|
415
|
+
'textureSampleLevel(sourceTexture, sourceSampler, fsInput.texcoord, ' +
|
|
416
|
+
`i32(${layer}), 0.0)`
|
|
417
|
+
);
|
|
418
|
+
case 'cube':
|
|
419
|
+
return (
|
|
420
|
+
'textureSampleLevel(sourceTexture, sourceSampler, ' +
|
|
421
|
+
`faceMat[i32(${layer})] * vec3f(fract(fsInput.texcoord), 1.0), 0.0)`
|
|
422
|
+
);
|
|
423
|
+
case 'cube-array':
|
|
424
|
+
return (
|
|
425
|
+
'textureSampleLevel(sourceTexture, sourceSampler, ' +
|
|
426
|
+
`faceMat[i32(${layer} % 6u)] * vec3f(fract(fsInput.texcoord), 1.0), ` +
|
|
427
|
+
`i32(${layer} / 6u), 0.0)`
|
|
428
|
+
);
|
|
429
|
+
default:
|
|
430
|
+
throw new Error(`Unsupported render dimension "${dimension}" for mipmap generation.`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function get3DComputeMipmapWGSL(format: TextureFormatColor): string {
|
|
435
|
+
return `
|
|
436
|
+
struct MipmapUniforms {
|
|
437
|
+
sourceWidth: u32,
|
|
438
|
+
sourceHeight: u32,
|
|
439
|
+
sourceDepth: u32,
|
|
440
|
+
destinationWidth: u32,
|
|
441
|
+
destinationHeight: u32,
|
|
442
|
+
destinationDepth: u32,
|
|
443
|
+
padding: u32,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
@group(0) @binding(0) var sourceTexture: texture_3d<f32>;
|
|
447
|
+
@group(0) @binding(1) var destinationTexture: texture_storage_3d<${format}, write>;
|
|
448
|
+
@group(0) @binding(2) var<uniform> uniforms: MipmapUniforms;
|
|
449
|
+
|
|
450
|
+
@compute @workgroup_size(${WORKGROUP_SIZE.x}, ${WORKGROUP_SIZE.y}, ${WORKGROUP_SIZE.z})
|
|
451
|
+
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
452
|
+
if (
|
|
453
|
+
id.x >= uniforms.destinationWidth ||
|
|
454
|
+
id.y >= uniforms.destinationHeight ||
|
|
455
|
+
id.z >= uniforms.destinationDepth
|
|
456
|
+
) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let sourceBase = id * 2u;
|
|
461
|
+
let sourceX0 = min(sourceBase.x, uniforms.sourceWidth - 1u);
|
|
462
|
+
let sourceY0 = min(sourceBase.y, uniforms.sourceHeight - 1u);
|
|
463
|
+
let sourceZ0 = min(sourceBase.z, uniforms.sourceDepth - 1u);
|
|
464
|
+
|
|
465
|
+
let sourceX1 = min(sourceBase.x + 1u, uniforms.sourceWidth - 1u);
|
|
466
|
+
let sourceY1 = min(sourceBase.y + 1u, uniforms.sourceHeight - 1u);
|
|
467
|
+
let sourceZ1 = min(sourceBase.z + 1u, uniforms.sourceDepth - 1u);
|
|
468
|
+
|
|
469
|
+
var sum = textureLoad(
|
|
470
|
+
sourceTexture,
|
|
471
|
+
vec3<i32>(i32(sourceX0), i32(sourceY0), i32(sourceZ0)),
|
|
472
|
+
0
|
|
473
|
+
);
|
|
474
|
+
sum += textureLoad(
|
|
475
|
+
sourceTexture,
|
|
476
|
+
vec3<i32>(i32(sourceX1), i32(sourceY0), i32(sourceZ0)),
|
|
477
|
+
0
|
|
478
|
+
);
|
|
479
|
+
sum += textureLoad(
|
|
480
|
+
sourceTexture,
|
|
481
|
+
vec3<i32>(i32(sourceX0), i32(sourceY1), i32(sourceZ0)),
|
|
482
|
+
0
|
|
483
|
+
);
|
|
484
|
+
sum += textureLoad(
|
|
485
|
+
sourceTexture,
|
|
486
|
+
vec3<i32>(i32(sourceX1), i32(sourceY1), i32(sourceZ0)),
|
|
487
|
+
0
|
|
488
|
+
);
|
|
489
|
+
sum += textureLoad(
|
|
490
|
+
sourceTexture,
|
|
491
|
+
vec3<i32>(i32(sourceX0), i32(sourceY0), i32(sourceZ1)),
|
|
492
|
+
0
|
|
493
|
+
);
|
|
494
|
+
sum += textureLoad(
|
|
495
|
+
sourceTexture,
|
|
496
|
+
vec3<i32>(i32(sourceX1), i32(sourceY0), i32(sourceZ1)),
|
|
497
|
+
0
|
|
498
|
+
);
|
|
499
|
+
sum += textureLoad(
|
|
500
|
+
sourceTexture,
|
|
501
|
+
vec3<i32>(i32(sourceX0), i32(sourceY1), i32(sourceZ1)),
|
|
502
|
+
0
|
|
503
|
+
);
|
|
504
|
+
sum += textureLoad(
|
|
505
|
+
sourceTexture,
|
|
506
|
+
vec3<i32>(i32(sourceX1), i32(sourceY1), i32(sourceZ1)),
|
|
507
|
+
0
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
textureStore(
|
|
511
|
+
destinationTexture,
|
|
512
|
+
vec3<i32>(i32(id.x), i32(id.y), i32(id.z)),
|
|
513
|
+
vec4<f32>(sum.xyz / 8.0, sum.w / 8.0)
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
@@ -294,7 +294,7 @@ export function getTextureCubeArraySubresources(data: TextureCubeArrayData): Tex
|
|
|
294
294
|
data.forEach((cubeData, cubeIndex) => {
|
|
295
295
|
for (const [face, faceData] of Object.entries(cubeData)) {
|
|
296
296
|
const faceDepth = getCubeArrayFaceIndex(cubeIndex, face as TextureCubeFace);
|
|
297
|
-
getTexture2DSubresources(faceDepth, faceData);
|
|
297
|
+
subresources.push(...getTexture2DSubresources(faceDepth, faceData));
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
300
|
return subresources;
|