Skip to content

Commit 686bdc5

Browse files
authored
Support for KTX2 Cubemaps (#9065)
* Support for KTX2 Cubemaps * Discard package-lock.json
1 parent d7cc823 commit 686bdc5

3 files changed

Lines changed: 58 additions & 27 deletions

File tree

src/framework/handlers/basis-worker.js

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ function BasisWorker() {
202202
const hasAlpha = !!basisFile.getHasAlpha();
203203
const isUASTC = basisFile.isUASTC && basisFile.isUASTC();
204204

205+
// a six-face file is a cubemap; faces are transcoded into per-mip arrays
206+
const cubemap = !!options.isCubemap;
207+
205208
if (!width || !height || !levels) {
206209
basisFile.close();
207210
basisFile.delete();
@@ -211,8 +214,8 @@ function BasisWorker() {
211214
// choose the target format
212215
const format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC);
213216

214-
// unswizzle gggr textures under pvr compression
215-
const unswizzle = !!options.isGGGR && format === 'pvr';
217+
// unswizzle gggr textures under pvr compression (never applies to cubemaps)
218+
const unswizzle = !cubemap && !!options.isGGGR && format === 'pvr';
216219

217220
// convert to basis format taking into consideration platform restrictions
218221
let basisFormat;
@@ -235,22 +238,34 @@ function BasisWorker() {
235238
throw new Error(`Failed to start transcoding url=${url}`);
236239
}
237240

238-
let i;
241+
const is16BitFormat = (basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444);
239242

240-
const levelData = [];
241-
for (let mip = 0; mip < levels; ++mip) {
242-
const dstSize = basisFile.getImageTranscodedSizeInBytes(mip, 0, 0, basisFormat);
243+
// transcode a single face of a single mip level into a typed array
244+
const transcodeImage = (mip, face) => {
245+
const dstSize = basisFile.getImageTranscodedSizeInBytes(mip, 0, face, basisFormat);
243246
const dst = new Uint8Array(dstSize);
244247

245-
if (!basisFile.transcodeImage(dst, mip, 0, 0, basisFormat, 0, -1, -1)) {
248+
if (!basisFile.transcodeImage(dst, mip, 0, face, basisFormat, 0, -1, -1)) {
246249
basisFile.close();
247250
basisFile.delete();
248251
throw new Error(`Failed to transcode image url=${url}`);
249252
}
250253

251-
const is16BitFormat = (basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444);
254+
return is16BitFormat ? new Uint16Array(dst.buffer) : dst;
255+
};
252256

253-
levelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst);
257+
const levelData = [];
258+
for (let mip = 0; mip < levels; ++mip) {
259+
if (cubemap) {
260+
// one entry per mip, each holding the six cubemap faces
261+
const faceData = [];
262+
for (let face = 0; face < 6; ++face) {
263+
faceData.push(transcodeImage(mip, face));
264+
}
265+
levelData.push(faceData);
266+
} else {
267+
levelData.push(transcodeImage(mip, 0));
268+
}
254269
}
255270

256271
basisFile.close();
@@ -259,7 +274,7 @@ function BasisWorker() {
259274
// handle unswizzle option
260275
if (unswizzle) {
261276
basisFormat = BASIS_FORMAT.cTFRGB565;
262-
for (i = 0; i < levelData.length; ++i) {
277+
for (let i = 0; i < levelData.length; ++i) {
263278
levelData[i] = pack565(unswizzleGGGR(levelData[i]));
264279
}
265280
}
@@ -269,7 +284,7 @@ function BasisWorker() {
269284
width: width,
270285
height: height,
271286
levels: levelData,
272-
cubemap: false,
287+
cubemap: cubemap,
273288
transcodeTime: performanceNow() - funcStart,
274289
url: url,
275290
unswizzledGGGR: unswizzle
@@ -380,8 +395,22 @@ function BasisWorker() {
380395
const workerTranscode = (url, data, options) => {
381396
try {
382397
const result = transcode(url, data, options);
383-
result.levels = result.levels.map(v => v.buffer);
384-
self.postMessage({ url: url, data: result }, result.levels);
398+
399+
// replace typed arrays with their backing buffers for transfer. cubemap levels
400+
// are arrays of six faces, all other levels are a single image.
401+
const transfer = [];
402+
result.levels = result.levels.map((level) => {
403+
if (Array.isArray(level)) {
404+
return level.map((face) => {
405+
transfer.push(face.buffer);
406+
return face.buffer;
407+
});
408+
}
409+
transfer.push(level.buffer);
410+
return level.buffer;
411+
});
412+
413+
self.postMessage({ url: url, data: result }, transfer);
385414
} catch (err) {
386415
self.postMessage({ url: url, err: err }, null);
387416
}

src/framework/handlers/basis.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,18 +169,15 @@ class BasisQueue {
169169
(callback[i])(err);
170170
}
171171
} else {
172-
// (re)create typed array from the returned array buffers
173-
if (data.format === PIXELFORMAT_RGB565 || data.format === PIXELFORMAT_RGBA4) {
174-
// handle 16 bit formats
175-
data.levels = data.levels.map((v) => {
176-
return new Uint16Array(v);
177-
});
178-
} else {
179-
// all other
180-
data.levels = data.levels.map((v) => {
181-
return new Uint8Array(v);
182-
});
183-
}
172+
// (re)create typed array from the returned array buffers. cubemap levels are
173+
// arrays of six face buffers, all other levels are a single image buffer.
174+
const TypedArray = (data.format === PIXELFORMAT_RGB565 || data.format === PIXELFORMAT_RGBA4) ?
175+
Uint16Array : Uint8Array;
176+
data.levels = data.levels.map((level) => {
177+
return Array.isArray(level) ?
178+
level.map(face => new TypedArray(face)) :
179+
new TypedArray(level);
180+
});
184181

185182
for (let i = 0; i < callback.length; ++i) {
186183
(callback[i])(null, data);
@@ -330,6 +327,8 @@ let deviceDetails = null;
330327
* circumstances the texture will be unswizzled during transcoding.
331328
* @param {boolean} [options.isKTX2] - Indicates the image is KTX2 format. Otherwise
332329
* basis format is assumed.
330+
* @param {boolean} [options.isCubemap] - Indicates the image contains six cubemap faces. Only
331+
* supported for KTX2 files.
333332
* @returns {boolean} True if the basis worker was initialized and false otherwise.
334333
* @ignore
335334
*/
@@ -345,7 +344,8 @@ function basisTranscode(device, url, data, callback, options) {
345344
queue.enqueueJob(url, data, callback, {
346345
deviceDetails: deviceDetails,
347346
isGGGR: !!options?.isGGGR,
348-
isKTX2: !!options?.isKTX2
347+
isKTX2: !!options?.isKTX2,
348+
isCubemap: !!options?.isCubemap
349349
});
350350

351351
return initializing;

src/framework/parsers/texture/ktx2.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ class Ktx2Parser extends TextureParser {
130130
callback,
131131
{
132132
isGGGR: (asset?.file?.variants?.basis?.opt & 8) !== 0,
133-
isKTX2: true
133+
isKTX2: true,
134+
// a six-face ktx2 file is a cubemap
135+
isCubemap: header.faceCount === 6
134136
}
135137
);
136138

0 commit comments

Comments
 (0)