@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
309 lines (276 loc) • 9.29 kB
JavaScript
import defined from "../Core/defined.js";
import Check from "../Core/Check.js";
import PixelFormat from "../Core/PixelFormat.js";
import RuntimeError from "../Core/RuntimeError.js";
import VulkanConstants from "../Core//VulkanConstants.js";
import PixelDatatype from "../Renderer/PixelDatatype.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import { read } from "ktx-parse";
import basis from "../ThirdParty/Workers/basis_transcoder.js";
const faceOrder = [
"positiveX",
"negativeX",
"positiveY",
"negativeY",
"positiveZ",
"negativeZ",
];
// Flags
const colorModelETC1S = 163;
const colorModelUASTC = 166;
let transcoderModule;
function transcode(parameters, transferableObjects) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("transcoderModule", transcoderModule);
//>>includeEnd('debug');
const data = parameters.ktx2Buffer;
const supportedTargetFormats = parameters.supportedTargetFormats;
let header;
try {
header = read(data);
} catch (e) {
throw new RuntimeError("Invalid KTX2 file.");
}
if (header.layerCount !== 0) {
throw new RuntimeError("KTX2 texture arrays are not supported.");
}
if (header.pixelDepth !== 0) {
throw new RuntimeError("KTX2 3D textures are unsupported.");
}
const dfd = header.dataFormatDescriptor[0];
const result = new Array(header.levelCount);
if (
header.vkFormat === 0x0 &&
(dfd.colorModel === colorModelETC1S || dfd.colorModel === colorModelUASTC)
) {
// Compressed, initialize transcoder module
transcodeCompressed(
data,
header,
supportedTargetFormats,
transcoderModule,
transferableObjects,
result,
);
} else {
transferableObjects.push(data.buffer);
parseUncompressed(header, result);
}
return result;
}
// Parser for uncompressed
function parseUncompressed(header, result) {
const internalFormat =
header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8_SRGB
? PixelFormat.RGB
: PixelFormat.RGBA;
let datatype;
if (header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8A8_UNORM) {
datatype = PixelDatatype.UNSIGNED_BYTE;
} else if (
header.vkFormat === VulkanConstants.VK_FORMAT_R16G16B16A16_SFLOAT
) {
datatype = PixelDatatype.HALF_FLOAT;
} else if (
header.vkFormat === VulkanConstants.VK_FORMAT_R32G32B32A32_SFLOAT
) {
datatype = PixelDatatype.FLOAT;
}
for (let i = 0; i < header.levels.length; ++i) {
const level = {};
result[i] = level;
const levelBuffer = header.levels[i].levelData;
const width = header.pixelWidth >> i;
const height = header.pixelHeight >> i;
const faceLength =
width * height * PixelFormat.componentsLength(internalFormat);
for (let j = 0; j < header.faceCount; ++j) {
// multiply levelBuffer.byteOffset by the size in bytes of the pixel data type
const faceByteOffset =
levelBuffer.byteOffset + faceLength * header.typeSize * j;
let faceView;
if (!defined(datatype) || PixelDatatype.sizeInBytes(datatype) === 1) {
faceView = new Uint8Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
} else if (PixelDatatype.sizeInBytes(datatype) === 2) {
faceView = new Uint16Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
} else {
faceView = new Float32Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
}
level[faceOrder[j]] = {
internalFormat: internalFormat,
datatype: datatype,
width: width,
height: height,
levelBuffer: faceView,
};
}
}
}
function transcodeCompressed(
data,
header,
supportedTargetFormats,
transcoderModule,
transferableObjects,
result,
) {
const ktx2File = new transcoderModule.KTX2File(data);
let width = ktx2File.getWidth();
let height = ktx2File.getHeight();
const levels = ktx2File.getLevels();
const hasAlpha = ktx2File.getHasAlpha();
if (!(width > 0) || !(height > 0) || !(levels > 0)) {
ktx2File.close();
ktx2File.delete();
throw new RuntimeError("Invalid KTX2 file");
}
let internalFormat, transcoderFormat;
const dfd = header.dataFormatDescriptor[0];
const BasisFormat = transcoderModule.transcoder_texture_format;
// Determine target format based on platform support
if (dfd.colorModel === colorModelETC1S) {
if (supportedTargetFormats.etc) {
internalFormat = hasAlpha
? PixelFormat.RGBA8_ETC2_EAC
: PixelFormat.RGB8_ETC2;
transcoderFormat = hasAlpha
? BasisFormat.cTFETC2_RGBA
: BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.etc1 && !hasAlpha) {
internalFormat = PixelFormat.RGB_ETC1;
transcoderFormat = BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.s3tc) {
internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
transcoderFormat = hasAlpha
? BasisFormat.cTFBC3_RGBA
: BasisFormat.cTFBC1_RGB;
} else if (supportedTargetFormats.pvrtc) {
internalFormat = hasAlpha
? PixelFormat.RGBA_PVRTC_4BPPV1
: PixelFormat.RGB_PVRTC_4BPPV1;
transcoderFormat = hasAlpha
? BasisFormat.cTFPVRTC1_4_RGBA
: BasisFormat.cTFPVRTC1_4_RGB;
} else if (supportedTargetFormats.astc) {
internalFormat = PixelFormat.RGBA_ASTC;
transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
} else if (supportedTargetFormats.bc7) {
internalFormat = PixelFormat.RGBA_BC7;
transcoderFormat = BasisFormat.cTFBC7_RGBA;
} else {
throw new RuntimeError(
"No transcoding format target available for ETC1S compressed ktx2.",
);
}
} else if (dfd.colorModel === colorModelUASTC) {
if (supportedTargetFormats.astc) {
internalFormat = PixelFormat.RGBA_ASTC;
transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
} else if (supportedTargetFormats.bc7) {
internalFormat = PixelFormat.RGBA_BC7;
transcoderFormat = BasisFormat.cTFBC7_RGBA;
} else if (supportedTargetFormats.s3tc) {
internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
transcoderFormat = hasAlpha
? BasisFormat.cTFBC3_RGBA
: BasisFormat.cTFBC1_RGB;
} else if (supportedTargetFormats.etc) {
internalFormat = hasAlpha
? PixelFormat.RGBA8_ETC2_EAC
: PixelFormat.RGB8_ETC2;
transcoderFormat = hasAlpha
? BasisFormat.cTFETC2_RGBA
: BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.etc1 && !hasAlpha) {
internalFormat = PixelFormat.RGB_ETC1;
transcoderFormat = BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.pvrtc) {
internalFormat = hasAlpha
? PixelFormat.RGBA_PVRTC_4BPPV1
: PixelFormat.RGB_PVRTC_4BPPV1;
transcoderFormat = hasAlpha
? BasisFormat.cTFPVRTC1_4_RGBA
: BasisFormat.cTFPVRTC1_4_RGB;
} else {
throw new RuntimeError(
"No transcoding format target available for UASTC compressed ktx2.",
);
}
}
if (!ktx2File.startTranscoding()) {
ktx2File.close();
ktx2File.delete();
throw new RuntimeError("startTranscoding() failed");
}
for (let i = 0; i < header.levels.length; ++i) {
const level = {};
result[i] = level;
width = header.pixelWidth >> i;
height = header.pixelHeight >> i;
// Since supercompressed cubemaps are unsupported, this function
// does not iterate over KTX2 faces and assumes faceCount = 1.
const dstSize = ktx2File.getImageTranscodedSizeInBytes(
i, // level index
0, // layer index
0, // face index
transcoderFormat.value,
);
const dst = new Uint8Array(dstSize);
const transcoded = ktx2File.transcodeImage(
dst,
i, // level index
0, // layer index
0, // face index
transcoderFormat.value,
0, // get_alpha_for_opaque_formats
-1, // channel0
-1, // channel1
);
if (!defined(transcoded)) {
throw new RuntimeError("transcodeImage() failed.");
}
transferableObjects.push(dst.buffer);
level[faceOrder[0]] = {
internalFormat: internalFormat,
width: width,
height: height,
levelBuffer: dst,
};
}
ktx2File.close();
ktx2File.delete();
return result;
}
async function initWorker(parameters, transferableObjects) {
// Require and compile WebAssembly module, or use fallback if not supported
const wasmConfig = parameters.webAssemblyConfig;
const basisTranscoder = basis ?? self.BASIS;
if (defined(wasmConfig.wasmBinaryFile)) {
transcoderModule = await basisTranscoder(wasmConfig);
} else {
transcoderModule = await basisTranscoder();
}
transcoderModule.initializeBasis();
return true;
}
function transcodeKTX2(parameters, transferableObjects) {
// Expect the first message to be to load a web assembly module
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig)) {
return initWorker(parameters, transferableObjects);
}
return transcode(parameters, transferableObjects);
}
export default createTaskProcessorWorker(transcodeKTX2);