/* global require */ 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"; 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; } function initWorker(compiledModule) { transcoderModule = compiledModule; transcoderModule.initializeBasis(); self.onmessage = createTaskProcessorWorker(transcode); self.postMessage(true); } function transcodeKTX2(event) { const data = event.data; // Expect the first message to be to load a web assembly module const wasmConfig = data.webAssemblyConfig; if (defined(wasmConfig)) { // Require and compile WebAssembly module, or use fallback if not supported return require([wasmConfig.modulePath], function (mscBasisTranscoder) { if (defined(wasmConfig.wasmBinaryFile)) { if (!defined(mscBasisTranscoder)) { mscBasisTranscoder = self.MSC_TRANSCODER; } mscBasisTranscoder(wasmConfig).then(function (compiledModule) { initWorker(compiledModule); }); } else { return mscBasisTranscoder().then(function (transcoder) { initWorker(transcoder); }); } }); } } export default transcodeKTX2;