/* 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 ktx_parse from "../ThirdParty/ktx-parse.js"; var faceOrder = [ "positiveX", "negativeX", "positiveY", "negativeY", "positiveZ", "negativeZ", ]; // Flags var colorModelETC1S = 163; var colorModelUASTC = 166; var transcoderModule; function transcode(parameters, transferableObjects) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("transcoderModule", transcoderModule); //>>includeEnd('debug'); var data = parameters.ktx2Buffer; var supportedTargetFormats = parameters.supportedTargetFormats; var header; try { header = ktx_parse(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."); } var dfd = header.dataFormatDescriptor[0]; var 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) { var internalFormat = header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8_SRGB ? PixelFormat.RGB : PixelFormat.RGBA; var 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 (var i = 0; i < header.levels.length; ++i) { var level = {}; result[i] = level; var levelBuffer = header.levels[i].levelData; var width = header.pixelWidth >> i; var height = header.pixelHeight >> i; var faceLength = width * height * PixelFormat.componentsLength(internalFormat); for (var j = 0; j < header.faceCount; ++j) { // multiply levelBuffer.byteOffset by the size in bytes of the pixel data type var faceByteOffset = levelBuffer.byteOffset + faceLength * header.typeSize * j; var 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 ) { var ktx2File = new transcoderModule.KTX2File(data); var width = ktx2File.getWidth(); var height = ktx2File.getHeight(); var levels = ktx2File.getLevels(); var hasAlpha = ktx2File.getHasAlpha(); if (!(width > 0) || !(height > 0) || !(levels > 0)) { ktx2File.close(); ktx2File.delete(); throw new RuntimeError("Invalid KTX2 file"); } var internalFormat, transcoderFormat; var dfd = header.dataFormatDescriptor[0]; var 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 (var i = 0; i < header.levels.length; ++i) { var 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. var dstSize = ktx2File.getImageTranscodedSizeInBytes( i, // level index 0, // layer index 0, // face index transcoderFormat.value ); var dst = new Uint8Array(dstSize); var 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) { var data = event.data; // Expect the first message to be to load a web assembly module var 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;