// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type {TextureLevel} from '@loaders.gl/schema'; import {assert} from '@loaders.gl/loader-utils'; import {GL_EXTENSIONS_CONSTANTS} from '../gl-extensions'; import {extractMipmapImages} from '../utils/extract-mipmap-images'; const DDS_CONSTANTS = { MAGIC_NUMBER: 0x20534444, HEADER_LENGTH: 31, MAGIC_NUMBER_INDEX: 0, HEADER_SIZE_INDEX: 1, HEADER_FLAGS_INDEX: 2, HEADER_HEIGHT_INDEX: 3, HEADER_WIDTH_INDEX: 4, MIPMAPCOUNT_INDEX: 7, HEADER_PF_FLAGS_INDEX: 20, HEADER_PF_FOURCC_INDEX: 21, DDSD_MIPMAPCOUNT: 0x20000, DDPF_FOURCC: 0x4 }; const DDS_PIXEL_FORMATS: Record = { DXT1: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGB_S3TC_DXT1_EXT, DXT3: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_S3TC_DXT3_EXT, DXT5: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_S3TC_DXT5_EXT, 'ATC ': GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGB_ATC_WEBGL, ATCA: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL, ATCI: GL_EXTENSIONS_CONSTANTS.COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL }; const getATCLevelSize = getDxt1LevelSize; const getATCALevelSize = getDxtXLevelSize; const getATCILevelSize = getDxtXLevelSize; const DDS_SIZE_FUNCTIONS: Record number> = { DXT1: getDxt1LevelSize, DXT3: getDxtXLevelSize, DXT5: getDxtXLevelSize, 'ATC ': getATCLevelSize, ATCA: getATCALevelSize, ATCI: getATCILevelSize }; /** * Check if data is in "DDS" format by its magic number * @param data - binary data of compressed texture * @returns true - data in "DDS" format, else - false */ export function isDDS(data: ArrayBuffer): boolean { const header = new Uint32Array(data, 0, DDS_CONSTANTS.HEADER_LENGTH); const magic = header[DDS_CONSTANTS.MAGIC_NUMBER_INDEX]; return magic === DDS_CONSTANTS.MAGIC_NUMBER; } /** * Parse texture data as "DDS" format * @param data - binary data of compressed texture * @returns Array of the texture levels */ export function parseDDS(data: ArrayBuffer): TextureLevel[] { const header = new Int32Array(data, 0, DDS_CONSTANTS.HEADER_LENGTH); const pixelFormatNumber = header[DDS_CONSTANTS.HEADER_PF_FOURCC_INDEX]; assert( Boolean(header[DDS_CONSTANTS.HEADER_PF_FLAGS_INDEX] & DDS_CONSTANTS.DDPF_FOURCC), 'DDS: Unsupported format, must contain a FourCC code' ); const fourCC = int32ToFourCC(pixelFormatNumber); const internalFormat = DDS_PIXEL_FORMATS[fourCC]; const sizeFunction = DDS_SIZE_FUNCTIONS[fourCC]; assert(internalFormat && sizeFunction, `DDS: Unknown pixel format ${pixelFormatNumber}`); let mipMapLevels = 1; if (header[DDS_CONSTANTS.HEADER_FLAGS_INDEX] & DDS_CONSTANTS.DDSD_MIPMAPCOUNT) { mipMapLevels = Math.max(1, header[DDS_CONSTANTS.MIPMAPCOUNT_INDEX]); } const width = header[DDS_CONSTANTS.HEADER_WIDTH_INDEX]; const height = header[DDS_CONSTANTS.HEADER_HEIGHT_INDEX]; const dataOffset = header[DDS_CONSTANTS.HEADER_SIZE_INDEX] + 4; const image = new Uint8Array(data, dataOffset); return extractMipmapImages(image, { mipMapLevels, width, height, sizeFunction, internalFormat }); } /** * DXT1 applicable function to calculate level size * @param width - level width * @param height - level height * @returns level size in bytes */ export function getDxt1LevelSize(width: number, height: number): number { return ((width + 3) >> 2) * ((height + 3) >> 2) * 8; } /** * DXT3 & DXT5 applicable function to calculate level size * @param width - level width * @param height - level height * @returns level size in bytes */ export function getDxtXLevelSize(width: number, height: number): number { return ((width + 3) >> 2) * ((height + 3) >> 2) * 16; } /** * Convert every byte of Int32 value to char * @param value - Int32 number * @returns string of 4 characters */ function int32ToFourCC(value: number): string { return String.fromCharCode( value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff ); }