UNPKG

@loaders.gl/textures

Version:

Framework-independent loaders for compressed and super compressed (basis) textures

243 lines (242 loc) 8.23 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { loadBasisEncoderModule, loadBasisTranscoderModule } from "./basis-module-loader.js"; import { GL_EXTENSIONS_CONSTANTS } from "../gl-extensions.js"; import { getSupportedGPUTextureFormats } from "../utils/texture-formats.js"; import { isKTX } from "./parse-ktx.js"; const OutputFormat = { etc1: { basisFormat: 0, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGB_ETC1_WEBGL }, etc2: { basisFormat: 1, compressed: true }, bc1: { basisFormat: 2, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGB_S3TC_DXT1_EXT }, bc3: { basisFormat: 3, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_S3TC_DXT5_EXT }, bc4: { basisFormat: 4, compressed: true }, bc5: { basisFormat: 5, compressed: true }, 'bc7-m6-opaque-only': { basisFormat: 6, compressed: true }, 'bc7-m5': { basisFormat: 7, compressed: true }, 'pvrtc1-4-rgb': { basisFormat: 8, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGB_PVRTC_4BPPV1_IMG }, 'pvrtc1-4-rgba': { basisFormat: 9, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG }, 'astc-4x4': { basisFormat: 10, compressed: true, format: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_ASTC_4X4_KHR }, 'atc-rgb': { basisFormat: 11, compressed: true }, 'atc-rgba-interpolated-alpha': { basisFormat: 12, compressed: true }, rgba32: { basisFormat: 13, compressed: false }, rgb565: { basisFormat: 14, compressed: false }, bgr565: { basisFormat: 15, compressed: false }, rgba4444: { basisFormat: 16, compressed: false } }; /** * parse data with a Binomial Basis_Universal module * @param data * @param options * @returns compressed texture data */ export async function parseBasis(data, options) { if (options.basis.containerFormat === 'auto') { if (isKTX(data)) { const fileConstructors = await loadBasisEncoderModule(options); return parseKTX2File(fileConstructors.KTX2File, data, options); } const { BasisFile } = await loadBasisTranscoderModule(options); return parseBasisFile(BasisFile, data, options); } switch (options.basis.module) { case 'encoder': const fileConstructors = await loadBasisEncoderModule(options); switch (options.basis.containerFormat) { case 'ktx2': return parseKTX2File(fileConstructors.KTX2File, data, options); case 'basis': default: return parseBasisFile(fileConstructors.BasisFile, data, options); } case 'transcoder': default: const { BasisFile } = await loadBasisTranscoderModule(options); return parseBasisFile(BasisFile, data, options); } } /** * Parse *.basis file data * @param BasisFile - initialized transcoder module * @param data * @param options * @returns compressed texture data */ function parseBasisFile(BasisFile, data, options) { const basisFile = new BasisFile(new Uint8Array(data)); try { if (!basisFile.startTranscoding()) { throw new Error('Failed to start basis transcoding'); } const imageCount = basisFile.getNumImages(); const images = []; for (let imageIndex = 0; imageIndex < imageCount; imageIndex++) { const levelsCount = basisFile.getNumLevels(imageIndex); const levels = []; for (let levelIndex = 0; levelIndex < levelsCount; levelIndex++) { levels.push(transcodeImage(basisFile, imageIndex, levelIndex, options)); } images.push(levels); } return images; } finally { basisFile.close(); basisFile.delete(); } } /** * Parse the particular level image of a basis file * @param basisFile * @param imageIndex * @param levelIndex * @param options * @returns compressed texture data */ function transcodeImage(basisFile, imageIndex, levelIndex, options) { const width = basisFile.getImageWidth(imageIndex, levelIndex); const height = basisFile.getImageHeight(imageIndex, levelIndex); // See https://github.com/BinomialLLC/basis_universal/pull/83 const hasAlpha = basisFile.getHasAlpha( /* imageIndex, levelIndex */); // Check options for output format etc const { compressed, format, basisFormat } = getBasisOptions(options, hasAlpha); const decodedSize = basisFile.getImageTranscodedSizeInBytes(imageIndex, levelIndex, basisFormat); const decodedData = new Uint8Array(decodedSize); if (!basisFile.transcodeImage(decodedData, imageIndex, levelIndex, basisFormat, 0, 0)) { throw new Error('failed to start Basis transcoding'); } return { // standard loaders.gl image category payload width, height, data: decodedData, compressed, format, // Additional fields // Add levelSize field. hasAlpha }; } /** * Parse *.ktx2 file data * @param KTX2File * @param data * @param options * @returns compressed texture data */ function parseKTX2File(KTX2File, data, options) { const ktx2File = new KTX2File(new Uint8Array(data)); try { if (!ktx2File.startTranscoding()) { throw new Error('failed to start KTX2 transcoding'); } const levelsCount = ktx2File.getLevels(); const levels = []; for (let levelIndex = 0; levelIndex < levelsCount; levelIndex++) { levels.push(transcodeKTX2Image(ktx2File, levelIndex, options)); } return [levels]; } finally { ktx2File.close(); ktx2File.delete(); } } /** * Parse the particular level image of a ktx2 file * @param ktx2File * @param levelIndex * @param options * @returns */ function transcodeKTX2Image(ktx2File, levelIndex, options) { const { alphaFlag, height, width } = ktx2File.getImageLevelInfo(levelIndex, 0, 0); // Check options for output format etc const { compressed, format, basisFormat } = getBasisOptions(options, alphaFlag); const decodedSize = ktx2File.getImageTranscodedSizeInBytes(levelIndex, 0 /* layerIndex */, 0 /* faceIndex */, basisFormat); const decodedData = new Uint8Array(decodedSize); if (!ktx2File.transcodeImage(decodedData, levelIndex, 0 /* layerIndex */, 0 /* faceIndex */, basisFormat, 0, -1 /* channel0 */, -1 /* channel1 */)) { throw new Error('Failed to transcode KTX2 image'); } return { // standard loaders.gl image category payload width, height, data: decodedData, compressed, // Additional fields levelSize: decodedSize, hasAlpha: alphaFlag, format }; } /** * Get BasisFormat by loader format option * @param options * @param hasAlpha * @returns BasisFormat data */ function getBasisOptions(options, hasAlpha) { let format = options && options.basis && options.basis.format; if (format === 'auto') { format = selectSupportedBasisFormat(); } if (typeof format === 'object') { format = hasAlpha ? format.alpha : format.noAlpha; } format = format.toLowerCase(); return OutputFormat[format]; } /** * Select transcode format from the list of supported formats * @returns key for OutputFormat map */ export function selectSupportedBasisFormat() { const supportedFormats = getSupportedGPUTextureFormats(); if (supportedFormats.has('astc')) { return 'astc-4x4'; } else if (supportedFormats.has('dxt')) { return { alpha: 'bc3', noAlpha: 'bc1' }; } else if (supportedFormats.has('pvrtc')) { return { alpha: 'pvrtc1-4-rgba', noAlpha: 'pvrtc1-4-rgb' }; } else if (supportedFormats.has('etc1')) { return 'etc1'; } else if (supportedFormats.has('etc2')) { return 'etc2'; } return 'rgb565'; }