UNPKG

@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
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);